使用 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 生成提示。
若要创建有效的提示,请遵循以下准则:
- 提供说明和/或示例。
- 确保具有足够示例的质量数据并对其进行校对。 虽然模型可以识别拼写错误,但它可能会假设拼写错误的有意性,从而影响响应。
- 使用
temperature
和top_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 |
要用于模型的完成类型。 如果出现提示,模型将返回一个或多个预测完成,以及每个位置的替代令牌的概率。 支持的选项: chat 和 text 。 默认值: 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 |
扩充的类型。 支持的值为 sequence 、 monologue 和 tools 。 |
提示管理
提示管理根据令牌预算和数据源调整提示的大小和内容。 对于具有 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 组件触发的事件。
FlaggedInputAction
和 FlaggedOutputAction
是用于处理审查器标志的内置操作处理程序。 如果审查器标记了传入消息输入,审查器将重定向到 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
,我们有 、 LightsOff
和 Pause
操作。 每次调用操作时,都会返回 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
扩充,可以通过以下方式禁用循环以防止任何意外循环:
- 可以在定义中
AIOptions
将false
设置为allow_looping?
。 - 可以在 文件中将 设置为
max_repair_attempts
0
。index.ts
管理历史记录
可以使用 MaxHistoryMessages
和 MaxConversationHistoryTokens
参数允许 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 |