Teams AI ライブラリの概要

Teams AI ライブラリは、AI コンポーネントを使用してインテリジェントなMicrosoft Teams アプリケーションを構築するプロセスを合理化します。 データにアクセスして操作するための API と、カスタム ユーザー インターフェイスを作成するためのさまざまなコントロールとコンポーネントが提供されます。

Teams AI ライブラリ、プロンプト管理、安全モデレーションをアプリに簡単に統合し、ユーザー エクスペリエンスを向上させることができます。 また、OpenAI API キーまたは 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) を呼び出す主要コンポーネントであり、モデルを強化およびカスタマイズするためのいくつかの機能が含まれています。 ユーザーの入力と使用可能なアクションに基づいてプランを生成して実行する責任があります。

サンプル コード リファレンス

    // 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
    );

ストレージとアプリケーションを定義する

アプリケーション オブジェクトは、ボットの会話とユーザーの状態を自動的に管理します。

  • ストレージ: ボットの会話とユーザーの状態を格納するストレージ プロバイダーを作成します。

  • アプリケーション: アプリケーション クラスには、アプリに必要なすべての情報とボット ロジックがあります。 このクラスでは、アプリのアクションまたはアクティビティ ハンドラーを登録できます。

サンプル コード リファレンス

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

TurnStateFactory では、アプリケーションのカスタム状態クラスを作成できます。 これを使用して、ボットに必要な追加情報またはロジックを格納できます。 ユーザー入力、ボットの出力、会話履歴など、ターン状態の既定のプロパティの一部をオーバーライドすることもできます。 TurnStateFactoryを使用するには、既定のターン状態を拡張するクラスを作成し、クラスのインスタンスを作成する関数をアプリケーション コンストラクターに渡す必要があります。

データ ソースを登録する

ベクター データ ソースを使用すると、プロンプトに RAG を簡単に追加できます。 名前付きデータ ソースを planner に登録し、データ ソースの名前を指定して、プロンプトの config.json ファイル内でプロンプトを拡張できます。 データ ソースを使用すると、AI は外部ソースからの関連情報をプロンプト (ベクター データベースやコグニティブ検索など) に挿入できます。 名前付きデータ ソースを planner に登録し、プロンプトの config.json ファイル内でプロンプトを拡張するデータ ソースの名前を指定できます。

サンプル コード リファレンス

// 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 によって生成されるベクターの一種です。 テキストには、単語、文、または文書全体を指定できます。 モデルは言語の構文とセマンティクスを理解しているため、埋め込みではテキストのセマンティックな意味をコンパクトな形式でキャプチャできます。 埋め込みは、テキスト分類やセンチメント分析などの自然言語処理タスクでよく使用されますが、検索にも使用されます。

Embeddings を生成するためのモデルは、基本的な 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 の両方に対してプロンプトを生成できます。

プロンプトを作成するためのガイドラインを次に示します。

  • 手順、例、またはその両方を指定します。
  • 品質データを提供します。 十分な例があることを確認し、例を校正します。 モデルは、基本的なスペルミスを確認して応答を提供するのに十分スマートですが、入力が意図的であると仮定する場合もあり、応答に影響を与える可能性があります。
  • プロンプト設定を確認します。 温度とtop_pの設定は、モデルが応答を生成する際の決定論的な方法を制御します。 0.8 などの値が大きいほど出力がランダムになり、0.2 などの値が小さいと、出力がフォーカスされ、決定的になります。

prompts という名前のフォルダーを作成し、そのフォルダーにプロンプトを定義します。 ユーザーがテキスト プロンプトを入力してボットと対話すると、ボットはテキスト入力候補で応答します。

  • skprompt.txt: プロンプト テキストが含まれており、テンプレート変数と関数がサポートされます。 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 モデルに使用する完了の種類。 プロンプトが表示されると、モデルは 1 つ以上の予測完了と、各位置での代替トークンの確率を返します。 サポートされているオプションは、 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 がそれ以上のトークンの生成を停止する最大 4 つのシーケンス。 返されたテキストに停止シーケンスは含まれません。
augmentation_type 拡張の種類。 サポートされる値は、 sequencemonologue 、および toolsです。

プロンプト管理

プロンプト管理は、使用可能なトークンの予算とデータ ソースまたは拡張を考慮して、言語モデルに送信されるプロンプトのサイズと内容を調整するのに役立ちます。

ボットに最大 4,000 個のトークンがあり、入力用のトークンが 2,800 個、出力用のトークンが 1,000 個ある場合、モデルはコンテキスト ウィンドウ全体を管理し、3,800 個を超えるトークンを処理しないようにすることができます。 モデルは、約 100 個のトークンのテキストから始まり、別の 1,200 トークンのデータ ソースに追加し、残りの予算である 1,500 トークンを確認します。 システムは、残りの 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;
});

アクション ハンドラーの登録

アクション ハンドラーは、ユーザーの意図で共有される目標をユーザーが達成するのに役立ちます。

アクション ハンドラーの重要な側面の 1 つは、最初にプロンプトにアクションを登録してから、ユーザーが目標を達成できるように支援する必要があるということです。

プロンプトに表示される各アクションのハンドラーを登録し、不明なアクションを処理するハンドラーも追加する必要があります。

次のライト ボットの例では、 LightsOnLightsOffPause アクションがあります。 アクションが呼び出されるたびに、 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";
        }
    }
}

sequencemonologue、または拡張toolsを使用する場合、モデルで無効な関数名、アクション名、または正しいパラメーターを幻覚することはできません。 新しいアクション ファイルを作成し、プロンプトで拡張をサポートするすべてのアクションを定義する必要があります。 アクションを実行するタイミングをモデルに伝えるアクションを定義する必要があります。 シーケンス拡張は、複数のステップまたは複雑なロジックを必要とするタスクに適しています。 モノローグ拡張は、自然言語の理解と生成、柔軟性と創造性を必要とするタスクに適しています。

次のライト ボットの例では、 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定義でallow_looping?falseに設定できます。
  • max_repair_attemptsindex.ts ファイル内の0に設定できます。

履歴の管理

MaxHistoryMessages引数とMaxConversationHistoryTokens引数を使用すると、AI ライブラリで履歴を自動的に管理できます。

フィードバック ループ

フィードバック ループを使用すると、時間の経過に伴うボットの相互作用を監視および改善し、より効果的で使いやすいアプリケーションを実現できます。 受信したフィードバックを使用して調整と改善を行い、ボットがユーザーのニーズと期待を一貫して満たすことができます。

フィードバック ループは、次の要素で構成されます。

修復ループ: モデルの応答が期待値を下回ると、修復ループがトリガーされます。 会話履歴がフォークされ、システムはメインの会話に影響を与えずにさまざまなソリューションを試すことができます。

検証: 検証では、修正された応答が検証されます。 検証に成功すると、システムは会話を強制解除し、修復された構造をメインの会話に再挿入します。

間違いから学ぶ: モデルが正しい動作の例を見ると、将来同様の間違いを避ける方法が学習されます。

複雑なコマンドを処理する: モデルが間違いから学習すると、より複雑なコマンドを処理し、目的のプランを返すことができます。

次の手順