Создание бота RAG в Teams
Расширенные чат-боты Q&A — это мощные приложения, созданные с помощью больших языковых моделей (LLM). Чат-боты отвечают на вопросы, извлекая информацию из определенных источников с помощью метода с именем Получение дополненного поколения (RAG). Архитектура RAG состоит из двух main потоков:
Прием данных. Конвейер для приема данных из источника и их индексирования. Обычно это происходит в автономном режиме.
Получение и создание. Цепочка RAG, которая принимает пользовательский запрос во время выполнения и извлекает соответствующие данные из индекса, а затем передает их модели.
Microsoft Teams позволяет создать бота для общения с RAG, чтобы создать улучшенный интерфейс для повышения производительности. Набор средств Teams предоставляет серию готовых к использованию шаблонов приложений в категории Чат с данными , которая сочетает в себе функции поиска Azure AI, Microsoft 365 SharePoint и пользовательского API в качестве разных источников данных и LLM для создания взаимодействия с поиском в Teams.
Предварительные условия
Установка | Для использования... |
---|---|
Visual Studio Code | Среды сборки JavaScript, TypeScript или Python. Используйте последнюю версию. |
Набор средств Teams | Расширение Microsoft Visual Studio Code, которое создает шаблон проекта для вашего приложения. Используйте последнюю версию. |
Node.js | Серверной среды выполнения JavaScript. Дополнительные сведения см . вNode.js таблице совместимости версий для типа проекта. |
Microsoft Teams | Microsoft Teams позволяет сотрудничать со всеми пользователями, с которыми вы работаете, с помощью приложений для чата, собраний и звонков в одном месте. |
Azure OpenAI | Сначала создайте ключ API OpenAI, чтобы использовать генеративный предварительно обученный преобразователь OpenAI (GPT). Если вы хотите разместить приложение или получить доступ к ресурсам в Azure, необходимо создать службу Azure OpenAI. |
Создание нового базового проекта чат-бота ИИ
Откройте Visual Studio Code.
Щелкните значок Набора средств Teams на панели действий Visual Studio Code.
Выберите Создать новое приложение.
Выберите Агент пользовательского обработчика.
Выберите Чат с данными.
Выберите Настроить.
Выберите JavaScript.
Выберите Azure OpenAI или OpenAI.
Введите учетные данные Azure OpenAI или OpenAI в зависимости от выбранной службы. Выберите ВВОД.
Выберите Папка по умолчанию.
Чтобы изменить расположение по умолчанию, выполните следующие действия.
- Нажмите кнопку Обзор.
- Выберите расположение рабочей области проекта.
- Выберите Выбрать папку.
Введите имя приложения, а затем нажмите клавишу ВВОД .
Вы успешно создали рабочую область проекта "Чат с данными ".
В разделе EXPLORER перейдите к файлу env.env.testtool.user>.
Обновите следующие значения:
SECRET_AZURE_OPENAI_API_KEY=<your-key>
AZURE_OPENAI_ENDPOINT=<your-endpoint>
AZURE_OPENAI_DEPLOYMENT_NAME=<your-deployment>
Чтобы выполнить отладку приложения, нажмите клавишу F5 или в левой области выберите Запуск и отладка (CTRL+SHIFT+D), а затем в раскрывающемся списке выберите Отладка в средстве тестирования (предварительная версия).
Средство тестирования открывает бот на веб-странице.
Обзор исходного кода приложения бота
Folder | Содержание |
---|---|
.vscode |
Visual Studio Code файлы для отладки. |
appPackage |
Шаблоны для манифеста приложения Teams. |
env |
Файлы среды. |
infra |
Шаблоны для подготовки ресурсов Azure. |
src |
Исходный код приложения. |
src/index.js |
Настройка сервера приложений бота. |
src/adapter.js |
Настраивает адаптер бота. |
src/config.js |
Определяет переменные среды. |
src/prompts/chat/skprompt.txt |
Определяет запрос. |
src/prompts/chat/config.json |
Настраивает запрос. |
src/app/app.js |
Обрабатывает бизнес-логику для бота RAG. |
src/app/myDataSource.js |
Определяет источник данных. |
src/data/*.md |
Источники необработанных текстовых данных. |
teamsapp.yml |
Это файл проекта набора средств Teams main. Файл проекта определяет свойства и определения этапов конфигурации. |
teamsapp.local.yml |
Это переопределяет teamsapp.yml действия, которые обеспечивают локальное выполнение и отладку. |
teamsapp.testtool.yml |
Это переопределяет teamsapp.yml действия, которые обеспечивают локальное выполнение и отладку в средстве тестирования приложений Teams. |
Сценарии RAG для ИИ Teams
В контексте ИИ векторные базы данных широко используются в качестве хранилищ RAG, которые хранят данные внедрения и обеспечивают поиск по сходству векторов. Библиотека ИИ Teams предоставляет служебные программы, помогающие создавать внедрения для заданных входных данных.
Совет
Библиотека ИИ Teams не предоставляет реализацию векторной базы данных, поэтому необходимо добавить собственную логику для обработки созданных внедрений.
// create OpenAIEmbeddings instance
const model = new OpenAIEmbeddings({ ... endpoint, apikey, model, ... });
// create embeddings for the given inputs
const embeddings = await model.createEmbeddings(model, inputs);
// your own logic to process embeddings
На следующей схеме показано, как библиотека ИИ Teams предоставляет функциональные возможности для упрощения каждого этапа процесса получения и создания.
Обработка входных данных. Самый простой способ — передать входные данные пользователя в извлечение без каких-либо изменений. Однако если вы хотите настроить входные данные перед извлечением, можно добавить обработчик действий к определенным входящим действиям.
Извлечение dataSource. Библиотека ИИ Teams предоставляет
DataSource
интерфейс, который позволяет добавлять собственную логику извлечения. Необходимо создать собственныйDataSource
экземпляр, и библиотека ИИ Teams вызывает его по запросу.class MyDataSource implements DataSource { /** * Name of the data source. */ public readonly name = "my-datasource"; /** * 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. * @returns The text to inject into the prompt as a `RenderedPromptSection` object. */ renderData( context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number ): Promise<RenderedPromptSection<string>> { ... } }
Вызов ИИ с помощью командной строки. В системе командной строки ИИ Teams можно легко внедрить
DataSource
, настроив раздел конфигурацииaugmentation.data_sources
. При этом запрос соединяется с оркестраторомDataSource
библиотеки и , чтобы вставитьDataSource
текст в окончательный запрос. Дополнительные сведения см. в разделе authorprompt. Например, в файле запроса:config.json
{ "schema": 1.1, ... "augmentation": { "data_sources": { "my-datasource": 1200 } } }
Ответ сборки. По умолчанию библиотека ИИ Teams отвечает на созданный ИИ ответ в виде текстового сообщения пользователю. Если вы хотите настроить ответ, можно переопределить действия SAY по умолчанию или явно вызвать модель ИИ для создания ответов, например с помощью адаптивных карточек.
Ниже приведен минимальный набор реализаций для добавления RAG в приложение. Как правило, он реализует DataSource
внедрение в knowledge
запрос, чтобы ИИ смог создать ответ на knowledge
основе .
Создайте
myDataSource.ts
файл для реализацииDataSource
интерфейса:export class MyDataSource implements DataSource { public readonly name = "my-datasource"; public async renderData( context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number ): Promise<RenderedPromptSection<string>> { const input = memory.getValue('temp.input') as string; let knowledge = "There's no knowledge found."; // hard-code knowledge if (input?.includes("shuttle bus")) { knowledge = "Company's shuttle bus may be 15 minutes late on rainy days."; } else if (input?.includes("cafe")) { knowledge = "The Cafe's available time is 9:00 to 17:00 on working days and 10:00 to 16:00 on weekends and holidays." } return { output: knowledge, length: knowledge.length, tooLong: false } } }
DataSource
Зарегистрируйте вapp.ts
файле :// Register your data source to prompt manager planner.prompts.addDataSource(new MyDataSource());
prompts/qa/skprompt.txt
Создайте файл и добавьте следующий текст:The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly to answer user's question. Base your answer off the text below:
prompts/qa/config.json
Создайте файл и добавьте следующий код для подключения к источнику данных:{ "schema": 1.1, "description": "Chat with QA Assistant", "type": "completion", "completion": { "model": "gpt-35-turbo", "completion_type": "chat", "include_history": true, "include_input": true, "max_input_tokens": 2800, "max_tokens": 1000, "temperature": 0.9, "top_p": 0.0, "presence_penalty": 0.6, "frequency_penalty": 0.0, "stop_sequences": [] }, "augmentation": { "data_sources": { "my-datasource": 1200 } } }
Выбор источников данных
В сценариях Чат с данными или RAG набор средств Teams предоставляет следующие типы источников данных:
Настройка. Позволяет полностью контролировать прием данных, чтобы создать собственный векторный индекс и использовать его в качестве источника данных. Дополнительные сведения см. в статье Создание собственного приема данных.
Вы также можете использовать расширение векторной базы данных Azure Cosmos DB или расширение вектора сервера Azure PostgreSQL в качестве векторных баз данных или API Bing для веб-поиска, чтобы получить последнее веб-содержимое для реализации любого экземпляра источника данных для подключения к собственному источнику данных.
Поиск Azure AI. Предоставляет пример для добавления документов в Службу поиска Azure AI, а затем используйте индекс поиска в качестве источника данных.
Пользовательский API. Позволяет чат-боту вызывать API, определенный в описании OpenAPI, для получения данных домена из службы API.
Microsoft Graph и SharePoint. Предоставляет пример использования содержимого Microsoft 365 из API поиска Microsoft Graph в качестве источника данных.
Создание собственного приема данных
Чтобы создать прием данных, выполните следующие действия.
Загрузка исходных документов. Убедитесь, что в документе есть осмысленный текст, так как модель внедрения принимает в качестве входных данных только текст.
Разделение на блоки. Убедитесь, что документ разделен, чтобы избежать сбоев вызовов API, так как модель внедрения имеет ограничение на входной маркер.
Вызов модели внедрения. Вызов API модели внедрения для создания внедрения для заданных входных данных.
Хранить внедрения. Храните созданные внедрения в векторной базе данных. Кроме того, включите полезные метаданные и необработанное содержимое для дальнейшего использования.
Пример кода
loader.ts
: обычный текст в качестве исходных входных данных.import * as fs from "node:fs"; export function loadTextFile(path: string): string { return fs.readFileSync(path, "utf-8"); }
splitter.ts
: разбиение текста на блоки с перекрытием.// split words by delimiters. const delimiters = [" ", "\t", "\r", "\n"]; export function split(content: string, length: number, overlap: number): Array<string> { const results = new Array<string>(); let cursor = 0, curChunk = 0; results.push(""); while(cursor < content.length) { const curChar = content[cursor]; if (delimiters.includes(curChar)) { // check chunk length while (curChunk < results.length && results[curChunk].length >= length) { curChunk ++; } for (let i = curChunk; i < results.length; i++) { results[i] += curChar; } if (results[results.length - 1].length >= length - overlap) { results.push(""); } } else { // append for (let i = curChunk; i < results.length; i++) { results[i] += curChar; } } cursor ++; } while (curChunk < results.length - 1) { results.pop(); } return results; }
embeddings.ts
: используйте библиотекуOpenAIEmbeddings
ИИ Teams для создания внедрений.import { OpenAIEmbeddings } from "@microsoft/teams-ai"; const embeddingClient = new OpenAIEmbeddings({ azureApiKey: "<your-aoai-key>", azureEndpoint: "<your-aoai-endpoint>", azureDeployment: "<your-embedding-deployment, e.g., text-embedding-ada-002>" }); export async function createEmbeddings(content: string): Promise<number[]> { const response = await embeddingClient.createEmbeddings(content); return response.output[0]; }
searchIndex.ts
: создание индекса поиска Azure AI.import { SearchIndexClient, AzureKeyCredential, SearchIndex } from "@azure/search-documents"; const endpoint = "<your-search-endpoint>"; const apiKey = "<your-search-key>"; const indexName = "<your-index-name>"; const indexDef: SearchIndex = { name: indexName, fields: [ { type: "Edm.String", name: "id", key: true, }, { type: "Edm.String", name: "content", searchable: true, }, { type: "Edm.String", name: "filepath", searchable: true, filterable: true, }, { type: "Collection(Edm.Single)", name: "contentVector", searchable: true, vectorSearchDimensions: 1536, vectorSearchProfileName: "default" } ], vectorSearch: { algorithms: [{ name: "default", kind: "hnsw" }], profiles: [{ name: "default", algorithmConfigurationName: "default" }] }, semanticSearch: { defaultConfigurationName: "default", configurations: [{ name: "default", prioritizedFields: { contentFields: [{ name: "content" }] } }] } }; export async function createNewIndex(): Promise<void> { const client = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey)); await client.createIndex(indexDef); }
searchIndexer.ts
: отправка созданных внедрений и других полей в индекс поиска Azure AI.import { AzureKeyCredential, SearchClient } from "@azure/search-documents"; export interface Doc { id: string, content: string, filepath: string, contentVector: number[] } const endpoint = "<your-search-endpoint>"; const apiKey = "<your-search-key>"; const indexName = "<your-index-name>"; const searchClient: SearchClient<Doc> = new SearchClient<Doc>(endpoint, indexName, new AzureKeyCredential(apiKey)); export async function indexDoc(doc: Doc): Promise<boolean> { const response = await searchClient.mergeOrUploadDocuments([doc]); return response.results.every((result) => result.succeeded); }
index.ts
: оркестрация указанных выше компонентов.import { createEmbeddings } from "./embeddings"; import { loadTextFile } from "./loader"; import { createNewIndex } from "./searchIndex"; import { indexDoc } from "./searchIndexer"; import { split } from "./splitter"; async function main() { // Only need to call once await createNewIndex(); // local files as source input const files = [`${__dirname}/data/A.md`, `${__dirname}/data/A.md`]; for (const file of files) { // load file const fullContent = loadTextFile(file); // split into chunks const contents = split(fullContent, 1000, 100); let partIndex = 0; for (const content of contents) { partIndex ++; // create embeddings const embeddings = await createEmbeddings(content); // upload to index await indexDoc({ id: `${file.replace(/[^a-z0-9]/ig, "")}___${partIndex}`, content: content, filepath: file, contentVector: embeddings, }); } } } main().then().finally();
Поиск ИИ Azure в качестве источника данных
В этом разделе вы узнаете, как:
- Добавьте документ в поиск Azure AI с помощью службы Azure OpenAI.
- Используйте индекс поиска Azure AI в качестве источника данных в приложении RAG.
Добавление документа в поиск Azure AI
Примечание.
Этот подход создает комплексный API чата, называемый моделью ИИ. Вы также можете использовать созданный ранее индекс в качестве источника данных, а также использовать библиотеку ИИ Teams для настройки получения и запроса.
Вы можете принимать документы знаний в Службу поиска Azure AI и создавать векторный индекс с помощью Azure OpenAI для своих данных. После приема индекс можно использовать в качестве источника данных.
Подготовьте данные в Хранилище BLOB-объектов Azure.
В Azure OpenAI Studio выберите Добавить источник данных.
Обновите обязательные поля.
Нажмите кнопку Далее.
Откроется страница Управление данными .
Обновите обязательные поля.
Нажмите кнопку Далее.
Обновите обязательные поля. Нажмите кнопку Далее.
Выберите Сохранить и закрыть.
Использование источника данных индекса поиска ИИ Azure
После приема данных в поиск Azure AI вы можете реализовать собственные DataSource
средства для получения данных из индекса поиска.
const { AzureKeyCredential, SearchClient } = require("@azure/search-documents");
const { DataSource, Memory, OpenAIEmbeddings, Tokenizer } = require("@microsoft/teams-ai");
const { TurnContext } = require("botbuilder");
// Define the interface for document
class Doc {
constructor(id, content, filepath) {
this.id = id;
this.content = content; // searchable
this.filepath = filepath;
}
}
// Azure OpenAI configuration
const aoaiEndpoint = "<your-aoai-endpoint>";
const aoaiApiKey = "<your-aoai-key>";
const aoaiDeployment = "<your-embedding-deployment, e.g., text-embedding-ada-002>";
// Azure AI Search configuration
const searchEndpoint = "<your-search-endpoint>";
const searchApiKey = "<your-search-apikey>";
const searchIndexName = "<your-index-name>";
// Define MyDataSource class implementing DataSource interface
class MyDataSource extends DataSource {
constructor() {
super();
this.name = "my-datasource";
this.embeddingClient = new OpenAIEmbeddings({
azureEndpoint: aoaiEndpoint,
azureApiKey: aoaiApiKey,
azureDeployment: aoaiDeployment
});
this.searchClient = new SearchClient(searchEndpoint, searchIndexName, new AzureKeyCredential(searchApiKey));
}
async renderData(context, memory, tokenizer, maxTokens) {
// use user input as query
const input = memory.getValue("temp.input");
// generate embeddings
const embeddings = (await this.embeddingClient.createEmbeddings(input)).output[0];
// query Azure AI Search
const response = await this.searchClient.search(input, {
select: [ "id", "content", "filepath" ],
searchFields: ["rawContent"],
vectorSearchOptions: {
queries: [{
kind: "vector",
fields: [ "contentVector" ],
vector: embeddings,
kNearestNeighborsCount: 3
}]
},
queryType: "semantic",
top: 3,
semanticSearchOptions: {
// your semantic configuration name
configurationName: "default",
}
});
// Add documents until you run out of tokens
let length = 0, output = '';
for await (const result of response.results) {
// Start a new doc
let doc = `${result.document.content}\n\n`;
let docLength = tokenizer.encode(doc).length;
const remainingTokens = maxTokens - (length + docLength);
if (remainingTokens <= 0) {
break;
}
// Append doc to output
output += doc;
length += docLength;
}
return { output, length, tooLong: length > maxTokens };
}
}
Добавление дополнительных API для пользовательского API в качестве источника данных
Выполните следующие действия, чтобы расширить настраиваемый агент обработчика из шаблона Пользовательского API дополнительными API.
Обновите
./appPackage/apiSpecificationFile/openapi.*
.Скопируйте соответствующую часть API, которую вы хотите добавить из спецификации, и добавьте в
./appPackage/apiSpecificationFile/openapi.*
.Обновите
./src/prompts/chat/actions.json
.Обновите необходимые сведения и свойства для пути, запроса и текста ДЛЯ API в следующем объекте:
{ "name": "${{YOUR-API-NAME}}", "description": "${{YOUR-API-DESCRIPTION}}", "parameters": { "type": "object", "properties": { "query": { "type": "object", "properties": { "${{YOUR-PROPERTY-NAME}}": { "type": "${{YOUR-PROPERTY-TYPE}}", "description": "${{YOUR-PROPERTY-DESCRIPTION}}", } // You can add more query properties here } }, "path": { // Same as query properties }, "body": { // Same as query properties } } } }
Обновите
./src/adaptiveCards
.Создайте новый файл с именем
${{YOUR-API-NAME}}.json
и заполните адаптивную карточку для ответа API api api.Обновите
./src/app/app.js
файл.Добавьте следующий код перед
module.exports = app;
:app.ai.action(${{YOUR-API-NAME}}, async (context: TurnContext, state: ApplicationTurnState, parameter: any) => { const client = await api.getClient(); const path = client.paths[${{YOUR-API-PATH}}]; if (path && path.${{YOUR-API-METHOD}}) { const result = await path.${{YOUR-API-METHOD}}(parameter.path, parameter.body, { params: parameter.query, }); const card = generateAdaptiveCard("../adaptiveCards/${{YOUR-API-NAME}}.json", result); await context.sendActivity({ attachments: [card] }); } else { await context.sendActivity("no result"); } return "result"; });
Microsoft 365 в качестве источника данных
Узнайте, как использовать API поиска Microsoft Graph для запроса содержимого Microsoft 365 в качестве источника данных для приложения RAG. Дополнительные сведения об API поиска Microsoft Graph см. в статье Использование API поиска (Майкрософт) для поиска в OneDrive и SharePoint.
Предварительные требования. Необходимо создать клиент API Graph и предоставить ему Files.Read.All
разрешение область на доступ к файлам, папкам, страницам и новостям SharePoint и OneDrive.
Прием данных
Доступен API поиска Microsoft Graph, который может искать содержимое SharePoint. Поэтому необходимо только убедиться, что документ отправлен в SharePoint или OneDrive без необходимости приема дополнительных данных.
Примечание.
Сервер SharePoint индексирует файл, только если его расширение указано на странице управления типами файлов. Полный список поддерживаемых расширений файлов см. в статье Расширения индексированных файлов по умолчанию и типы файлов, проанализированные на сервере SharePoint и SharePoint в Microsoft 365.
Реализация источника данных
Ниже приведен пример поиска текстовых файлов в SharePoint и OneDrive.
import {
DataSource,
Memory,
RenderedPromptSection,
Tokenizer,
} from "@microsoft/teams-ai";
import { TurnContext } from "botbuilder";
import { Client, ResponseType } from "@microsoft/microsoft-graph-client";
export class GraphApiSearchDataSource implements DataSource {
public readonly name = "my-datasource";
public readonly description =
"Searches the graph for documents related to the input";
public client: Client;
constructor(client: Client) {
this.client = client;
}
public async renderData(
context: TurnContext,
memory: Memory,
tokenizer: Tokenizer,
maxTokens: number
): Promise<RenderedPromptSection<string>> {
const input = memory.getValue("temp.input") as string;
const contentResults = [];
const response = await this.client.api("/search/query").post({
requests: [
{
entityTypes: ["driveItem"],
query: {
// Search for markdown files in the user's OneDrive and SharePoint
// The supported file types are listed here:
// https://learn.microsoft.com/sharepoint/technical-reference/default-crawled-file-name-extensions-and-parsed-file-types
queryString: `${input} filetype:txt`,
},
// This parameter is required only when searching with application permissions
// https://learn.microsoft.com/graph/search-concept-searchall
// region: "US",
},
],
});
for (const value of response?.value ?? []) {
for (const hitsContainer of value?.hitsContainers ?? []) {
contentResults.push(...(hitsContainer?.hits ?? []));
}
}
// Add documents until you run out of tokens
let length = 0,
output = "";
for (const result of contentResults) {
const rawContent = await this.downloadSharepointFile(
result.resource.webUrl
);
if (!rawContent) {
continue;
}
let doc = `${rawContent}\n\n`;
let docLength = tokenizer.encode(doc).length;
const remainingTokens = maxTokens - (length + docLength);
if (remainingTokens <= 0) {
break;
}
// Append do to output
output += doc;
length += docLength;
}
return { output, length, tooLong: length > maxTokens };
}
// Download the file from SharePoint
// https://docs.microsoft.com/en-us/graph/api/driveitem-get-content
private async downloadSharepointFile(
contentUrl: string
): Promise<string | undefined> {
const encodedUrl = this.encodeSharepointContentUrl(contentUrl);
const fileContentResponse = await this.client
.api(`/shares/${encodedUrl}/driveItem/content`)
.responseType(ResponseType.TEXT)
.get();
return fileContentResponse;
}
private encodeSharepointContentUrl(webUrl: string): string {
const byteData = Buffer.from(webUrl, "utf-8");
const base64String = byteData.toString("base64");
return (
"u!" + base64String.replace("=", "").replace("/", "_").replace("+", "_")
);
}
}
См. также
Platform Docs