Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Crie um assistente de RH inteligente usando LangChain.js e serviços do Azure. Esse agente ajuda os funcionários da empresa fictícia NorthWind a encontrar respostas para perguntas sobre recursos humanos pesquisando a documentação da empresa.
Você usará o Azure AI Search para localizar documentos relevantes e o Azure OpenAI para gerar respostas precisas. A estrutura LangChain.js lida com a complexidade da orquestração de agente, permitindo que você se concentre em seus requisitos de negócios específicos.
O que você aprenderá:
- Implantar recursos do Azure usando a CLI do Desenvolvedor do Azure
- Criar um agente de LangChain.js que se integra aos serviços do Azure
- Implementar RAG (geração aumentada de recuperação) para pesquisa de documentos
- Testar e depurar seu agente localmente e no Azure
Ao final deste tutorial, você tem uma API REST funcional que responde a perguntas de RH usando a documentação da sua empresa.
Visão geral da arquitetura
NorthWind depende de duas fontes de dados:
- Documentação de RH acessível a todos os funcionários
- Banco de dados de RH confidencial que contém dados confidenciais de funcionários.
Este tutorial se concentra na criação de um agente de LangChain.js que determina se a pergunta de um funcionário pode ser respondida usando os documentos públicos de RH. Nesse caso, o agente LangChain.js fornecerá a resposta diretamente.
Pré-requisitos
Para usar este exemplo no Codespace ou no contêiner de desenvolvimento local, incluindo a criação e a execução do agente de LangChain.js, você precisa do seguinte:
- Uma conta ativa do Azure. Crie uma conta gratuitamente se você não tiver uma.
Se você executar o código de exemplo localmente sem um contêiner de desenvolvimento, também precisará:
- Node.js LTS instalado em seu sistema.
- TypeScript para escrever e compilar código TypeScript.
- CLI do Azure Developer (azd) está instalada e configurada.
- LangChain.js biblioteca para a criação do agente.
- Opcional: LangSmith para monitorar o uso de IA. Você precisa do nome do projeto, da chave e do ponto de extremidade.
- Opcional: LangGraph Studio para depurar cadeias e agentes LangChain.js do LangGraph.
Recursos do Azure
Os seguintes recursos do Azure são necessários. Eles são criados para você neste artigo usando os modelos da CLI do Desenvolvedor do Azure e do Bicep usando a AVM (Módulos Verificados do Azure). Os recursos são criados com acesso sem senha e chave para fins de aprendizagem. Este tutorial usa sua conta de desenvolvedor local para autenticação sem senha:
- Identidade gerenciada para autenticação sem senha nos serviços do Azure.
- Registro de Contêiner do Azure para armazenar a imagem do Docker para o servidor de API Node.js Fastify.
- Aplicativo de Contêiner do Azure para hospedar o servidor de API do Node.js Fastify.
- Recurso de Pesquisa de IA do Azure para pesquisa de vetor.
-
Recurso do Azure OpenAI com os seguintes modelos:
- Um modelo de inserções como
text-embedding-3-small. - Um modelo de linguagem grande (LLM) como
'gpt-4.1-mini.
- Um modelo de inserções como
Arquitetura do agente
O framework LangChain.js fornece um fluxo de decisão para criar agentes inteligentes como um LangGraph. Neste tutorial, você criará um agente de LangChain.js que se integra ao Azure AI Search e ao Azure OpenAI para responder a perguntas relacionadas a RH. A arquitetura do agente foi projetada para:
- Determine se uma pergunta é relevante para a documentação geral de RH disponível para todos os funcionários.
- Recupere documentos relevantes do Azure AI Search com base na consulta de usuário.
- Use o Azure OpenAI para gerar uma resposta com base nos documentos recuperados e no modelo LLM.
Componentes principais:
Estrutura do grafo: o agente LangChain.js é representado como um grafo, em que:
- Os nós executam tarefas específicas, como a tomada de decisão ou a recuperação de dados.
- As bordas definem o fluxo entre nós, determinando a sequência de operações.
Integração do Azure AI Search:
- Usa um modelo de inserções para criar vetores.
- Insere documentos de RH (*.md, *.pdf) no repositório de vetores. Os documentos incluem:
- Informações da empresa
- Manual do funcionário
- Manual de benefícios
- Biblioteca de funções do funcionário
- Recupera documentos relevantes com base no prompt do usuário.
-
Integração do Azure OpenAI:
- Usa um modelo de linguagem grande para:
- Determina se uma pergunta pode ser respondida a partir de documentos de RH impessoais.
- Gera resposta com prompt usando o contexto de documentos e pergunta do usuário.
- Usa um modelo de linguagem grande para:
A tabela a seguir tem exemplos de perguntas do usuário que são e não são relevantes e que podem ser respondidas em documentos gerais de recursos humanos:
| Pergunta | Relevante | Explanation |
|---|---|---|
Does the NorthWind Health Plus plan cover eye exams? |
Yes | Os documentos de RH, como o manual do funcionário, devem fornecer uma resposta. |
How much of my perks + benefits have I spent? |
Não | Essa pergunta requer acesso a dados confidenciais de funcionários, que estão fora do escopo desse agente. |
Usando a estrutura de LangChain.js, você evita grande parte do código clichê agente normalmente necessário para agentes e integração de serviços do Azure, permitindo que você se concentre em suas necessidades comerciais.
Clonar o repositório de código de exemplo
Em um novo diretório, clone o repositório de código de exemplo e altere para o novo diretório:
git clone https://github.com/Azure-Samples/azure-typescript-langchainjs.git
cd azure-typescript-langchainjs
Este exemplo fornece o código necessário para criar recursos seguros do Azure, criar o agente LangChain.js com o Azure AI Search e o Azure OpenAI e usar o agente de um servidor de API Node.js Fastify.
Autentique-se na CLI do Azure e na Azure Developer CLI
Entre no Azure com a CLI do Desenvolvedor do Azure, crie os recursos do Azure e implante o código-fonte. Como o processo de implantação usa a CLI do Azure e a CLI do Desenvolvedor do Azure, entre na CLI do Azure e configure a CLI do Desenvolvedor do Azure para usar sua autenticação na CLI do Azure:
az login
azd config set auth.useAzCliAuth true
Criar recursos e implantar código com a CLI do Desenvolvedor do Azure
Inicie o processo de implantação executando o azd up comando:
azd up
Durante o azd up comando, responda às perguntas:
-
Novo nome do ambiente: insira um nome de ambiente exclusivo, como
langchain-agent. Esse nome de ambiente é usado como parte do grupo de recursos do Azure. - Selecione uma Assinatura do Azure: selecione a assinatura em que os recursos são criados.
-
Selecione uma região: como
eastus2.
A implantação leva aproximadamente de 10 a 15 minutos. A CLI do Desenvolvedor do Azure orquestra o processo usando fases e ganchos definidos no azure.yaml arquivo:
Fase de provisionamento (equivalente a azd provision):
- Cria recursos do Azure definidos em
infra/main.bicep:- Aplicativo de Contêiner do Azure
- OpenAI
- Busca por IA
- Registro de Contêiner
- Identidade gerenciada
-
Gancho pós-provisionamento: verifica se o índice
northwinddo Azure AI Search já existe- Se o índice não existir: executa
npm installenpm run load_datapara carregar documentos de RH usando o carregador de PDF do LangChain.js e o cliente de incorporação. - Se o índice existir: ignora o carregamento de dados para evitar duplicatas (você pode recarregar manualmente excluindo o índice ou executando
npm run load_data) Fase de implantação (equivalente aazd deploy):
- Se o índice não existir: executa
- Gancho de pré-implantação: cria a imagem do Docker para o servidor de API do Fastify e a envia por push para o Registro de Contêiner do Azure
- Implanta o servidor de API em contêineres nos Aplicativos de Contêiner do Azure
Quando a implantação é concluída, variáveis de ambiente e informações de recurso são salvas no .env arquivo na raiz do repositório. Você pode exibir os recursos no portal do Azure.
Os recursos são criados com acesso sem senha e chave para fins de aprendizagem. Este tutorial introdutório usa sua conta de desenvolvedor local para autenticação sem senha. Para aplicativos de produção, use apenas a autenticação sem senha com identidades gerenciadas. Saiba mais sobre a autenticação sem senha.
Usar o código de exemplo localmente
Agora que os recursos do Azure são criados, você pode executar o agente LangChain.js localmente.
Instalar dependências
Instale os pacotes de Node.js para este projeto.
npm installEsse comando instala as dependências definidas nos dois
package.jsonarquivos nopackages-v1diretório, incluindo:-
./packages-v1/server-api:- Fastify para o servidor Web
-
./packages-v1/langgraph-agent:- LangChain.js para a criação do agente
- Biblioteca
@azure/search-documentsde clientes do SDK do Azure para integração com o recurso do Azure AI Search. A documentação de referência está aqui.
-
Crie os dois pacotes: o servidor de API e o agente de IA.
npm run buildEsse comando cria um link entre os dois pacotes para que o servidor de API possa chamar o agente de IA.
Executar o servidor de API localmente
A CLI do Desenvolvedor do Azure criou os recursos necessários do Azure e configurou as variáveis de ambiente no arquivo raiz .env . Essa configuração incluiu um módulo pós-provisionamento para transferir os dados para o repositório de vetores. Agora, você pode executar o servidor de API do Fastify que hospeda o agente de LangChain.js. Inicie o servidor de API do Fastify.
npm run dev
O servidor inicia e escuta na porta 3000. Você pode testar o servidor navegando até [http://localhost:3000] no navegador da Web. Você deverá ver uma mensagem de boas-vindas indicando que o servidor está em execução.
Usar a API para fazer perguntas
Você pode usar uma ferramenta como o Cliente REST ou curl para enviar uma solicitação POST para o /ask ponto de extremidade com um corpo JSON que contém sua pergunta.
As consultas REST do cliente estão disponíveis no diretório packages-v1/server-api/http.
Exemplo usando curl:
curl -X POST http://localhost:3000/answer -H "Content-Type: application/json" -d "{\"question\": \"Does the NorthWind Health Plus plan cover eye exams?\"}"
Você deve receber uma resposta JSON com a resposta do agente LangChain.js.
{
"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."
}
Várias perguntas de exemplo estão disponíveis no packages-v1/server-api/http diretório. Abra os arquivos no Visual Studio Code com o cliente REST para testá-los rapidamente.
Entender o código do aplicativo
Esta seção explica como o agente LangChain.js se integra aos serviços do Azure. O aplicativo do repositório é organizado como um workspace npm com dois pacotes principais:
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
Principais decisões de arquitetura:
- Estrutura monorepo: npm workspaces permitem dependências compartilhadas e pacotes interligados
-
Separação de preocupações: a lógica do agente (
langgraph-agent) é independente do servidor de API (server-api) -
Autenticação centralizada: Arquivos em
./langgraph-agent/src/azuregerenciam autenticação tanto baseada em chave quanto sem senha, além de integração com serviços do Azure.
Autenticação nos Serviços do Azure
O aplicativo dá suporte a métodos de autenticação baseados em chave e sem senha, controlados pela variável de SET_PASSWORDLESS ambiente. A API DefaultAzureCredential da biblioteca de Identidade do Azure é usada para autenticação sem senha, permitindo que o aplicativo seja executado perfeitamente em ambientes locais de desenvolvimento e do Azure. Você pode ver essa autenticação no seguinte snippet de código:
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;
}
Ao usar bibliotecas de terceiros como LangChain.js ou a biblioteca OpenAI para acessar o Azure OpenAI, você precisa de uma função de provedor de token em vez de passar um objeto de credencial diretamente. A getBearerTokenProvider função da biblioteca de Identidade do Azure resolve esse problema criando um provedor de token que busca e atualiza automaticamente tokens de portador OAuth 2.0 para um escopo de recurso específico do Azure (por exemplo, "https://cognitiveservices.azure.com/.default"). Você configura o escopo uma vez durante a instalação e o provedor de token manipula todo o gerenciamento de tokens automaticamente. Essa abordagem funciona com qualquer credencial da biblioteca de identidade do Azure, incluindo identidade gerenciada e credenciais da CLI do Azure. Embora as bibliotecas do SDK do Azure aceitem DefaultAzureCredential diretamente, bibliotecas de terceiros como LangChain.js exigem esse padrão de provedor de token para preencher a lacuna de autenticação.
Integração do Azure AI Search
O recurso do Azure AI Search armazena inserções de documentos e permite a pesquisa semântica de conteúdo relevante. O aplicativo usa LangChain's AzureAISearchVectorStore para gerenciar o repositório de vetores sem que você precise definir o esquema de índice.
O repositório de vetores é criado com a configuração para operações de administração (gravação) e consulta (leitura) para que o carregamento e a consulta de documentos possam usar configurações diferentes. Isso é importante se você estiver usando chaves ou autenticação sem senha com identidades gerenciadas.
A implantação da CLI do Desenvolvedor do Azure inclui um gancho pós-implantação que carrega os documentos no repositório de vetores com LangChain.js carregador PDF e cliente de inserção. Esse gancho pós-implantação é a última etapa do azd up comando depois que o recurso do Azure AI Search é criado. O script de carregamento de documento usa lógica de envio em lote e repetição para lidar com os limites de taxa de serviço.
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
Use o arquivo raiz .env criado pela CLI do Desenvolvedor do Azure, você pode se autenticar no recurso do Azure AI Search e criar o cliente 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;
Quando você consulta, o repositório de vetores converte a consulta do usuário em uma inserção, pesquisa documentos com representações de vetor semelhantes e retorna as partes mais relevantes.
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);
}
Como o repositório de vetores é criado sobre LangChain.js, ele abstrai a complexidade de interagir diretamente com o repositório de vetores. Depois de aprender a interface do repositório de vetores LangChain.js, você poderá alternar facilmente para outras implementações do repositório de vetores no futuro.
Integração do Azure OpenAI
O aplicativo usa o Azure OpenAI para inserções e recursos de LLM (modelo de linguagem grande). A AzureOpenAIEmbeddings classe de LangChain.js é usada para gerar inserções para documentos e consultas. Depois de criar o cliente de inserções, LangChain.js o usará para criar as inserções.
Integração do Azure OpenAI para inserções
Use o arquivo raiz .env criado pela CLI do Desenvolvedor do Azure para autenticar-se no recurso Azure OpenAI e criar o cliente 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 });
}
Integração do Azure OpenAI para LLM
Use o arquivo raiz .env criado pela CLI do Desenvolvedor do Azure para autenticar-se no recurso Azure OpenAI e criar o cliente 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;
O aplicativo usa a AzureChatOpenAI classe de LangChain.js @langchain/openai para interagir com modelos do 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,
},
],
};
};
Fluxo de trabalho do agente do LangGraph
O agente usa o LangGraph para definir um fluxo de trabalho de decisão que determina se uma pergunta pode ser respondida usando documentos de RH.
Estrutura do grafo:
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";
O fluxo de trabalho consiste nas seguintes etapas:
- Iniciar: o usuário envia uma pergunta.
- requires_hr_documents node: LLM determina se a pergunta pode ser respondida a partir de documentos gerais de RH.
-
Roteamento condicional:
- Se sim, prossiga para o nó
get_answer. - Se não, retorna a mensagem de que a pergunta requer dados pessoais de RH.
- Se sim, prossiga para o nó
- get_answer nó: recupera documentos e gera resposta.
- Fim: retorna a resposta ao usuário.
Essa verificação de relevância é importante porque nem todas as perguntas de RH podem ser respondidas de documentos gerais. Perguntas pessoais como "Quanta PTO eu tenho?" exigem acesso a bancos de dados de funcionários que contêm dados individuais de funcionários. Ao verificar a relevância primeiro, o agente evita alucinar respostas para perguntas que precisam de informações pessoais às quais não tem acesso.
Decidir se a pergunta requer documentos de RH
O nó requires_hr_documents utiliza um Modelo de Linguagem de Grande Escala (LLM) para verificar se a pergunta do usuário pode ser respondida utilizando documentos gerais de RH. Ele usa um modelo de prompt que instrui o modelo a responder com YES ou NO com base na relevância da pergunta. Ele retorna a resposta em uma mensagem estruturada, que pode ser passada ao longo do fluxo de trabalho. O próximo nó usa essa resposta para rotear o fluxo de trabalho para o END ou o 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;
};
Obter os documentos de RH necessários
Depois que for determinado que a pergunta requer documentos de RH, o fluxo de trabalho usará getAnswer para recuperar os documentos relevantes do repositório de vetores, adicioná-los ao contexto do prompt e passar todo o prompt para a 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],
};
}
Se nenhum documento relevante for encontrado, o agente retornará uma mensagem indicando que não conseguiu encontrar uma resposta nos documentos de RH.
Resolução de problemas
Para quaisquer problemas com o procedimento, crie um problema no repositório de código de exemplo
Limpar os recursos
Você pode excluir o grupo de recursos, que contém o recurso do Azure AI Search e o recurso Azure OpenAI ou usar a CLI do Desenvolvedor do Azure para excluir imediatamente todos os recursos criados por este tutorial.
azd down --purge