使用 LangChain.js 和 Azure 服务构建智能 HR 助手。 此代理通过搜索公司文档帮助虚构的 NorthWind 公司的员工找到人力资源问题的答案。
你将使用 Azure AI 搜索 查找相关文档和 Azure OpenAI 来生成准确的答案。 LangChain.js 框架处理代理业务流程的复杂性,让你专注于特定的业务需求。
学习内容:
- 使用 Azure 开发人员 CLI 部署 Azure 资源
- 生成与 Azure 服务集成的 LangChain.js 代理
- 实现文档搜索的检索增强生成(RAG)
- 在本地和 Azure 中测试和调试代理
在本教程结束时,你将获得一个工作 REST API,该 API 使用公司的文档回答 HR 问题。
体系结构概述
NorthWind 依赖于两个数据源:
- 可供 所有 员工访问的 HR 文档
- 包含敏感员工数据的机密 HR 数据库。
本教程重点介绍如何构建一个 LangChain.js 代理,该代理确定员工的问题是否可以使用公共 HR 文档进行解答。 如果是这样,LangChain.js 代理会直接提供答案。
先决条件
若要在 Codespace 或本地开发容器(包括生成和运行 LangChain.js 代理)中使用此示例,需要满足以下条件:
- 有效的 Azure 帐户。 如果没有帐户,请免费创建帐户。
如果在不使用开发容器的情况下在本地运行示例代码,则还需要:
- Node.js LTS 安装在您的系统上。
- TypeScript 用于编写和编译 TypeScript 代码。
- 已安装并配置了 Azure 开发人员 CLI(azd)。
- 用于构建代理的 LangChain.js 库。
- 可选:用于监视 AI 使用情况的 LangSmith 。 需要项目名称、密钥和终结点。
- 可选:用于调试 LangGraph 链和 LangChain.js 代理的 LangGraph Studio 。
Azure 资源
需要以下 Azure 资源。 本文中使用 Azure 开发人员 CLI 和 Bicep 模板,通过 Azure 验证模块(AVM) 为您创建它们。 资源是使用无密码访问和密钥访问创建的,用于学习目的。 本教程使用本地开发人员帐户进行无密码身份验证:
- 用于向 Azure 服务进行无密码身份验证的托管标识。
- 用于存储 Node.js Fastify API 服务器的 Docker 映像的 Azure 容器注册表。
- 用于托管 Node.js Fastify API 服务器的 Azure 容器应用。
- 用于矢量搜索的 Azure AI 搜索资源 。
- 具有以下模型的 Azure OpenAI 资源:
- 像
text-embedding-3-small这样的嵌入模型。 - 类似大型语言模型(LLM)如
'gpt-4.1-mini。
- 像
代理体系结构
LangChain.js 框架提供了一个决策流,用于构建智能代理作为 LangGraph。 在本教程中,你将创建一个与 Azure AI 搜索和 Azure OpenAI 集成的 LangChain.js 代理来回答与 HR 相关的问题。 代理的体系结构旨在:
- 确定问题是否与 所有 员工可用的常规 HR 文档相关。
- 根据用户查询从 Azure AI 搜索中检索相关文档。
- 使用 Azure OpenAI 基于检索的文档和 LLM 模型生成答案。
关键组件:
图形结构:LangChain.js 代理表示为图形,其中:
- 节点 执行特定任务,例如决策或检索数据。
- 边 定义节点之间的流,确定操作的顺序。
Azure AI 搜索集成:
- 使用嵌入模型创建向量。
- 将 HR 文档 (*.md, *.pdf) 插入向量存储中。
文档包括:
- 公司信息
- 员工手册
- 权益手册
- 员工角色库
- 根据用户提示检索相关文档。
-
Azure OpenAI 集成:
- 使用大型语言模型可以:
- 确定问题是否可从非个人 HR 文档得到解答。
- 使用文档和用户问题的上下文生成提示答案。
- 使用大型语言模型可以:
下表包含用户问题的示例,这些问题与常规人力资源文档无关且不可回答:
| 问题 | 相关 | Explanation |
|---|---|---|
Does the NorthWind Health Plus plan cover eye exams? |
是的 | 人力资源文档(如员工手册)应提供答案。 |
How much of my perks + benefits have I spent? |
否 | 此问题需要访问此代理范围之外的机密员工数据。 |
通过使用 LangChain.js 框架,您可以避免通常在代理和 Azure 服务集成中需编写的大量样板代码,从而能够更专注于业务需求。
克隆示例代码存储库
在新目录中,克隆示例代码存储库并更改为新目录:
git clone https://github.com/Azure-Samples/azure-typescript-langchainjs.git
cd azure-typescript-langchainjs
此示例提供了创建安全 Azure 资源所需的代码,使用 Azure AI 搜索和 Azure OpenAI 生成 LangChain.js 代理,并使用来自 Node.js Fastify API 服务器的代理。
向 Azure CLI 和 Azure 开发人员 CLI 进行身份验证
使用 Azure 开发人员 CLI 登录到 Azure,创建 Azure 资源并部署源代码。 由于部署过程同时使用 Azure CLI 和 Azure 开发人员 CLI,请登录到 Azure CLI,然后将 Azure 开发人员 CLI 配置为使用 Azure CLI 中的身份验证:
az login
azd config set auth.useAzCliAuth true
使用 Azure 开发人员 CLI 创建资源和部署代码
通过运行以下命令 azd up 开始部署过程:
azd up
在 azd up 命令中,回答以下问题:
-
新环境名称:输入唯一的环境名称,例如
langchain-agent。 此环境名称用作 Azure 资源组的一部分。 - 选择 Azure 订阅:选择用于创建资源的订阅。
-
选择一个区域,例如
eastus2。
部署大约需要 10-15 分钟。 Azure 开发者 CLI 使用 azure.yaml 文件中定义的阶段和挂钩协调流程。
预配阶段 (等效于 azd provision):
- 创建在
infra/main.bicep中定义的 Azure 资源:- Azure 容器应用
- OpenAI
- AI 搜索
- 容器注册表
- 托管标识
-
配置后挂钩:检查 Azure AI 搜索索引
northwind是否已存在- 如果索引不存在:运行
npm install和npm run load_data,使用 LangChain.js PDF 加载器和嵌入客户端上传 HR 文档。 - 如果索引存在:跳过数据加载以避免重复项(可以通过删除索引或运行
npm run load_data来手动重新加载)部署阶段(等效于azd deploy):
- 如果索引不存在:运行
- 预部署挂钩:生成 Fastify API 服务器的 Docker 映像并将其推送到 Azure 容器注册表
- 将容器化 API 服务器部署到 Azure 容器应用
部署完成后,环境变量和资源信息将 .env 保存到存储库根目录中的文件。 可以在 Azure 门户中查看资源。
资源是使用无密码访问和密钥访问创建的,用于学习目的。 本介绍性教程使用本地开发人员帐户进行无密码身份验证。 对于生产应用程序,应仅使用托管标识进行无密码身份验证。 详细了解 无密码身份验证。
在本地使用示例代码
创建 Azure 资源后,即可在本地运行 LangChain.js 代理。
安装依赖项
安装此项目的 Node.js 包。
npm install此命令将在
package.json目录中安装由两个packages-v1文件定义的依赖项,包括:-
./packages-v1/server-api:- 使用 Fastify 作为网络服务器
-
./packages-v1/langgraph-agent:- 用于生成代理的 LangChain.js
- 用于与 Azure AI 搜索资源集成的 Azure SDK 客户端库
@azure/search-documents。 参考文档在此处。
-
生成两个包:API 服务器和 AI 代理。
npm run build此命令在两个包之间创建链接,以便 API 服务器可以调用 AI 代理。
在本地运行 API 服务器
Azure 开发人员 CLI 创建了所需的 Azure 资源,并在根 .env 文件中配置了环境变量。 此配置包括一个发布预配挂钩,用于将数据上传到向量存储中。 现在,可以运行托管 LangChain.js 代理的 Fastify API 服务器。 启动 Fastify API 服务器。
npm run dev
服务器在端口 3000 上启动并侦听。 可以通过在 Web 浏览器中导航到 [http://localhost:3000] 来测试服务器。 应会看到一条指示服务器正在运行的欢迎消息。
使用 API 提出问题
可以使用 REST 客户端 之类的工具,或使用 curl 包含问题的 JSON 正文向终结点发送 POST 请求 /ask 。
Rest 客户端查询在 packages-v1/server-api/http 目录中可用。
使用 curl 的示例:
curl -X POST http://localhost:3000/answer -H "Content-Type: application/json" -d "{\"question\": \"Does the NorthWind Health Plus plan cover eye exams?\"}"
您应收到 LangChain.js 代理发来的答案的 JSON 响应。
{
"answer": "Yes, the NorthWind Health Plus plan covers eye exams. According to the Employee Handbook, employees enrolled in the Health Plus plan are eligible for annual eye exams as part of their vision benefits."
}
目录中提供了 packages-v1/server-api/http 几个示例问题。 使用 REST 客户端打开 Visual Studio Code 中的文件以快速测试这些文件。
了解应用程序代码
本部分介绍 LangChain.js 代理如何与 Azure 服务集成。 存储库的应用程序组织为包含两个主要包的 npm 工作区:
Project Root
│
├── packages-v1/
│ │
│ ├── langgraph-agent/ # Core LangGraph agent implementation
│ │ ├── src/
│ │ │ ├── azure/ # Azure service integrations
│ │ │ │ ├── azure-credential.ts # Centralized auth with DefaultAzureCredential
│ │ │ │ ├── embeddings.ts # Azure OpenAI embeddings + PDF loading + rate limiting
│ │ │ │ ├── llm.ts # Azure OpenAI chat completion (key-based & passwordless)
│ │ │ │ └── vector_store.ts # Azure AI Search vector store + indexing + similarity search
│ │ │ │
│ │ │ ├── langchain/ # LangChain agent logic
│ │ │ │ ├── node_get_answer.ts # RAG: retrieves docs + generates answers
│ │ │ │ ├── node_requires_hr_documents.ts # Determines if HR docs needed
│ │ │ │ ├── nodes.ts # LangGraph node definitions + state management
│ │ │ │ └── prompt.ts # System prompts + conversation templates
│ │ │ │
│ │ │ └── scripts/ # Utility scripts
│ │ │ └── load_vector_store.ts # Uploads PDFs to Azure AI Search
│ │ │
│ │ └── data/ # Source documents (PDFs) for vector store
│ │
│ └── server-api/ # Fastify REST API server
│ └── src/
│ └── server.ts # HTTP server with /answer endpoint
│
├── infra/ # Infrastructure as Code
│ └── main.bicep # Azure resources: Container Apps, OpenAI, AI Search, ACR, managed identity
│
├── azure.yaml # Azure Developer CLI config + deployment hooks
├── Dockerfile # Multi-stage Docker build for containerized deployment
└── package.json # Workspace configuration + build scripts
关键体系结构决策:
- Monorepo 结构: npm 工作区 允许共享依赖项和链接包
-
关注点分离:代理逻辑(
langgraph-agent)独立于 API 服务器(server-api) -
集中式身份验证:处理基于密钥和无密码的身份验证和 Azure 服务集成的文件
./langgraph-agent/src/azure
向 Azure 服务进行身份验证
应用程序支持基于密钥和无密码的身份验证方法,由 SET_PASSWORDLESS 环境变量控制。
Azure 标识库中的 DefaultAzureCredential API 用于无密码身份验证,允许应用程序在本地开发和 Azure 环境中无缝运行。 可以在以下 代码片段中看到此身份验证:
import { DefaultAzureCredential } from "@azure/identity";
export const CREDENTIAL = new DefaultAzureCredential();
export const SCOPE_OPENAI = "https://cognitiveservices.azure.com/.default";
export async function azureADTokenProvider_OpenAI() {
const tokenResponse = await CREDENTIAL.getToken(SCOPE_OPENAI);
return tokenResponse.token;
}
使用第三方库(如 LangChain.js 或 OpenAI 库)访问 Azure OpenAI 时,需要 令牌提供程序函数 ,而不是直接传递凭据对象。
getBearerTokenProvider Azure 标识库中的函数通过创建一个令牌提供程序来解决此问题,该提供程序会自动提取和刷新特定 Azure 资源范围的 OAuth 2.0 持有者令牌(例如)。 "https://cognitiveservices.azure.com/.default" 在设置过程中配置作用域一次,令牌提供程序会自动处理所有令牌管理。 此方法适用于任何 Azure 标识库凭据,包括托管标识和 Azure CLI 凭据。 虽然 Azure SDK 库直接接受 DefaultAzureCredential ,但第三方库(如 LangChain.js)需要此令牌提供程序模式来弥合身份验证差距。
Azure AI 搜索集成
Azure AI 搜索资源存储文档嵌入内容,并启用相关内容的语义搜索。 应用程序使用 LangChain 来管理 AzureAISearchVectorStore 矢量存储,而无需定义索引架构。
矢量存储是同时为写入(管理员)和查询(读取)操作配置而创建的,这样文档加载和查询可以使用不同的配置。 无论对托管标识使用密钥还是无密码身份验证,这都很重要。
Azure 开发人员 CLI 部署包括一个部署后挂钩,该挂钩使用 LangChain.js PDF 加载器和嵌入客户端将文档上传到矢量存储。 此部署后钩子是在创建 Azure AI 搜索资源后,azd up 命令的最后一步。 文档加载脚本使用批处理和重试逻辑来处理服务速率限制。
postdeploy:
posix:
sh: bash
run: |
echo "Checking if vector store data needs to be loaded..."
# Check if already loaded
INDEX_CREATED=$(azd env get-values | grep INDEX_CREATED | cut -d'=' -f2 || echo "false")
if [ "$INDEX_CREATED" = "true" ]; then
echo "Index already created. Skipping data load."
echo "Current document count: $(azd env get-values | grep INDEX_DOCUMENT_COUNT | cut -d'=' -f2)"
else
echo "Loading vector store data..."
npm install
npm run build
npm run load_data
# Get document count from the index
SEARCH_SERVICE=$(azd env get-values | grep AZURE_AISEARCH_ENDPOINT | cut -d'/' -f3 | cut -d'.' -f1)
DOC_COUNT=$(az search index show --service-name $SEARCH_SERVICE --name northwind --query "documentCount" -o tsv 2>/dev/null || echo "0")
# Mark as loaded
azd env set INDEX_CREATED true
azd env set INDEX_DOCUMENT_COUNT $DOC_COUNT
echo "Data loading complete! Indexed $DOC_COUNT documents."
fi
使用根 .env 文件由 Azure 开发人员 CLI 创建,可以向 Azure AI 搜索资源进行身份验证并创建 AzureAISearchVectorStore 客户端:
const endpoint = process.env.AZURE_AISEARCH_ENDPOINT;
const indexName = process.env.AZURE_AISEARCH_INDEX_NAME;
const adminKey = process.env.AZURE_AISEARCH_ADMIN_KEY;
const queryKey = process.env.AZURE_AISEARCH_QUERY_KEY;
export const QUERY_DOC_COUNT = 3;
const MAX_INSERT_RETRIES = 3;
const shared_admin = {
endpoint,
indexName,
};
export const VECTOR_STORE_ADMIN_KEY: AzureAISearchConfig = {
...shared_admin,
key: adminKey,
};
export const VECTOR_STORE_ADMIN_PASSWORDLESS: AzureAISearchConfig = {
...shared_admin,
credentials: CREDENTIAL,
};
export const VECTOR_STORE_ADMIN_CONFIG: AzureAISearchConfig =
process.env.SET_PASSWORDLESS == "true"
? VECTOR_STORE_ADMIN_PASSWORDLESS
: VECTOR_STORE_ADMIN_KEY;
const shared_query = {
endpoint,
indexName,
search: {
type: AzureAISearchQueryType.Similarity,
},
};
// Key-based config
export const VECTOR_STORE_QUERY_KEY: AzureAISearchConfig = {
key: queryKey,
...shared_query,
};
export const VECTOR_STORE_QUERY_PASSWORDLESS: AzureAISearchConfig = {
credentials: CREDENTIAL,
...shared_query,
};
export const VECTOR_STORE_QUERY_CONFIG =
process.env.SET_PASSWORDLESS == "true"
? VECTOR_STORE_QUERY_PASSWORDLESS
: VECTOR_STORE_QUERY_KEY;
查询时,矢量存储会将用户的查询转换为嵌入,搜索具有类似矢量表示形式的文档,并返回最相关的区块。
export function getReadOnlyVectorStore(): AzureAISearchVectorStore {
const embeddings = getEmbeddingClient();
return new AzureAISearchVectorStore(embeddings, VECTOR_STORE_QUERY_CONFIG);
}
export async function getDocsFromVectorStore(
query: string,
): Promise<Document[]> {
const store = getReadOnlyVectorStore();
// @ts-ignore
//return store.similaritySearchWithScore(query, QUERY_DOC_COUNT);
return store.similaritySearch(query, QUERY_DOC_COUNT);
}
由于矢量存储基于 LangChain.js而构建,因此将直接与矢量存储交互的复杂性抽象化。 了解 LangChain.js 向量存储接口后,以后可以轻松切换到其他向量存储实现。
Azure OpenAI 集成
应用程序使用 Azure OpenAI 来嵌入和大型语言模型 (LLM) 功能。 LangChain.js 中的 AzureOpenAIEmbeddings 类用于为文档和查询生成嵌入内容。 创建嵌入客户端后,LangChain.js 使用它来创建嵌入内容。
用于嵌入的 Azure OpenAI 集成
使用 Azure 开发人员 CLI 创建的根 .env 文件向 Azure OpenAI 资源进行身份验证,并创建 AzureOpenAIEmbeddings 客户端:
const shared = {
azureOpenAIApiInstanceName: instance,
azureOpenAIApiEmbeddingsDeploymentName: model,
azureOpenAIApiVersion: apiVersion,
azureOpenAIBasePath,
dimensions: 1536, // for text-embedding-3-small
batchSize: EMBEDDING_BATCH_SIZE,
maxRetries: 7,
timeout: 60000,
};
export const EMBEDDINGS_KEY_CONFIG = {
azureOpenAIApiKey: key,
...shared,
};
export const EMBEDDINGS_CONFIG_PASSWORDLESS = {
azureADTokenProvider: azureADTokenProvider_OpenAI,
...shared,
};
export const EMBEDDINGS_CONFIG =
process.env.SET_PASSWORDLESS == "true"
? EMBEDDINGS_CONFIG_PASSWORDLESS
: EMBEDDINGS_KEY_CONFIG;
export function getEmbeddingClient(): AzureOpenAIEmbeddings {
return new AzureOpenAIEmbeddings({ ...EMBEDDINGS_CONFIG });
}
适用于 LLM 的 Azure OpenAI 集成
使用 Azure 开发人员 CLI 创建的根 .env 文件向 Azure OpenAI 资源进行身份验证,并创建 AzureChatOpenAI 客户端:
const shared = {
azureOpenAIApiInstanceName: instance,
azureOpenAIApiDeploymentName: model,
azureOpenAIApiVersion: apiVersion,
azureOpenAIBasePath,
maxTokens: maxTokens ? parseInt(maxTokens, 10) : 100,
maxRetries: 7,
timeout: 60000,
temperature: 0,
};
export const LLM_KEY_CONFIG = {
azureOpenAIApiKey: key,
...shared,
};
export const LLM_CONFIG_PASSWORDLESS = {
azureADTokenProvider: azureADTokenProvider_OpenAI,
...shared,
};
export const LLM_CONFIG =
process.env.SET_PASSWORDLESS == "true"
? LLM_CONFIG_PASSWORDLESS
: LLM_KEY_CONFIG;
应用程序使用 AzureChatOpenAI LangChain.js @langchain/openai 中的类与 Azure OpenAI 模型交互。
export const callChatCompletionModel = async (
state: typeof StateAnnotation.State,
_config: RunnableConfig,
): Promise<typeof StateAnnotation.Update> => {
const llm = new AzureChatOpenAI({
...LLM_CONFIG,
});
const completion = await llm.invoke(state.messages);
completion;
return {
messages: [
...state.messages,
{
role: "assistant",
content: completion.content,
},
],
};
};
LangGraph 代理工作流
代理使用 LangGraph 定义决策工作流,该工作流确定是否可以使用 HR 文档回答问题。
图形结构:
import { StateGraph } from "@langchain/langgraph";
import {
START,
ANSWER_NODE,
DECISION_NODE,
route as endRoute,
StateAnnotation,
} from "./langchain/nodes.js";
import { getAnswer } from "./langchain/node_get_answer.js";
import {
requiresHrResources,
routeRequiresHrResources,
} from "./langchain/node_requires_hr_documents.js";
const builder = new StateGraph(StateAnnotation)
.addNode(DECISION_NODE, requiresHrResources)
.addNode(ANSWER_NODE, getAnswer)
.addEdge(START, DECISION_NODE)
.addConditionalEdges(DECISION_NODE, routeRequiresHrResources)
.addConditionalEdges(ANSWER_NODE, endRoute);
export const hr_documents_answer_graph = builder.compile();
hr_documents_answer_graph.name = "Azure AI Search + Azure OpenAI";
工作流包括以下步骤:
- 开始:用户提交问题。
- requires_hr_documents节点:LLM 确定问题是否能从通用 HR 文档中得到回答。
-
条件路由:
- 如果是,则继续到
get_answer节点。 - 如果否,则返回问题需要个人数据的消息。
- 如果是,则继续到
- get_answer节点:检索文档并生成答案。
- 结束:向用户返回答案。
此相关性检查很重要,因为并非所有 HR 问题都可以从常规文档回答。 个人问题(如“我拥有多少 PTO?”)需要访问包含单个员工数据的员工数据库。 通过首先检查相关性,代理可避免幻觉回答需要其无权访问的个人信息的问题。
确定问题是否需要 HR 文档
该 requires_hr_documents 节点使用 LLM 来确定是否可以使用常规 HR 文档回答用户的问题。 它使用提示模板指示模型基于问题的相关性来选择响应 YES 或 NO。 它会在结构化消息中返回答案,该消息可以沿工作流传递。 下一个节点使用此响应将工作流路由到 END 或 ANSWER_NODE.
// @ts-nocheck
import { getLlmChatClient } from "../azure/llm.js";
import { StateAnnotation } from "../langchain/state.js";
import { RunnableConfig } from "@langchain/core/runnables";
import { BaseMessage } from "@langchain/core/messages";
import { ANSWER_NODE, END } from "./nodes.js";
const PDF_DOCS_REQUIRED = "Answer requires HR PDF docs.";
export async function requiresHrResources(
state: typeof StateAnnotation.State,
_config: RunnableConfig,
): Promise<typeof StateAnnotation.Update> {
const lastUserMessage: BaseMessage = [...state.messages].reverse()[0];
let pdfDocsRequired = false;
if (lastUserMessage && typeof lastUserMessage.content === "string") {
const question = `Does the following question require general company policy information that could be found in HR documents like employee handbooks, benefits overviews, or company-wide policies, then answer yes. Answer no if this requires personal employee-specific information that would require access to an individual's private data, employment records, or personalized benefits details: '${lastUserMessage.content}'. Answer with only "yes" or "no".`;
const llm = getLlmChatClient();
const response = await llm.invoke(question);
const answer = response.content.toLocaleLowerCase().trim();
console.log(`LLM question (is HR PDF documents required): ${question}`);
console.log(`LLM answer (is HR PDF documents required): ${answer}`);
pdfDocsRequired = answer === "yes";
}
// If HR documents (aka vector store) are required, append an assistant message to signal this.
if (!pdfDocsRequired) {
const updatedState = {
messages: [
...state.messages,
{
role: "assistant",
content:
"Not a question for our HR PDF resources. This requires data specific to the asker.",
},
],
};
return updatedState;
} else {
const updatedState = {
messages: [
...state.messages,
{
role: "assistant",
content: `${PDF_DOCS_REQUIRED} You asked: ${lastUserMessage.content}. Let me check.`,
},
],
};
return updatedState;
}
}
export const routeRequiresHrResources = (
state: typeof StateAnnotation.State,
): typeof END | typeof ANSWER_NODE => {
const lastMessage: BaseMessage = [...state.messages].reverse()[0];
if (lastMessage && !lastMessage.content.includes(PDF_DOCS_REQUIRED)) {
console.log("go to end");
return END;
}
console.log("go to llm");
return ANSWER_NODE;
};
获取所需的 HR 文档
确定问题需要 HR 文档后,工作流将使用 getAnswer 从向量存储中检索相关文档,将它们添加到提示的 上下文 中,并将整个提示传递给 LLM。
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { getLlmChatClient } from "../azure/llm.js";
import { StateAnnotation } from "./nodes.js";
import { AIMessage } from "@langchain/core/messages";
import { getReadOnlyVectorStore } from "../azure/vector_store.js";
const EMPTY_STATE = { messages: [] };
export async function getAnswer(
state: typeof StateAnnotation.State = EMPTY_STATE,
): Promise<typeof StateAnnotation.Update> {
const vectorStore = getReadOnlyVectorStore();
const llm = getLlmChatClient();
// Extract the last user message's content from the state as input
const lastMessage = state.messages[state.messages.length - 1];
const userInput =
lastMessage && typeof lastMessage.content === "string"
? lastMessage.content
: "";
const docs = await vectorStore.similaritySearch(userInput, 3);
if (docs.length === 0) {
const noDocMessage = new AIMessage(
"I'm sorry, I couldn't find any relevant information to answer your question.",
);
return {
messages: [...state.messages, noDocMessage],
};
}
const formattedDocs = docs.map((doc) => doc.pageContent).join("\n\n");
const prompt = ChatPromptTemplate.fromTemplate(`
Use the following context to answer the question:
{context}
Question: {question}
`);
const ragChain = prompt.pipe(llm);
const result = await ragChain.invoke({
context: formattedDocs,
question: userInput,
});
const assistantMessage = new AIMessage(result.text);
return {
messages: [...state.messages, assistantMessage],
};
}
如果未找到相关文档,代理将返回一条消息,指示它在 HR 文档中找不到答案。
故障排除
对于该过程的任何问题,请在示例代码存储库上创建问题
清理资源
可以删除包含 Azure AI 搜索资源和 Azure OpenAI 资源的资源组,或使用 Azure 开发人员 CLI 立即删除本教程创建的所有资源。
azd down --purge