使用 Teams AI 库进行生成

Teams AI 库简化了使用 AI 组件生成智能Microsoft Teams 应用程序。 它提供用于数据访问和自定义 UI 创建的 API。 可以轻松集成即时管理和安全审查,并使用 OpenAI 或 Azure OpenAI 创建机器人,获得 AI 驱动的体验。

初始设置

Teams AI 库基于 Bot Framework SDK 构建,使用其基础知识提供 Bot Framework SDK 功能的扩展。 作为初始设置的一部分,导入 Bot Framework SDK 功能非常重要。 处理与通道连接的适配器类是从 Bot Framework SDK 导入的。

示例代码参考

using Microsoft.Teams.AI;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.TeamsFx.Conversation;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600));
builder.Services.AddHttpContextAccessor();

// Prepare Configuration for ConfigurationBotFrameworkAuthentication
var config = builder.Configuration.Get<ConfigOptions>();
builder.Configuration["MicrosoftAppType"] = "MultiTenant";
builder.Configuration["MicrosoftAppId"] = config.BOT_ID;
builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD;

// Create the Bot Framework Authentication to be used with the Bot Adapter.
builder.Services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

// Create the Cloud Adapter with error handling enabled.
// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so
// register the same adapter instance for all types.
builder.Services.AddSingleton<CloudAdapter, AdapterWithErrorHandler>();
builder.Services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
builder.Services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());

导入 Teams AI 库

@microsoft/teams-ai 中导入所有类以生成机器人并使用 Teams AI 库功能。

示例代码参考

// import Teams AI library
import {
    AI,
    Application,
    ActionPlanner,
    OpenAIModerator,
    OpenAIModel,
    PromptManager,
    TurnState
} from '@microsoft/teams-ai';
import { addResponseFormatter } from './responseFormatter';
import { VectraDataSource } from './VectraDataSource';

创建 AI 组件

在现有机器人应用或新的 Bot Framework 应用中创建 AI 组件:

  • OpenAIModel:OpenAIModel 类提供了一种访问 OpenAI API 或任何其他服务的方法,该服务遵循 OpenAI REST 格式。 它与 OpenAI 和 Azure OpenAI 语言模型兼容。

  • 提示管理器:提示管理器处理提示创建。 它调用函数,并将代码中的注入到提示符中。 它会自动将聊天状态和用户状态复制到提示中。

  • ActionPlanner:ActionPlanner 是调用大型语言模型 (LLM) 的main组件,包括用于增强和自定义模型的多项功能。 它负责根据用户的输入和可用操作生成和执行计划。

示例代码参考

    // Create model
    
    OpenAIModel? model = null;
    
    if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey))
    {
        model = new(new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo"));
    }
    else if (!string.IsNullOrEmpty(config.Azure?.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint))
    {
        model = new(new AzureOpenAIModelOptions(
            config.Azure.OpenAIApiKey,
            "gpt-35-turbo",
            config.Azure.OpenAIEndpoint
        ));
    }
    
    if (model == null)
    {
        throw new Exception("please configure settings for either OpenAI or Azure");
    }

    // Create prompt manager
    PromptManager prompts = new(new()
    {
        PromptFolder = "./Prompts",
    });

    // Add function to be referenced in the prompt template

    prompts.AddFunction("getLightStatus", async (context, memory, functions, tokenizer, args) =>
    {
        bool lightsOn = (bool)(memory.GetValue("conversation.lightsOn") ?? false);
        return await Task.FromResult(lightsOn ? "on" : "off");
    });

    // Create ActionPlanner
    ActionPlanner<AppState> planner = new(
        options: new(
            model: model,
            prompts: prompts,
            defaultPrompt: async (context, state, planner) =>
            {
                PromptTemplate template = prompts.GetPrompt("sequence");
                return await Task.FromResult(template);
            }
        )
        { LogRepairs = true },
        loggerFactory: loggerFactory
    );

定义存储和应用程序

应用程序对象自动管理机器人的聊天和用户状态。

  • 存储:创建存储提供程序来存储机器人的对话和用户状态。

  • 应用程序:在 类中 Application 为应用注册操作或活动处理程序,该类包含应用所需的所有信息和机器人逻辑。

示例代码参考

 return new TeamsLightBot(new()
    {
        Storage = sp.GetService<IStorage>(),
        AI = new(planner),
        LoggerFactory = loggerFactory,
        TurnStateFactory = () =>
        {
            return new AppState();
        }
    });

TurnStateFactory 允许你为应用创建自定义状态类,以存储机器人的额外信息或逻辑。 可以替代默认属性,例如用户输入、机器人输出或对话历史记录。 若要使用它,请创建一个扩展默认轮次状态的类,并将创建类实例的函数传递给应用构造函数。

注册数据源

矢量数据源简化了将 RAG 添加到任何提示。 向规划器注册命名数据源,并在提示 config.json 文件中指定它以增加提示。 这允许 AI 将来自外部源(例如矢量数据库或认知搜索)的相关信息注入提示。

示例代码参考

// Register your data source with planner
planner.prompts.addDataSource(new VectraDataSource({
    name: 'teams-ai',
    apiKey:  process.env.OPENAI_API_KEY!,
    indexFolder: path.join(__dirname, '../index'),
}));

嵌入

嵌入是 LLM 生成的向量,它表示文本,以紧凑形式捕获其语义含义。 它用于文本分类、情绪分析和搜索等任务。 用于生成嵌入的模型不同于基础 LLM。 例如,OpenAI 的 text-embedding-ada-002 模型返回表示输入文本的 1536 个数字列表。 这些嵌入存储在矢量数据库中。 在自定义引擎代理中,可以通过从矢量数据库检索相关数据并使用此信息扩充提示来实现 RAG 模式。


下面是 VectraDataSource 和 OpenAIEmbeddings 的示例:
import { DataSource, Memory, RenderedPromptSection, Tokenizer } from '@microsoft/teams-ai';
import { OpenAIEmbeddings, LocalDocumentIndex } from 'vectra';
import * as path from 'path';
import { TurnContext } from 'botbuilder';

/**
 * Options for creating a `VectraDataSource`.
 */
export interface VectraDataSourceOptions {
    /**
     * Name of the data source and local index.
     */
    name: string;

    /**
     * OpenAI API key to use for generating embeddings.
     */
    apiKey: string;

    /**
     * Path to the folder containing the local index.
     * @remarks
     * This should be the root folder for all local indexes and the index itself
     * needs to be in a subfolder under this folder.
     */
    indexFolder: string;

    /**
     * Optional. Maximum number of documents to return.
     * @remarks
     * Defaults to `5`.
     */
    maxDocuments?: number;

    /**
     * Optional. Maximum number of chunks to return per document.
     * @remarks
     * Defaults to `50`.
     */
    maxChunks?: number;

    /**
     * Optional. Maximum number of tokens to return per document.
     * @remarks
     * Defaults to `600`.
     */
    maxTokensPerDocument?: number;
}

/**
 * A data source that uses a local Vectra index to inject text snippets into a prompt.
 */
export class VectraDataSource implements DataSource {
    private readonly _options: VectraDataSourceOptions;
    private readonly _index: LocalDocumentIndex;

    /**
     * Name of the data source.
     * @remarks
     * This is also the name of the local Vectra index.
     */
    public readonly name: string;

    /**
     * Creates a new `VectraDataSource` instance.
     * @param options Options for creating the data source.
     */
    public constructor(options: VectraDataSourceOptions) {
        this._options = options;
        this.name = options.name;

        // Create embeddings model
        const embeddings = new OpenAIEmbeddings({
            model: 'text-embedding-ada-002',
            apiKey: options.apiKey,
        });

        // Create local index
        this._index = new LocalDocumentIndex({
            embeddings,
            folderPath: path.join(options.indexFolder, options.name),
        });
    }

    /**
     * Renders the data source as a string of text.
     * @param context Turn context for the current turn of conversation with the user.
     * @param memory An interface for accessing state values.
     * @param tokenizer Tokenizer to use when rendering the data source.
     * @param maxTokens Maximum number of tokens allowed to be rendered.
     */
    public async renderData(context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number): Promise<RenderedPromptSection<string>> {
        // Query index
        const query = memory.getValue('temp.input') as string;
        const results = await this._index.queryDocuments(query, {
            maxDocuments: this._options.maxDocuments ?? 5,
            maxChunks: this._options.maxChunks ?? 50,
        });

        // Add documents until you run out of tokens
        let length = 0;
        let output = '';
        let connector = '';
        for (const result of results) {
            // Start a new doc
            let doc = `${connector}url: ${result.uri}\n`;
            let docLength = tokenizer.encode(doc).length;
            const remainingTokens = maxTokens - (length + docLength);
            if (remainingTokens <= 0) {
                break;
            }

            // Render document section
            const sections = await result.renderSections(Math.min(remainingTokens, this._options.maxTokensPerDocument ?? 600), 1);
            docLength += sections[0].tokenCount;
            doc += sections[0].text;

            // Append do to output
            output += doc;
            length += docLength;
            connector = '\n\n';
        }

        return { output, length, tooLong: length > maxTokens };
    }

}

提示

提示是用于创建对话体验的文本片段,例如开始对话、提问和生成响应。 它们简化了创建具有吸引力的交互的过程。 新的基于对象的提示系统将提示划分为多个部分,每个部分都有自己的令牌预算,这些预算可以是固定集,也可以与剩余令牌成比例。 可以为文本完成和聊天完成样式 API 生成提示。

若要创建有效的提示,请遵循以下准则:

  • 提供说明和/或示例。
  • 确保具有足够示例的质量数据并对其进行校对。 虽然模型可以识别拼写错误,但它可能会假设拼写错误的有意性,从而影响响应。
  • 使用 temperaturetop_p 调整提示设置,以控制模型的响应。 较高的温度(如 0.8)使输出随机,而较低的温度(如 0.2)使输出具有焦点和确定性。

创建名为 prompts 的文件夹,并在其中定义提示。 当用户使用文本提示与机器人交互时,它会响应文本补全。 在 prompts 文件夹中创建以下文件:

  • skprompt.txt:包含提示文本,并支持模板变量和函数。

  • config.json:包含确保机器人响应符合要求的提示模型设置

    示例代码参考

     {
        "schema": 1.1,
        "description": "A bot that can turn the lights on and off",
        "type": "completion",
        "completion": {
            "model": "gpt-3.5-turbo",
            "completion_type": "chat",
            "include_history": true,
            "include_input": true,
            "max_input_tokens": 2800,
            "max_tokens": 1000,
            "temperature": 0.2,
            "top_p": 0.0,
            "presence_penalty": 0.6,
            "frequency_penalty": 0.0,
            "stop_sequences": []
        },
        "augmentation": {
            "augmentation_type": "sequence"
            "data_sources": {
                 "teams-ai": 1200
         }
        }
      }
    

查询参数

下表列出了查询参数:

说明
model 要使用的模型的 ID。
completion_type 要用于模型的完成类型。 如果出现提示,模型将返回一个或多个预测完成,以及每个位置的替代令牌的概率。
支持的选项: chattext
默认值:chat
include_history Boolean 值。 如果要包含历史记录。 每个提示获取其自己的单独对话历史记录,以确保模型不会混淆。
include_input Boolean 值。 如果要在提示中包含用户的输入。
max_input_tokens 输入的最大令牌数。 支持的最大令牌数为 4000。
max_tokens 完成时要生成的最大令牌数。 提示符加 max_tokens 的标记计数不能超过模型的上下文长度。
temperature 要使用的采样温度介于 0 和 2 之间。 较高的值(如 0.8)会使输出更加随机,而较低的值(如 0.2)会使输出更加集中和确定。
top_p 温度采样的替代方法,称为核采样,其中模型考虑具有 top_p 概率质量的标记的结果。 因此,0.1 表示仅考虑包含前 10% 概率质量的标记。
presence_penalty 介于 -2.0 和 2.0 之间的数字。 正值会根据新令牌是否出现在文本中,从而增加模型谈论新主题的可能性。
frequency_penalty 介于 -2.0 和 2.0 之间的数字。 正值会根据新标记在文本中的现有频率来惩罚这些标记,从而降低模型逐字重复同一行的可能性。
stop_sequences 最多四个序列,其中 API 停止生成更多令牌。 返回的文本不包含停止序列。
augmentation_type 扩充的类型。 支持的值为 sequencemonologuetools

提示管理

提示管理根据令牌预算和数据源调整提示的大小和内容。 对于具有 4,000 个令牌限制的机器人,其中 2,800 个令牌用于输入,1,000 个令牌用于输出,模型管理上下文窗口以保持在 3,800 个令牌内。 它以 100 个文本标记开头,并从数据源添加 1,200 个令牌。 它将剩余的 1,500 个令牌分配给对话历史记录和输入,并确保模型永远不会超过 2,800 个令牌。

提示操作

计划允许模型执行操作或响应用户。 可以创建计划的架构,并添加支持执行操作和传递参数的操作列表。 OpenAI 终结点确定必要的操作、提取实体,并将其作为参数传递给操作调用。

The following is a conversation with an AI assistant.
The assistant can turn a light on or off.

context:
The lights are currently {{getLightStatus}}.

提示模板

提示模板是使用纯文本定义和撰写 AI 函数的一种简单而强大的方法。 可以创建自然语言提示、生成响应、提取信息、调用其他提示或执行任何基于文本的任务。

语言支持包含变量、调用外部函数以及将参数传递给函数的功能。 无需编写任何代码或导入任何外部库,只需使用大括号 {{...} 以在提示中嵌入表达式。 Teams 分析模板并执行模板背后的逻辑。 这样,你可以以最少的工作量和最大的灵活性轻松地将 AI 集成到应用中。

  • {{function}}:调用已注册的函数并插入其返回值字符串。

  • {{$input}}:插入消息文本。 它从 state.temp.input 获取其值。

  • {{$state.[property]}}:插入状态属性。

操作

操作处理由 AI 组件触发的事件。

FlaggedInputActionFlaggedOutputAction 是用于处理审查器标志的内置操作处理程序。 如果审查器标记了传入消息输入,审查器将重定向到 FlaggedInputAction 处理程序,并 context.sendActivity 向用户发送有关该标志的消息。 如果要停止操作,则必须添加 AI.StopCommandName

示例代码参考

// Register other AI actions
app.ai.action(
    AI.FlaggedInputActionName,
    async (context: TurnContext, state: ApplicationTurnState, data: Record<string, any>) => {
        await context.sendActivity(`I'm sorry your message was flagged: ${JSON.stringify(data)}`);
        return AI.StopCommandName;
    }
);

app.ai.action(AI.FlaggedOutputActionName, async (context: TurnContext, state: ApplicationTurnState, data: any) => {
    await context.sendActivity(`I'm not allowed to talk about such things.`);
    return AI.StopCommandName;
});

注册操作处理程序

操作处理程序可帮助用户实现目标,这些目标在用户意向中共享。 操作处理程序中的一个关键方面是,必须首先在提示中注册操作,然后为提示中列出的每个操作(包括未知操作)注册处理程序。

在以下示例的轻型机器人中 LightsOn,我们有 、 LightsOffPause 操作。 每次调用操作时,都会返回 string。 如果需要机器人返回时间,则无需分析时间并将其转换为数字。 属性 PauseParameters 确保它以数字格式返回时间,而不会暂停提示。

示例代码参考

public class LightBotActions
    {
        [Action("LightsOn")]
        public async Task<string> LightsOn([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState)
        {
            turnState.Conversation!.LightsOn = true;
            await turnContext.SendActivityAsync(MessageFactory.Text("[lights on]"));
            return "the lights are now on";
        }

        [Action("LightsOff")]
        public async Task<string> LightsOff([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState)
        {
            turnState.Conversation!.LightsOn = false;
            await turnContext.SendActivityAsync(MessageFactory.Text("[lights off]"));
            return "the lights are now off";
        }

        [Action("Pause")]
        public async Task<string> LightsOff([ActionTurnContext] ITurnContext turnContext, [ActionParameters] Dictionary<string, object> args)
        {
            // Try to parse entities returned by the model.
            // Expecting "time" to be a number of milliseconds to pause.
            if (args.TryGetValue("time", out object? time))
            {
                if (time != null && time is string timeString)
                {
                    if (int.TryParse(timeString, out int timeInt))
                    {
                        await turnContext.SendActivityAsync(MessageFactory.Text($"[pausing for {timeInt / 1000} seconds]"));
                        await Task.Delay(timeInt);
                    }
                }
            }

            return "done pausing";
        }

        [Action("LightStatus")]
        public async Task<string> LightStatus([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState)
        {
            await turnContext.SendActivityAsync(ResponseGenerator.LightStatus(turnState.Conversation!.LightsOn));
            return turnState.Conversation!.LightsOn ? "the lights are on" : "the lights are off";
        }

        [Action(AIConstants.UnknownActionName)]
        public async Task<string> UnknownAction([ActionTurnContext] TurnContext turnContext, [ActionName] string action)
        {
            await turnContext.SendActivityAsync(ResponseGenerator.UnknownAction(action ?? "Unknown"));
            return "unknown action";
        }
    }
}

使用序列、独白或工具扩充可防止模型产生无效函数名称、操作名称或参数的幻觉。 创建操作文件以:

  • 定义用于提示扩充的操作。
  • 指示何时执行操作。

序列扩充非常适合多步骤或复杂任务,而独白扩充则适合需要自然语言理解、灵活性和创造力的任务。

在轻型机器人的以下示例中 actions.json ,该文件包含机器人可以执行的所有操作的列表:

[
    {
        "name": "LightsOn",
        "description": "Turns on the lights"
    },
    {
        "name": "LightsOff",
        "description": "Turns off the lights"
    },
    {
        "name": "Pause",
        "description": "Delays for a period of time",
        "parameters": {
            "type": "object",
            "properties": {
                "time": {
                    "type": "number",
                    "description": "The amount of time to delay in milliseconds"
                }
            },
            "required": [
                "time"
            ]
        }
    }
]
  • name:操作的名称。 必填。
  • description:操作的说明。 可选。
  • parameters:添加所需参数的 JSON 架构对象。

反馈循环是模型用于验证、更正或优化问题的答案的响应。 如果使用 sequence 扩充,可以通过以下方式禁用循环以防止任何意外循环:

  • 可以在定义中AIOptionsfalse 设置为 allow_looping?
  • 可以在 文件中将 设置为 max_repair_attempts0index.ts

管理历史记录

可以使用 MaxHistoryMessagesMaxConversationHistoryTokens 参数允许 AI 库自动管理历史记录。

反馈循环

反馈循环有助于监视和改进机器人的交互,从而生成更有效且用户友好的应用程序。 反馈用于调整和增强机器人以满足用户需求和期望。 反馈循环包括:

  • 修复Loop:如果模型的响应不足,则触发。 会话历史记录分叉,使系统能够在不影响main对话的情况下尝试不同的解决方案。
  • 验证:验证更正的响应,并在成功验证响应时将其重新插入到main会话中。
  • 从错误中学习:模型从正确的行为示例中学习,以避免将来出现类似的错误。
  • 处理复杂命令:模型在从错误中吸取教训后,能够处理更复杂的命令。

提升传统机器人以使用 AI

可以将现有传统机器人提升为 AI 提供支持。 生成机器人后,可以添加 AI 层,为机器人启用 AI 支持的功能。

以下代码片段演示了如何将 AI 组件添加到机器人。 在此示例中,机器人使用 Bot Framework 适配器来处理传入请求,然后使用 对象运行 AI 层 app

// Create AI components
const model = new OpenAIModel({
    // OpenAI Support
    apiKey: process.env.OPENAI_KEY!,
    defaultModel: 'gpt-4o',

    // Azure OpenAI Support
    azureApiKey: process.env.AZURE_OPENAI_KEY!,
    azureDefaultDeployment: 'gpt-4o',
    azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT!,
    azureApiVersion: '2023-03-15-preview',

    // Request logging
    logRequests: true
});

const prompts = new PromptManager({
    promptsFolder: path.join(__dirname, '../src/prompts')
});

// Define a prompt function for getting the current status of the lights
prompts.addFunction('getLightStatus', async (context: TurnContext, memory: Memory) => {
    return memory.getValue('conversation.lightsOn') ? 'on' : 'off';
});

const planner = new ActionPlanner({
    model,
    prompts,
    defaultPrompt: 'tools'
});

// Define storage and application
const storage = new MemoryStorage();
const app = new Application<ApplicationTurnState>({
    storage,
    ai: {
        planner
    }
});

app.ai.action('LightStatus', async (context: TurnContext, state: ApplicationTurnState) => {
    const status = state.conversation.lightsOn ? 'on' : 'off';
    return `the lights are ${status}`;
});

// Register action handlers
app.ai.action('LightsOn', async (context: TurnContext, state: ApplicationTurnState) => {
    state.conversation.lightsOn = true;
    await context.sendActivity(`[lights on]`);
    return `the lights are now on`;
});

app.ai.action('LightsOff', async (context: TurnContext, state: ApplicationTurnState) => {
    state.conversation.lightsOn = false;
    await context.sendActivity(`[lights off]`);
    return `the lights are now off`;
});

interface PauseParameters {
    time: number;
}

app.ai.action('Pause', async (context: TurnContext, state: ApplicationTurnState, parameters: PauseParameters) => {
    await context.sendActivity(`[pausing for ${parameters.time / 1000} seconds]`);
    await new Promise((resolve) => setTimeout(resolve, parameters.time));
    return `done pausing`;
});

// Listen for incoming server requests.
server.post('/api/messages', async (req, res) => {
    // Route received a request to adapter for processing
    await adapter.process(req, res as any, async (context) => {
        // Dispatch to application for routing
        await app.run(context);
    });
});

迁移机器人以使用 Teams AI 库

如果使用 Bot Framework SDK 创建了机器人应用,则可以切换到 Teams AI 库以使用其高级 AI 功能。 此迁移具有以下优势:

  • 高级 AI 系统,用于创建由 LLM 提供支持的复杂 Teams 应用程序。
  • 用户身份验证已集成到库中,使设置更加轻松。
  • 基于 Bot Framework SDK 工具和概念构建,使现有知识可转移。
  • 支持 LLM 空间中的最新工具和 API。

在 Teams AI 库中, Application 对象取代了传统 ActivityHandler 对象,与基于 ActivityHandler 继承的类相比,该对象支持更简单、流畅的机器人创作风格。 它包括对以下项的内置支持:

  • 调用 Teams AI 库的系统,以创建使用 LLM 和其他 AI 功能的机器人。
  • 配置用于访问第三方用户数据的用户身份验证。

使用以下方法之一迁移机器人应用以使用 Teams AI 库:

迁移 Bot Framework SDK 应用... 若要使用 Teams AI 库...
使用 JavaScript 生成的机器人应用 迁移
使用 C 生成的机器人应用# 迁移
使用 Python 的机器人应用 迁移

代码示例

示例名称 说明 .NET Node.js
操作映射 lightbot 此示例演示 LightBot 如何理解用户意图,准确解释命令以轻松控制光机器人。 View View

后续步骤