이 자습서에서는 LangChain.js 사용하여 NorthWind 회사 직원이 인사 관련 질문을 할 수 있는 LangChain.js 에이전트를 빌드합니다. 프레임워크를 사용하면 일반적으로 LangChain.js 에이전트 및 Azure 서비스 통합에 필요한 상용구 코드를 방지하여 비즈니스 요구 사항에 집중할 수 있습니다.
이 자습서에서는 다음을 수행합니다.
- LangChain.js 에이전트 설정
- azure 리소스를 LangChain.js 에이전트에 통합
- 선택적으로 LangSmith Studio에서 LangChain.js 에이전트 테스트
NorthWind는 모든 직원이 액세스할 수 있는 공용 HR 설명서와 중요한 직원 데이터를 포함하는 기밀 HR 데이터베이스라는 두 가지 데이터 원본을 사용합니다. 이 자습서에서는 공용 HR 문서를 사용하여 직원의 질문에 대답할 수 있는지 여부를 결정하는 LangChain.js 에이전트를 빌드하는 데 중점을 둡니다. 이 경우 LangChain.js 에이전트가 직접 답변을 제공합니다.
경고
이 문서에서는 키를 사용하여 리소스에 액세스합니다. 프로덕션 환경에서 권장되는 모범 사례는 Azure RBAC 및 관리 ID를 사용하는 것입니다. 이 방법을 사용하면 키를 관리하거나 회전할 필요가 없으므로 보안이 강화되고 액세스 제어가 간소화됩니다.
필수 조건
- 활성 Azure 계정입니다. 계정이 없는 경우 무료로 계정을 만듭니다.
- 시스템에 설치된Node.js LTS
- TypeScript 코드를 작성하고 컴파일하기 위한 TypeScript입니다.
- 에이전트를 빌드하기 위한 LangChain.js 라이브러리입니다.
- 선택 사항: AI 사용량을 모니터링하기 위한 LangSmith 입니다. 프로젝트 이름, 키 및 엔드포인트가 필요합니다.
- 선택 사항: LangGraph 체인 및 LangChain.js 에이전트를 디버깅하기 위한 LangGraph Studio 입니다.
- Azure AI Search 리소스: 리소스 엔드포인트, 관리 키(문서 삽입용), 쿼리 키(문서 읽기용) 및 인덱스 이름이 있는지 확인합니다.
-
Azure OpenAI 리소스: 해당 API 버전을 사용하는 리소스 인스턴스 이름, 키 및 두 모델이 필요합니다.
- embeddings 모델(예:
text-embedding-ada-002
.) - 와 같은
gpt-4o
큰 언어 모델입니다.
- embeddings 모델(예:
에이전트 아키텍처
LangChain.js 프레임워크는 지능형 에이전트를 LangGraph로 빌드하기 위한 의사 결정 흐름을 제공합니다. 이 자습서에서는 HR 관련 질문에 답변하기 위해 Azure AI Search 및 Azure OpenAI와 통합되는 LangChain.js 에이전트를 만듭니다. 에이전트의 아키텍처는 다음을 수행하도록 설계되었습니다.
- 질문이 HR 설명서와 관련이 있는지 확인합니다.
- Azure AI Search에서 관련 문서를 검색합니다.
- Azure OpenAI를 사용하여 검색된 문서 및 LLM 모델을 기반으로 답변을 생성합니다.
주요 구성 요소:
그래프 구조: LangChain.js 에이전트는 그래프로 표시됩니다. 여기서는 다음을 수행합니다.
- 노드는 의사 결정 또는 데이터 검색과 같은 특정 작업을 수행합니다.
- 엣지는 노드 간의 흐름을 정의하고 작업 순서를 결정합니다.
Azure AI Search 통합:
- HR 문서를 임베딩으로 변환하여 벡터 저장소에 삽입합니다.
- 임베딩 모델(
text-embedding-ada-002
)을 사용하여 임베딩을 생성합니다. - 사용자 프롬프트에 따라 관련 문서를 검색합니다.
Azure OpenAI 통합:
- 큰 언어 모델(
gpt-4o
)을 사용하여 다음을 수행합니다.- 일반 HR 문서에서 질문에 대답할 수 있는지 여부를 확인합니다.
- 문서 및 사용자 질문의 컨텍스트를 사용하여 프롬프트를 사용하여 답변을 생성합니다.
- 큰 언어 모델(
다음 표에는 일반 인사 문서에서 관련이 있고 답변할 수 없는 사용자 질문의 예가 있습니다.
질문 | HR 문서와 관련성 |
---|---|
Does the NorthWind Health Plus plan cover eye exams? |
관련. 직원 핸드북과 같은 HR 문서는 답변을 제공해야 합니다. |
How much of my perks + benefits have I spent? |
해당 없음. 이 질문에는 이 에이전트의 범위를 벗어나는 기밀 직원 데이터에 액세스해야 합니다. |
프레임워크를 사용하면 일반적으로 LangChain.js 에이전트 및 Azure 서비스 통합에 필요한 상용구 코드를 방지하여 비즈니스 요구 사항에 집중할 수 있습니다.
Node.js 프로젝트를 초기화하십시오.
새 디렉터리에서 TypeScript 에이전트에 대한 Node.js 프로젝트를 초기화합니다. 다음 명령어를 실행하세요:
npm init -y
npm pkg set type=module
npx tsc --init
환경 파일 만들기
.env
Azure 리소스 및 LangGraph에 대한 환경 변수를 저장하는 로컬 개발용 파일을 만듭니다. 리소스 인스턴스 이름이 임베딩 및 대규모 언어 모델에서 엔드포인트가 아닌 리소스 이름인지 확인하세요.
선택 사항: LangSmith를 사용하는 경우 LANGSMITH_TRACING
을 true
로 설정하여 로컬 개발을 진행합니다. 이 기능을 사용하지 않도록 설정false
하거나 프로덕션 환경에서 제거합니다.
종속성 설치
Azure AI Search에 대한 Azure 종속성을 설치합니다.
npm install @azure/search-documents
에이전트를 만들고 사용하기 위한 LangChain.js 종속성을 설치합니다.
npm install @langchain/community @langchain/core @langchain/langgraph @langchain/openai langchain
로컬 개발을 위한 개발 종속성을 설치합니다.
npm install --save-dev dotenv
Azure AI 검색 리소스 구성 파일 만들기
이 자습서에서 사용되는 다양한 Azure 리소스 및 모델을 관리하려면 각 리소스에 대한 특정 구성 파일을 만듭니다. 이 방법을 사용하면 문제를 명확하게 구분하고 분리하여 구성을 보다 쉽게 관리하고 유지 관리할 수 있습니다.
벡터 저장소에 문서를 업로드하는 구성
Azure AI Search 구성 파일은 관리 키를 사용하여 벡터 저장소에 문서를 삽입합니다. 이 키는 Azure AI Search로 데이터 수집을 관리하는 데 필수적입니다.
const endpoint = process.env.AZURE_AISEARCH_ENDPOINT;
const adminKey = process.env.AZURE_AISEARCH_ADMIN_KEY;
const indexName = process.env.AZURE_AISEARCH_INDEX_NAME;
export const VECTOR_STORE_ADMIN = {
endpoint,
key: adminKey,
indexName,
};
LangChain.js Azure AI Search로 데이터 수집을 위한 스키마를 정의해야 하는 필요성을 추상화하여 대부분의 시나리오에 적합한 기본 스키마를 제공합니다. 이 추상화는 프로세스를 간소화하고 사용자 지정 스키마 정의의 필요성을 줄입니다.
벡터 저장소를 쿼리하는 구성
벡터 저장소를 쿼리하려면 별도의 구성 파일을 만듭니다.
import {
AzureAISearchConfig,
AzureAISearchQueryType,
} from "@langchain/community/vectorstores/azure_aisearch";
const endpoint = process.env.AZURE_AISEARCH_ENDPOINT;
const queryKey = process.env.AZURE_AISEARCH_QUERY_KEY;
const indexName = process.env.AZURE_AISEARCH_INDEX_NAME;
export const DOC_COUNT = 3;
export const VECTOR_STORE_QUERY: AzureAISearchConfig = {
endpoint,
key: queryKey,
indexName,
search: {
type: AzureAISearchQueryType.Similarity,
},
};
벡터 저장소를 쿼리할 때 대신 쿼리 키를 사용합니다. 키를 분리하면 리소스에 안전하고 효율적으로 액세스할 수 있습니다.
Azure OpenAI 리소스 구성 파일 만들기
embeddings 및 LLM의 두 가지 모델을 관리하려면 별도의 구성 파일을 만듭니다. 이 방법을 사용하면 문제를 명확하게 구분하고 분리하여 구성을 보다 쉽게 관리하고 유지 관리할 수 있습니다.
벡터 저장소를 위한 임베딩 구성
Azure AI Search 벡터 저장소에 문서를 삽입하기 위한 포함을 만들려면 구성 파일을 만듭니다.
const key = process.env.AZURE_OPENAI_EMBEDDING_KEY;
const instance = process.env.AZURE_OPENAI_EMBEDDING_INSTANCE;
const apiVersion =
process.env.AZURE_OPENAI_EMBEDDING_API_VERSION || "2023-05-15";
const model =
process.env.AZURE_OPENAI_EMBEDDING_MODEL || "text-embedding-ada-002";
export const EMBEDDINGS_CONFIG = {
azureOpenAIApiKey: key,
azureOpenAIApiInstanceName: instance,
azureOpenAIApiEmbeddingsDeploymentName: model,
azureOpenAIApiVersion: apiVersion,
maxRetries: 1,
};
답변을 생성하기 위한 LLM 구성
큰 언어 모델에서 답변을 만들려면 구성 파일을 만듭니다.
const key = process.env.AZURE_OPENAI_COMPLETE_KEY;
const instance = process.env.AZURE_OPENAI_COMPLETE_INSTANCE;
const apiVersion =
process.env.AZURE_OPENAI_COMPLETE_API_VERSION || "2024-10-21";
const model = process.env.AZURE_OPENAI_COMPLETE_MODEL || "gpt-4o";
const maxTokens = process.env.AZURE_OPENAI_COMPLETE_MAX_TOKENS;
export const LLM_CONFIG = {
model,
azureOpenAIApiKey: key,
azureOpenAIApiInstanceName: instance,
azureOpenAIApiDeploymentName: model,
azureOpenAIApiVersion: apiVersion,
maxTokens: maxTokens ? parseInt(maxTokens, 10) : 100,
maxRetries: 1,
timeout: 60000,
};
상수 및 프롬프트
AI 애플리케이션은 종종 상수 문자열 및 프롬프트를 사용합니다. 이러한 상수는 별도의 파일로 관리합니다.
시스템 프롬프트를 만듭니다.
export const SYSTEM_PROMPT = `Answer the query with a complete paragraph based on the following context:`;
노드 상수 만들기:
export const ANSWER_NODE = "vector_store_retrieval";
export const DECISION_NODE = "requires_hr_documents";
export const START = "__start__";
export const END = "__end__";
예제 사용자 쿼리 만들기:
export const USER_QUERIES = [
"Does the NorthWind Health plus plan cover eye exams?",
"What is included in the NorthWind Health plus plan that is not included in the standard?",
"What happens in a performance review?",
];
Azure AI Search에 문서 로드
Azure AI Search에 문서를 로드하려면 LangChain.js 사용하여 프로세스를 간소화합니다. PDF로 저장된 문서는 포함으로 변환되어 벡터 저장소에 삽입됩니다. 이 프로세스를 통해 문서를 효율적으로 검색하고 쿼리할 수 있습니다.
주요 고려 사항:
- LangChain.js 추상화: LangChain.js 스키마 정의 및 클라이언트 생성과 같은 많은 복잡성을 처리하여 프로세스를 간단하게 만듭니다.
- 제한 및 다시 시도 논리: 샘플 코드에는 최소 대기 함수가 포함되어 있지만 프로덕션 애플리케이션은 포괄적인 오류 처리 및 재시도 논리를 구현하여 제한 및 일시적 오류를 관리해야 합니다.
문서를 로드하는 단계
PDF 문서를 찾습니다. 문서는 데이터 디렉터리에 저장됩니다.
LangChain.js에 PDF 로드 :
loadPdfsFromDirectory
함수를 사용하여 문서를 로드합니다. 이 함수는 LangChain.js 커뮤니티의PDFLoader.load
메서드를 활용하여 각 파일을 읽고 배열을 반환합니다Document[]
. 이 배열은 표준 LangChain.js 문서 형식입니다.import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf"; import { waiter } from "../utils/waiter.js"; import { loadDocsIntoAiSearchVector } from "./load_vector_store.js"; import fs from "fs/promises"; import path from "path"; export async function loadPdfsFromDirectory( embeddings: any, dirPath: string, ): Promise<void> { try { const files = await fs.readdir(dirPath); console.log( `PDF: Loading directory ${dirPath}, ${files.length} files found`, ); for (const file of files) { if (file.toLowerCase().endsWith(".pdf")) { const fullPath = path.join(dirPath, file); console.log(`PDF: Found ${fullPath}`); const pdfLoader = new PDFLoader(fullPath); console.log(`PDF: Loading ${fullPath}`); const docs = await pdfLoader.load(); console.log(`PDF: Sending ${fullPath} to index`); const storeResult = await loadDocsIntoAiSearchVector(embeddings, docs); console.log(`PDF: Indexing result: ${JSON.stringify(storeResult)}`); await waiter(1000 * 60); // waits for 1 minute between files } } } catch (err) { console.error("Error loading PDFs:", err); } }
Azure AI Search에 문서 삽입: 이 함수를
loadDocsIntoAiSearchVector
사용하여 문서 배열을 Azure AI Search 벡터 저장소로 보냅니다. 이 함수는 embedding 클라이언트를 사용하여 문서를 처리하고, 부하 조절 기능을 위한 기본 대기 함수를 포함합니다. 프로덕션의 경우 강력한 재시도/백오프 메커니즘을 구현합니다.import { AzureAISearchVectorStore } from "@langchain/community/vectorstores/azure_aisearch"; import type { Document } from "@langchain/core/documents"; import type { EmbeddingsInterface } from "@langchain/core/embeddings"; import { VECTOR_STORE_ADMIN } from "../config/vector_store_admin.js"; export async function loadDocsIntoAiSearchVector( embeddings: EmbeddingsInterface, documents: Document[], ): Promise<AzureAISearchVectorStore> { const vectorStore = await AzureAISearchVectorStore.fromDocuments( documents, embeddings, VECTOR_STORE_ADMIN, ); return vectorStore; }
에이전트 워크플로 만들기
LangChain.js에서 LangGraph를 사용하여 LangChain.js 에이전트를 빌드합니다. LangGraph를 사용하면 노드와 가장자리를 정의할 수 있습니다.
- 노드: 작업이 수행되는 위치입니다.
- Edge: 노드 간의 연결을 정의합니다.
워크플로 구성 요소
이 애플리케이션에서 두 개의 작업 노드는 다음과 같습니다.
- requiresHrResources: 질문이 Azure OpenAI LLM을 사용하는 HR 설명서와 관련이 있는지 확인합니다.
- getAnswer: 답변을 검색합니다. 답변은 Azure AI Search에서 문서 임베딩을 사용하여 Azure OpenAI LLM으로 문서를 전송하는 LangChain.js 리트리버 체인을 통해 제공됩니다. 이것이 검색-강화 생성의 본질입니다.
에지는 getAnswer 노드를 호출하는 데 필요한 시작, 종료 및 조건을 정의합니다.
그래프 내보내기
LangGraph Studio를 사용하여 그래프를 실행하고 디버그하려면 해당 개체로 내보냅니다.
import { StateGraph } from "@langchain/langgraph";
import { StateAnnotation } from "./langchain/state.js";
import { route as endRoute } from "./langchain/check_route_end.js";
import { getAnswer } from "./azure/get_answer.js";
import { START, ANSWER_NODE, DECISION_NODE } from "./config/nodes.js";
import {
requiresHrResources,
routeRequiresHrResources,
} from "./azure/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";
addNode, addEdge 및 addConditionalEdges 메서드에서 첫 번째 매개 변수는 그래프 내의 개체를 식별하는 이름(문자열)입니다. 두 번째 매개 변수는 해당 단계에서 호출해야 하는 함수 또는 호출할 노드의 이름입니다.
addEdge 메서드의 경우 해당 이름은 START(./src/config/nodes.ts 파일에 정의된 "start")이며 항상 DECISION_NODE 호출합니다. 해당 노드는 두 개의 매개 변수로 정의됩니다. 첫 번째는 이름, DECISION_NODE, 두 번째는 requiresHrResources라는 함수입니다.
일반적인 기능
이 앱은 일반적인 LangChain 기능을 제공합니다.
상태 관리:
import { BaseMessage, BaseMessageLike } from "@langchain/core/messages"; import { Annotation, messagesStateReducer } from "@langchain/langgraph"; export const StateAnnotation = Annotation.Root({ messages: Annotation<BaseMessage[], BaseMessageLike[]>({ reducer: messagesStateReducer, default: () => [], }), });
경로 종료:
import { StateAnnotation } from "./state.js"; import { END, ANSWER_NODE } from "../config/nodes.js"; export const route = ( state: typeof StateAnnotation.State, ): typeof END | typeof ANSWER_NODE => { if (state.messages.length > 0) { return END; } return ANSWER_NODE; };
이 애플리케이션에 대한 유일한 사용자 지정 경로는 routeRequiresHrResources입니다. 이 경로는 requiresHrResources 노드의 대답이 사용자의 질문이 ANSWER_NODE 노드로 계속 진행되어야 했음을 나타내는지 확인하는 데 사용됩니다. 이 경로는 requiresHrResources의 출력을 받기 때문에 동일한 파일에 있습니다.
Azure OpenAI 리소스 통합
Azure OpenAI 통합은 두 가지 모델을 사용합니다.
- 포함: 문서를 벡터 저장소에 삽입하는 데 사용됩니다.
- LLM: 벡터 저장소를 쿼리하고 응답을 생성하여 질문에 대답하는 데 사용됩니다.
embeddings 클라이언트와 LLM 클라이언트는 다양한 용도로 사용됩니다. 단일 모델 또는 클라이언트로 줄이지 마세요.
임베딩 모델
벡터 저장소에서 문서를 검색할 때마다 embeddings 클라이언트가 필요합니다. 일시적인 오류를 처리하는 maxRetries 에 대한 구성이 포함되어 있습니다.
import { AzureOpenAIEmbeddings } from "@langchain/openai";
import { EMBEDDINGS_CONFIG } from "../config/embeddings.js";
export function getEmbeddingClient(): AzureOpenAIEmbeddings {
return new AzureOpenAIEmbeddings({ ...EMBEDDINGS_CONFIG, maxRetries: 1 });
}
LLM 모델
LLM 모델은 두 가지 유형의 질문에 대답하는 데 사용됩니다.
- HR에 대한 관련성: 사용자의 질문이 HR 설명서와 관련이 있는지 여부를 결정합니다.
- 답변 생성: Azure AI Search의 문서로 보강된 사용자의 질문에 대한 답변을 제공합니다.
LLM 클라이언트는 응답이 필요할 때 만들어지고 호출됩니다.
import { RunnableConfig } from "@langchain/core/runnables";
import { StateAnnotation } from "../langchain/state.js";
import { AzureChatOpenAI } from "@langchain/openai";
import { LLM_CONFIG } from "../config/llm.js";
export const getLlmChatClient = (): AzureChatOpenAI => {
return new AzureChatOpenAI({
...LLM_CONFIG,
temperature: 0,
});
};
export const callChatCompletionModel = async (
state: typeof StateAnnotation.State,
_config: RunnableConfig,
): Promise<typeof StateAnnotation.Update> => {
const llm = new AzureChatOpenAI({
...LLM_CONFIG,
temperature: 0,
});
const completion = await llm.invoke(state.messages);
completion;
return {
messages: [
...state.messages,
{
role: "assistant",
content: completion.content,
},
],
};
};
LangChain.js 에이전트는 LLM을 사용하여 질문이 HR 설명서와 관련이 있는지 또는 워크플로가 그래프의 끝으로 라우팅되어야 하는지 여부를 결정합니다.
// @ts-nocheck
import { getLlmChatClient } from "./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 "../config/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;
};
requiresHrResources 함수는 업데이트된 상태에 HR resources required detected
콘텐츠로 메시지를 설정합니다. 라우터 routeRequiresHrResources는 메시지를 보낼 위치를 결정하기 위해 해당 콘텐츠를 찾습니다.
벡터 저장소에 대한 Azure AI Search 리소스 통합
Azure AI Search 통합은 LLM이 getAnswer 노드에 대한 답변을 보강할 수 있도록 벡터 저장소 문서를 제공합니다. LangChain.js 필요한 코드가 최소화되도록 추상화의 대부분을 다시 제공합니다. 함수는 다음과 같습니다.
- getReadOnlyVectorStore: 쿼리 키를 사용하여 클라이언트를 검색합니다.
- getDocsFromVectorStore: 사용자의 질문에 대한 관련 문서를 찾습니다.
import { AzureAISearchVectorStore } from "@langchain/community/vectorstores/azure_aisearch";
import { VECTOR_STORE_QUERY, DOC_COUNT } from "../config/vector_store_query.js";
import { getEmbeddingClient } from "./embeddings.js";
export function getReadOnlyVectorStore(): AzureAISearchVectorStore {
const embeddings = getEmbeddingClient();
return new AzureAISearchVectorStore(embeddings, VECTOR_STORE_QUERY);
}
export async function getDocsFromVectorStore(
query: string,
): Promise<Document[]> {
const store = getReadOnlyVectorStore();
// @ts-ignore
//return store.similaritySearchWithScore(query, DOC_COUNT);
return store.similaritySearch(query, DOC_COUNT);
}
LangChain.js 통합 코드를 사용하면 벡터 저장소에서 관련 문서를 매우 쉽게 검색할 수 있습니다.
LLM에서 답변을 얻기 위한 코드 작성
이제 통합 구성 요소가 빌드되었으므로 getAnswer 함수를 만들어 관련 벡터 저장소 문서를 검색하고 LLM을 사용하여 답변을 생성합니다.
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { createRetrievalChain } from "langchain/chains/retrieval";
import { getLlmChatClient } from "./llm.js";
import { StateAnnotation } from "../langchain/state.js";
import { AIMessage } from "@langchain/core/messages";
import { getReadOnlyVectorStore } from "./vector_store.js";
const EMPTY_STATE = { messages: [] };
export async function getAnswer(
state: typeof StateAnnotation.State = EMPTY_STATE,
): Promise<typeof StateAnnotation.Update> {
const vectorStore = getReadOnlyVectorStore();
// 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 questionAnsweringPrompt = ChatPromptTemplate.fromMessages([
[
"system",
"Answer the user's questions based on the below context:\n\n{context}",
],
["human", "{input}"],
]);
const combineDocsChain = await createStuffDocumentsChain({
llm: getLlmChatClient(),
prompt: questionAnsweringPrompt,
});
const retrievalChain = await createRetrievalChain({
retriever: vectorStore.asRetriever(2),
combineDocsChain,
});
const result = await retrievalChain.invoke({ input: userInput });
const assistantMessage = new AIMessage(result.answer);
return {
messages: [...state.messages, assistantMessage],
};
}
이 함수는 두 자리 표시자가 있는 프롬프트를 제공합니다. 하나는 사용자의 질문에 대한 것이고 다른 하나는 컨텍스트용입니다. 컨텍스트는 AI Search 벡터 저장소의 모든 관련 문서입니다. 프롬프트 및 LLM 클라이언트를 createStuffDocumentsChain 에 전달하여 LLM 체인을 만듭니다. LLM 체인을 createRetrievalChain에 전달하여 프롬프트, 관련 문서 및 LLM을 포함하는 체인을 생성합니다.
retrievalChain.invoke 및 사용자의 질문을 입력으로 사용하여 체인을 실행하여 답변을 가져옵니다. 메시지 상태에서 답변을 반환합니다.
에이전트 패키지 빌드
package.json 스크립트를 추가하여 TypeScript 애플리케이션을 빌드합니다.
"build": "tsc",
LangChain.js 에이전트를 빌드합니다.
npm run build
선택 사항 - LangChain Studio를 사용하여 로컬 개발에서 LangChain.js 에이전트 실행
필요에 따라 로컬 개발의 경우 LangChain Studio를 사용하여 LangChain.js 에이전트로 작업합니다.
그래프를
langgraph.json
정의하는 파일을 만듭니다.{ "dependencies": [], "graphs": { "agent": "./src/graph.ts:hr_documents_answer_graph" }, "env": "../.env" }
LangGraph CLI를 설치합니다.
npm install @langchain/langgraph-cli --save-dev
LangGraph CLI에 파일을 전달하기 위해 package.json 스크립트를 작성하십시오.
.env
"studio": "npx @langchain/langgraph-cli dev",
CLI는 터미널에서 실행되고 LangGraph Studio에 대한 브라우저를 엽니다.
Welcome to ╦ ┌─┐┌┐┌┌─┐╔═╗┬─┐┌─┐┌─┐┬ ┬ ║ ├─┤││││ ┬║ ╦├┬┘├─┤├─┘├─┤ ╩═╝┴ ┴┘└┘└─┘╚═╝┴└─┴ ┴┴ ┴ ┴.js - 🚀 API: http://localhost:2024 - 🎨 Studio UI: https://smith.langchain.com/studio?baseUrl=http://localhost:2024 This in-memory server is designed for development and testing. For production use, please use LangGraph Cloud. info: ▪ Starting server... info: ▪ Initializing storage... info: ▪ Registering graphs from C:\Users\myusername\azure-typescript-langchainjs\packages\langgraph-agent info: ┏ Registering graph with id 'agent' info: ┗ [1] { graph_id: 'agent' } info: ▪ Starting 10 workers info: ▪ Server running at ::1:2024
LangGraph Studio에서 LangChain.js 에이전트를 봅니다.
+ 메시지를 선택하여 사용자 질문을 추가한 다음 제출을 선택합니다.
질문 HR 문서와 관련성 Does the NorthWind Health plus plan cover eye exams?
이 질문은 HR과 관련이 있으며 직원 핸드북, 복리후생 핸드북 및 직원 역할 라이브러리와 같은 HR 문서가 답변할 수 있을 만큼 일반적입니다. What is included in the NorthWind Health plus plan that is not included in the standard?
이 질문은 HR과 관련이 있으며 직원 핸드북, 복리후생 핸드북 및 직원 역할 라이브러리와 같은 HR 문서가 답변할 수 있을 만큼 일반적입니다. How much of my perks + benefit have I spent
이 질문은 일반적인 비인격 HR 문서와 관련이 없습니다. 이 질문은 직원 데이터에 액세스할 수 있는 에이전트로 전송되어야 합니다. 질문이 HR 문서와 관련된 경우 DECISION_NODE 통과하여 ANSWER_NODE 전달해야 합니다.
터미널 출력을 확인하여 LLM에 대한 질문과 LLM의 답변을 확인합니다.
질문이 HR 문서와 관련이 없는 경우 흐름은 직접 종료됩니다.
LangChain.js 에이전트가 잘못된 결정을 내릴 때 문제는 다음과 같습니다.
- 사용된 LLM 모델
- 벡터 저장소의 문서 수
- 의사 결정 노드에 사용되는 프롬프트입니다.
앱에서 LangChain.js 에이전트 실행
웹 API와 같은 부모 애플리케이션에서 LangChain.js 에이전트를 호출하려면 LangChain.js 에이전트의 호출을 제공해야 합니다.
import { HumanMessage } from "@langchain/core/messages";
import { hr_documents_answer_graph as app } from "./graph.js";
const AIMESSAGE = "aimessage";
export async function ask_agent(question: string) {
const initialState = { messages: [new HumanMessage(question)], iteration: 0 };
const finalState = await app.invoke(initialState);
return finalState;
}
export async function get_answer(question: string) {
try {
const answerResponse = await ask_agent(question);
const answer = answerResponse.messages
.filter(
(m: any) =>
m &&
m.constructor?.name?.toLowerCase() === AIMESSAGE.toLocaleLowerCase(),
)
.map((m: any) => m.content)
.join("\n");
return answer;
} catch (e) {
console.error("Error in get_answer:", e);
throw e;
}
}
두 함수는 다음과 같습니다.
- ask_agent: 이 함수는 langChain 다중 에이전트 워크플로에 LangChain.js 에이전트를 추가할 수 있도록 상태를 반환합니다.
- get_answer: 이 함수는 답변의 텍스트만 반환합니다. 이 함수는 API에서 호출할 수 있습니다.
문제 해결
- 프로시저에 문제가 있는 경우 샘플 코드 리포지토리에서 문제를 만듭니다.
자원을 정리하세요
Azure AI Search 리소스 및 Azure OpenAI 리소스를 보유하는 리소스 그룹을 삭제합니다.