次の方法で共有


チュートリアル: Azure AI Search を使用して LangChain.js エージェントを構築する

このチュートリアルでは、LangChain.js を使用して、NorthWind 社の従業員が人事関連の質問をできるようにする LangChain.js エージェントを構築します。 フレームワークを使用すると、通常、LangChain.js エージェントと Azure サービス統合に必要な定型コードを回避し、ビジネス ニーズに集中できます。

このチュートリアルでは、次の操作を行います。

  • LangChain.js エージェントを設定する
  • azure リソースを LangChain.js エージェントに統合する
  • 必要に応じて、LangSmith Studio で LangChain.js エージェントをテストする

NorthWind は、すべての従業員がアクセスできるパブリック人事ドキュメントと、機密の従業員データを含む機密人事データベースという 2 つのデータ ソースに依存しています。 このチュートリアルでは、パブリック人事ドキュメントを使用して従業員の質問に回答できるかどうかを決定する LangChain.js エージェントの構築に焦点を当てます。 その場合は、LangChain.js エージェントが直接回答を提供します。

質問に回答するために人事ドキュメントを使用する LangChain.js エージェント ワークフローとそのデシジョン ブランチを示す図。

警告

この記事では、キーを使用してリソースにアクセスします。 運用環境では、Azure RBAC とマネージド ID を使用することをお勧めします。 この方法では、キーを管理またはローテーションする必要がなくなり、セキュリティが強化され、アクセス制御が簡素化されます。

[前提条件]

  • アクティブな Azure アカウントアカウントがない場合、Azure 試用版にサインアップして、最大 10 件の無料 Mobile Apps を入手できます。 アカウントがない場合は、無料でアカウントを作成 します。
  • Node.js LTS がシステムにインストールされています。
  • TypeScript コードを記述およびコンパイルするための TypeScript。
  • LangChain.js エージェントをビルドするためのライブラリです。
  • 省略可能: AI の使用状況を監視するための LangSmith 。 プロジェクト名、キー、エンドポイントが必要です。
  • 省略可能: LangGraph チェーン と LangChain.js エージェントをデバッグするための LangGraph Studio。
  • Azure AI Search リソース: リソース エンドポイント、管理キー (ドキュメント挿入用)、クエリ キー (ドキュメントの読み取り用)、およびインデックス名があることを確認します。
  • Azure OpenAI リソース: リソース インスタンス名、キー、および API バージョンを含む 2 つのモデルが必要です。
    • text-embedding-ada-002などの埋め込みモデル。
    • gpt-4oのような大規模な言語モデル。

エージェントのアーキテクチャ

LangChain.js フレームワークは、LangGraph としてインテリジェント なエージェントを構築するための意思決定フローを提供します。 このチュートリアルでは、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 ドキュメントへの関連性
Does the NorthWind Health Plus plan cover eye exams? 関連した。 従業員ハンドブックなどの人事文書は、回答を提供する必要があります。
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

環境ファイルを作成する

Azure リソースと LangGraph の環境変数を格納するためのローカル開発用の .env ファイルを作成します。 埋め込みおよび LLM のリソース インスタンス名が、エンドポイントではなくリソース名に過ぎないことを確認します。

省略可能: LangSmith を使用する場合は、 LANGSMITH_TRACING をローカル開発用に true に設定します。 運用環境で無効にする (false) か、削除します。

依存関係のインストール

  1. Azure AI Search の Azure 依存関係をインストールします。

    npm install @azure/search-documents
    
  2. エージェント LangChain.js 作成および使用するための依存関係をインストールします。

    npm install @langchain/community @langchain/core @langchain/langgraph @langchain/openai langchain
    
  3. ローカル開発用の開発依存関係をインストールします。

    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 リソース構成ファイルを作成する

埋め込みと LLM という 2 つの異なるモデルを管理するには、個別の構成ファイルを作成します。 このアプローチにより、懸念事項の明確さと分離が確保され、構成の管理と保守が容易になります。

ベクター ストアの埋め込みの構成

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 にドキュメントを読み込むには、LangChain.js を使用してプロセスを簡略化します。 PDF として格納されたドキュメントは埋め込み形式に変換され、ベクター ストアに挿入されます。 このプロセスにより、ドキュメントを効率的に取得してクエリを実行する準備が整います。

重要な考慮事項:

  • LangChain.js 抽象化: LangChain.js は、スキーマ定義やクライアントの作成など、複雑さの多くを処理し、プロセスを簡単にします。
  • 調整と再試行ロジック: サンプル コードには最小限の待機関数が含まれていますが、実稼働アプリケーションでは、調整と一時的なエラーを管理するための包括的なエラー処理と再試行ロジックを実装する必要があります。

ドキュメントを読み込む手順

  1. PDF ドキュメントを見つけます。ドキュメントは データ ディレクトリに格納されます。

  2. 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);
      }
    }
    
  3. Azure AI Search にドキュメントを挿入する: loadDocsIntoAiSearchVector 関数を使用して、ドキュメント配列を Azure AI Search ベクター ストアに送信します。 この関数は、埋め込みクライアントを使用してドキュメントを処理し、レート制限を管理するための基本的な待機関数を含みます。 運用環境では、堅牢な再試行/バックオフ メカニズムを実装します。

    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 を使用すると、ノードとエッジを定義できます。

  • ノード: 作業が実行される場所。
  • エッジ: ノード間の接続を定義します。

ワークフローのコンポーネント

このアプリケーションでは、次の 2 つの作業ノードがあります。

  • 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";

addNodeaddEdge、および addConditionalEdges メソッドでは、最初のパラメーターは、グラフ内のオブジェクトを識別するための名前を文字列として指定します。 2 番目のパラメーターは、そのステップで呼び出す必要がある関数か、呼び出すノードの名前です。

addEdge メソッドの場合、その名前は START (./src/config/nodes.ts ファイルで定義されている "start" )、常にDECISION_NODEを呼び出します。 そのノードは 2 つのパラメーターで定義されます。1 つ目は名前、DECISION_NODE、2 つ目は 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 統合では、次の 2 つの異なるモデルが使用されます。

  • 埋め込み: ベクター ストアにドキュメントを挿入するために使用します。
  • LLM: ベクター ストアにクエリを実行し、応答を生成することで、質問に回答するために使用されます。

埋め込みクライアントと LLM クライアントは、さまざまな目的を果たします。 単一のモデルまたはクライアントに減らさないでください。

埋め込みモデル

ベクター ストアからドキュメントが取得されるたびに、埋め込みクライアントが必要です。 一時的なエラーを処理するための 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 モデルは、次の 2 種類の質問に回答するために使用されます。

  • 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],
  };
}

この関数は、ユーザーの質問用とコンテキスト用の 2 つのプレースホルダーを含むプロンプトを提供します。 コンテキストは、AI Search ベクター ストアからのすべての関連ドキュメントです。 プロンプトと LLM クライアントを createStuffDocumentsChain に渡して、LLM チェーンを作成します。 LLM チェーンを渡して createRetrievalChain を作成し、プロンプト、関連ドキュメント、および LLM を含むチェーンを作成します。

retrievalChain.invoke とユーザーの質問を入力として使用してチェーンを実行し、回答を取得します。 メッセージ状態の回答を返します。

エージェント パッケージをビルドする

  1. package.json にスクリプト を追加して TypeScript アプリケーションをビルドします。

    "build": "tsc",
    
  2. LangChain.js エージェントをビルドします。

    npm run build
    

省略可能 - LangChain Studio を使用してローカル開発で LangChain.js エージェントを実行する

必要に応じて、ローカル開発用に LangChain Studio を使用して、LangChain.js エージェントを操作します。

  1. グラフを定義する langgraph.json ファイルを作成します。

    {
        "dependencies": [],
        "graphs": {
          "agent": "./src/graph.ts:hr_documents_answer_graph"
        },
        "env": "../.env"
      }
    
  2. LangGraph CLI をインストールします。

    npm install @langchain/langgraph-cli --save-dev
    
  3. .env ファイルを LangGraph CLI に渡すスクリプトをpackage.jsonに作成します。

    "studio": "npx @langchain/langgraph-cli dev",
    
  4. 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
    
  5. LangGraph Studio で LangChain.js エージェントを表示します。

    グラフが読み込まれた LangSmith Studio のスクリーンショット。

  6. [+ メッセージ] を選択してユーザーの質問を追加し、[送信] を選択します。

    質問 人事文書との関連性
    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? この質問は人事および一般に関連しており、従業員ハンドブック、特典ハンドブック、従業員ロールライブラリなどの人事文書がそれに回答できる必要があります。
    How much of my perks + benefit have I spent この質問は、一般的で非人格的なHRドキュメントには関連しません。 この質問は、従業員データにアクセスできるエージェントに送信する必要があります。
  7. 質問が人事ドキュメントに関連する場合は、 DECISION_NODE を通過し、 ANSWER_NODEに渡す必要があります。

    ターミナル出力を見て、LLM への質問と LLM からの回答を確認します。

  8. 質問が HR ドキュメントに関連しない場合、フローは直接 終了します。

LangChain.js エージェントが正しくない決定を行うと、次の問題が発生する可能性があります。

  • 使用される LLM モデル
  • ベクター ストアからのドキュメントの数
  • 意思決定ノードで使用されるプロンプト。

アプリから LangChain.js エージェントを実行する

Web 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;
  }
}

2 つの関数は次のとおりです。

  • ask_agent: この関数は状態を返します。これにより、LangChain.js エージェントを LangChain マルチエージェント ワークフローに追加できます。
  • get_answer: この関数は、回答のテキストのみを返します。 この関数は API から呼び出すことができます。

トラブルシューティング

リソースをクリーンアップする

Azure AI Search リソースと Azure OpenAI リソースを保持するリソース グループを削除します。