Udostępnij za pośrednictwem


Samouczek: tworzenie agenta LangChain.js za pomocą usługi Azure AI Search

W tym samouczku użyjesz LangChain.js do utworzenia agenta LangChain.js, który umożliwia pracownikom firmy NorthWind zadawanie pytań związanych z zasobami ludzkimi. Korzystając ze struktury, należy unikać standardowego kodu zwykle wymaganego w przypadku agentów LangChain.js i integracji usług platformy Azure, co pozwala skupić się na potrzebach biznesowych.

W tym samouczku nauczysz się następujących rzeczy:

  • Konfigurowanie agenta LangChain.js
  • Integrowanie zasobów platformy Azure z agentem LangChain.js
  • Opcjonalnie przetestuj agenta LangChain.js w programie LangSmith Studio

NorthWind opiera się na dwóch źródłach danych: publiczna dokumentacja kadr dostępna dla wszystkich pracowników i poufnej bazy danych kadr zawierającej poufne dane pracowników. Ten samouczek koncentruje się na tworzeniu agenta LangChain.js, który określa, czy można odpowiedzieć na pytanie pracownika przy użyciu publicznych dokumentów kadrowych. Jeśli tak, agent LangChain.js zapewnia bezpośrednią odpowiedź.

Diagram ilustrujący przepływ pracy agenta LangChain.js i jego gałąź decyzyjną służącą do używania dokumentacji hr w celu udzielenia odpowiedzi na pytania.

Ostrzeżenie

W tym artykule użyto kluczy do uzyskiwania dostępu do zasobów. W środowisku produkcyjnym zalecanym najlepszym rozwiązaniem jest użycie kontroli dostępu opartej na rolach platformy Azure i tożsamości zarządzanej. Takie podejście eliminuje konieczność zarządzania kluczami lub obracania ich, zwiększania zabezpieczeń i upraszczania kontroli dostępu.

Wymagania wstępne

  • Aktywne konto platformy Azure. Utwórz bezpłatne konto , jeśli go nie masz.
  • Node.js LTS zainstalowane w systemie.
  • TypeScript do pisania i kompilowania kodu TypeScript.
  • LangChain.js bibliotekę do kompilowania agenta.
  • Opcjonalnie: LangSmith do monitorowania użycia sztucznej inteligencji. Potrzebujesz nazwy projektu, klucza i punktu końcowego.
  • Opcjonalnie: LangGraph Studio do debugowania łańcuchów LangGraph i agentów LangChain.js.
  • Zasób usługi Azure AI Search: upewnij się, że masz punkt końcowy zasobu, klucz administracyjny (dla wstawiania dokumentów), klucz zapytania (do odczytywania dokumentów) i nazwę indeksu.
  • Zasób Azure OpenAI: potrzebujesz nazwy instancji zasobu, klucza i dwóch modeli z ich wersjami interfejsu API:
    • Model osadzania, taki jak text-embedding-ada-002.
    • Duży model językowy, taki jak gpt-4o.

Architektura agenta

Struktura LangChain.js zapewnia przepływ decyzji do tworzenia inteligentnych agentów jako LangGraph. W tym samouczku utworzysz agenta LangChain.js, który integruje się z usługą Azure AI Search i usługą Azure OpenAI, aby odpowiedzieć na pytania związane z hr. Architektura agenta została zaprojektowana w celu:

  • Ustal, czy pytanie dotyczy dokumentacji kadrowej.
  • Pobierz odpowiednie dokumenty z usługi Azure AI Search.
  • Użyj usługi Azure OpenAI, aby wygenerować odpowiedź na podstawie pobranych dokumentów i modelu LLM.

Kluczowe składniki:

  • Struktura grafu: agent LangChain.js jest reprezentowany jako graf, gdzie:

    • Węzły wykonują określone zadania, takie jak podejmowanie decyzji lub pobieranie danych.
    • Krawędzie definiują przepływ między węzłami, co określa kolejność operacji.
  • Integracja usługi Azure AI Search:

    • Wstawia dokumenty HR do magazynu wektorowego jako osadzanie.
    • Używa modelu osadzania (text-embedding-ada-002), aby utworzyć te osadzanie.
    • Pobiera odpowiednie dokumenty na podstawie monitu użytkownika.
  • Integracja z usługą Azure OpenAI:

    • Wykorzystuje duży model językowy (gpt-4o) do:
      • Określa, czy na pytanie można odpowiedzieć na podstawie ogólnych dokumentów kadrowych.
      • Generuje odpowiedź na podstawie polecenia przy użyciu kontekstu z dokumentów i pytania użytkownika.

W poniższej tabeli przedstawiono przykłady pytań użytkowników, które są i nie są istotne oraz odpowiadające na podstawie ogólnych dokumentów dotyczących zasobów ludzkich.

Pytanie Znaczenie dla dokumentów KADR
Does the NorthWind Health Plus plan cover eye exams? Istotny. Dokumenty kadr, takie jak podręcznik pracownika, powinny dostarczyć odpowiedź.
How much of my perks + benefits have I spent? Nie dotyczy. To pytanie wymaga dostępu do poufnych danych pracowników, które wykraczają poza zakres tego agenta.

Korzystając ze struktury, należy unikać standardowego kodu zwykle wymaganego w przypadku agentów LangChain.js i integracji usług platformy Azure, co pozwala skupić się na potrzebach biznesowych.

Inicjowanie projektu Node.js

W nowym katalogu zainicjuj projekt Node.js dla swojego agenta TypeScript. Uruchom następujące polecenia:

npm init -y
npm pkg set type=module
npx tsc --init

Tworzenie pliku środowiska

Utwórz plik do lokalnego .env programowania w celu przechowywania zmiennych środowiskowych dla zasobów platformy Azure i języka LangGraph. Upewnij się, że nazwa wystąpienia zasobu dla embeddingu i LLM powinna być tylko nazwą zasobu, a nie punktem końcowym.

Opcjonalnie: w przypadku korzystania z biblioteki LangSmith ustaw wartość LANGSMITH_TRACING na true potrzeby programowania lokalnego. Wyłącz ją (false) lub usuń w środowisku produkcyjnym.

Instalowanie zależności

  1. Zainstaluj zależności platformy Azure dla usługi Azure AI Search:

    npm install @azure/search-documents
    
  2. Zainstaluj zależności LangChain.js, aby tworzyć i używać agenta.

    npm install @langchain/community @langchain/core @langchain/langgraph @langchain/openai langchain
    
  3. Zainstaluj zależności programistyczne na potrzeby programowania lokalnego:

    npm install --save-dev dotenv
    

Tworzenie plików konfiguracji zasobów usługi Azure AI search

Aby zarządzać różnymi zasobami i modelami platformy Azure używanymi w tym samouczku, utwórz określone pliki konfiguracji dla każdego zasobu. Takie podejście zapewnia przejrzystość i separację problemów, co ułatwia zarządzanie konfiguracjami i ich konserwację.

Konfiguracja przesyłania dokumentów do repozytorium wektorów

Plik konfiguracji usługi Azure AI Search używa klucza administratora do wstawiania dokumentów do magazynu wektorów. Ten klucz jest niezbędny do zarządzania pozyskiwaniem danych w usłudze 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 upraszcza potrzebę definiowania schematu wprowadzania danych do usługi Azure AI Search, zapewniając domyślny schemat odpowiedni dla większości scenariuszy. Ta abstrakcja upraszcza proces i zmniejsza potrzebę niestandardowych definicji schematu.

Konfiguracja magazynu wektorów zapytań

W przypadku wykonywania zapytań dotyczących magazynu wektorów utwórz oddzielny plik konfiguracji:

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

Zamiast tego, podczas wykonywania zapytań do magazynu wektorów, użyj klucza zapytania. To rozdzielenie kluczy zapewnia bezpieczny i wydajny dostęp do zasobu.

Tworzenie plików konfiguracji zasobów usługi Azure OpenAI

Aby zarządzać dwoma różnymi modelami, osadzeniami i LLM, twórz osobne pliki konfiguracyjne. Takie podejście zapewnia przejrzystość i separację problemów, co ułatwia zarządzanie konfiguracjami i ich konserwację.

Konfiguracja osadzeń dla przechowywania wektorów

Aby utworzyć osadzanie do wstawiania dokumentów do magazynu wektorów usługi Azure AI Search, utwórz plik konfiguracji:

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

Konfiguracja dla usługi LLM w celu wygenerowania odpowiedzi

Aby utworzyć odpowiedzi na podstawie dużego modelu językowego, utwórz plik konfiguracji:

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

Stałe i komunikaty

Aplikacje sztucznej inteligencji często opierają się na ciągach stałych i monitach. Zarządzaj tymi stałymi przy użyciu oddzielnych plików.

Utwórz monit systemowy:

export const SYSTEM_PROMPT = `Answer the query with a complete paragraph based on the following context:`;

Utwórz stałe dla węzłów.

export const ANSWER_NODE = "vector_store_retrieval";
export const DECISION_NODE = "requires_hr_documents";
export const START = "__start__";
export const END = "__end__";

Tworzenie przykładowych zapytań użytkowników:

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?",
];

Aby załadować dokumenty do usługi Azure AI Search, użyj LangChain.js, aby uprościć proces. Dokumenty, przechowywane jako pliki PDF, są konwertowane na reprezentacje wektorowe i wstawiane do sklepu wektorów. Ten proces zapewnia, że dokumenty są gotowe do wydajnego pobierania i wykonywania zapytań.

Najważniejsze zagadnienia:

  • LangChain.js abstrakcja: LangChain.js obsługuje dużą część złożoności, taką jak definicje schematów i tworzenie klientów, czyniąc proces prostszym.
  • Ograniczanie i logika ponawiania: mimo że przykładowy kod zawiera minimalną funkcję oczekiwania, aplikacje produkcyjne powinny implementować kompleksową obsługę błędów i logikę ponawiania w celu zarządzania ograniczaniem i błędami przejściowymi.

Kroki ładowania dokumentów

  1. Znajdź dokumenty PDF: dokumenty są przechowywane w katalogu danych.

  2. Załaduj pliki PDF do LangChain.js: użyj loadPdfsFromDirectory funkcji , aby załadować dokumenty. Ta funkcja wykorzystuje metodę LangChain.js community PDFLoader.load do odczytywania każdego pliku i zwracania Document[] tablicy. Ta tablica jest standardowym formatem dokumentu 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. Wstawianie dokumentów do usługi Azure AI Search: użyj loadDocsIntoAiSearchVector funkcji , aby wysłać tablicę dokumentów do magazynu wektorów usługi Azure AI Search. Ta funkcja używa klienta osadzania do przetwarzania dokumentów i zawiera podstawową funkcję oczekiwania do obsługi ograniczania przepustowości. W przypadku środowiska produkcyjnego zaimplementuj niezawodny mechanizm ponawiania/wycofywania.

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

Tworzenie przepływu pracy agenta

W LangChain.jsskompiluj agenta LangChain.js za pomocą usługi LangGraph. Aplikacja LangGraph umożliwia definiowanie węzłów i krawędzi:

  • Węzeł: gdzie wykonuje się pracę.
  • Edge: definiuje połączenie między węzłami.

Składniki przepływu pracy

W tej aplikacji dwa węzły robocze to:

  • requiresHrResources: określa, czy pytanie dotyczy dokumentacji kadr przy użyciu usługi Azure OpenAI LLM.
  • getAnswer: pobiera odpowiedź. Odpowiedź pochodzi z łańcucha LangChain.js retriever, który używa osadzania dokumentów z usługi Azure AI Search i wysyła je do usługi Azure OpenAI LLM. To jest istota generacji wspomaganej przez pobieranie.

Krawędzie definiują, gdzie należy rozpocząć, zakończyć oraz warunki potrzebne do wywołania węzła getAnswer.

Eksportowanie grafu

Aby uruchomić i debugować graf za pomocą programu LangGraph Studio, wyeksportuj go jako własny obiekt.

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

W metodach addNode, addEdge i addConditionalEdges pierwszy parametr jest nazwą jako ciąg, aby zidentyfikować obiekt w grafie. Drugi parametr to funkcja, która powinna być wywoływana w tym kroku lub nazwa węzła do wywołania.

W przypadku metody addEdge jej nazwa to START ("start" zdefiniowana w pliku ./src/config/nodes.ts) i zawsze wywołuje DECISION_NODE. Ten węzeł jest zdefiniowany przy użyciu dwóch parametrów: pierwszy to jego nazwa, DECISION_NODE, a druga to funkcja o nazwie requiresHrResources.

Typowe funkcje

Ta aplikacja udostępnia typowe funkcje langchain:

  • Zarządzanie stanem:

    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: () => [],
      }),
    });
    
  • Zakończenie trasy:

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

Jedyną trasą niestandardową dla tej aplikacji jest routeRequiresHrResources. Ta trasa służy do określania, czy odpowiedź z węzła requiresHrResources wskazuje, że pytanie użytkownika powinno być kontynuowane do węzła ANSWER_NODE . Ponieważ ta trasa odbiera dane wyjściowe parametru requiresHrResources, znajduje się w tym samym pliku.

Integrowanie zasobów usługi Azure OpenAI

Integracja z usługą Azure OpenAI korzysta z dwóch różnych modeli:

  • Embeddings: Używane do wstawiania dokumentów do magazynu wektorów.
  • LLM: służy do odpowiadania na pytania przez wysyłanie zapytań do magazynu wektorów i generowanie odpowiedzi.

Klient do osadzania i klient LLM służą różnym celom. Nie należy ograniczać ich do pojedynczego modelu ani klienta.

Model osadzania

Klient osadzania jest wymagany za każdym razem, gdy dokumenty są pobierane z magazynu wektorów. Zawiera on konfigurację maxRetries do obsługi błędów przejściowych.

import { AzureOpenAIEmbeddings } from "@langchain/openai";
import { EMBEDDINGS_CONFIG } from "../config/embeddings.js";

export function getEmbeddingClient(): AzureOpenAIEmbeddings {
  return new AzureOpenAIEmbeddings({ ...EMBEDDINGS_CONFIG, maxRetries: 1 });
}

Model LLM

Model LLM służy do odpowiadania na dwa typy pytań:

  • Znaczenie dla kadr: określa, czy pytanie użytkownika jest istotne dla dokumentacji kadrowej.
  • Generowanie odpowiedzi: zapewnia odpowiedź na pytanie użytkownika rozszerzoną o dokumenty z usługi Azure AI Search.

Klient LLM jest tworzony i wywoływany, gdy jest wymagana odpowiedź.

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

Agent LangChain.js używa usługi LLM, aby zdecydować, czy pytanie jest istotne dla dokumentacji kadrowej, czy też przepływ pracy powinien kierować na koniec grafu.

// @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;
};

Funkcja requiresHrResources ustawia komunikat w zaktualizowanym stanie, używając zawartości HR resources required detected. Router routeRequiresHrResources szuka tej zawartości, aby określić, gdzie wysyłać komunikaty.

Integrowanie zasobu usługi Azure AI Search dla magazynu wektorów

Integracja usługi Azure AI Search udostępnia dokumenty magazynu wektorów, dzięki czemu usługa LLM może rozszerzyć odpowiedź dla węzła getAnswer . LangChain.js ponownie zapewnia znaczną część abstrakcji, więc wymagany kod jest minimalny. Funkcje to:

  • getReadOnlyVectorStore: pobiera klienta z kluczem zapytania.
  • getDocsFromVectorStore: znajduje odpowiednie dokumenty na pytanie użytkownika.
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);
}

Kod integracji LangChain.js sprawia, że pobieranie odpowiednich dokumentów z magazynu wektorów jest niezwykle łatwe.

Napisz kod, aby uzyskać odpowiedź z modelu LLM

Teraz, po utworzeniu składników integracji, utwórz funkcję getAnswer, aby pobrać odpowiednie dokumenty z wektorowego repozytorium i wygenerować odpowiedź z użyciem 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],
  };
}

Ta funkcja udostępnia monit z dwoma miejscami na wypełnienie: jednym na pytanie użytkownika i jednym na kontekst. Kontekst to wszystkie odpowiednie dokumenty z bazy danych wektorowych wyszukiwania z użyciem sztucznej inteligencji. Prześlij monit i klienta LLM do funkcji createStuffDocumentsChain, aby utworzyć łańcuch LLM. Przekaż łańcuch LLM do funkcji createRetrievalChain, aby utworzyć łańcuch zawierający monit, odpowiednie dokumenty oraz LLM.

Uruchom łańcuchy za pomocą retrievalChain.invoke oraz pytania użytkownika jako danych wejściowych, aby uzyskać odpowiedź. Zwróć odpowiedź w stanie wiadomości.

Kompilowanie pakietu agenta

  1. Dodaj skrypt do package.json , aby skompilować aplikację TypeScript:

    "build": "tsc",
    
  2. Skompiluj agenta LangChain.js.

    npm run build
    

Opcjonalne — uruchamianie agenta LangChain.js w środowisku lokalnym za pomocą programu LangChain Studio

Opcjonalnie w przypadku programowania lokalnego użyj programu LangChain Studio do pracy z agentem LangChain.js.

  1. Utwórz plik langgraph.json do zdefiniowania grafu.

    {
        "dependencies": [],
        "graphs": {
          "agent": "./src/graph.ts:hr_documents_answer_graph"
        },
        "env": "../.env"
      }
    
  2. Zainstaluj interfejs wiersza polecenia langgraph.

    npm install @langchain/langgraph-cli --save-dev
    
  3. Utwórz skrypt w package.json, aby przekazać plik .env do interfejsu wiersza polecenia LangGraph.

    "studio": "npx @langchain/langgraph-cli dev",
    
  4. Interfejs wiersza polecenia jest uruchamiany w terminalu i otwiera przeglądarkę w programie 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. Wyświetl agenta LangChain.js w programie LangGraph Studio.

    Zrzut ekranu programu LangSmith Studio z załadowanym grafem.

  6. Wybierz pozycję + Wiadomość , aby dodać pytanie użytkownika, a następnie wybierz pozycję Prześlij.

    Pytanie Znaczenie dla dokumentów kadrowych
    Does the NorthWind Health plus plan cover eye exams? To pytanie jest istotne dla kadr i ogólnie rzecz biorąc, że dokumenty kadr, takie jak podręcznik pracownika, podręcznik korzyści i biblioteka ról pracowników powinny być w stanie odpowiedzieć na nie.
    What is included in the NorthWind Health plus plan that is not included in the standard? To pytanie jest istotne dla kadr i ogólnie rzecz biorąc, że dokumenty kadr, takie jak podręcznik pracownika, podręcznik korzyści i biblioteka ról pracowników powinny być w stanie odpowiedzieć na nie.
    How much of my perks + benefit have I spent To pytanie nie jest istotne dla ogólnych, bezosobowych dokumentów kadrowych. To pytanie powinno zostać wysłane do agenta, który ma dostęp do danych pracowników.
  7. Jeśli pytanie jest istotne dla dokumentacji HR, powinno przejść przez DECISION_NODE i dalej do ANSWER_NODE.

    Obejrzyj dane wyjściowe terminalu, aby wyświetlić pytanie do usługi LLM i odpowiedź z usługi LLM.

  8. Jeśli pytanie nie jest istotne dla dokumentacji HR, proces przechodzi bezpośrednio do końca.

Gdy agent LangChain.js podejmuje nieprawidłową decyzję, problem może być:

  • Używany model LLM
  • Liczba dokumentów z wektorowej bazy danych
  • Polecenie używane w węźle decyzyjnym.

Uruchamianie agenta LangChain.js z poziomu aplikacji

Aby wywołać agenta LangChain.js z aplikacji nadrzędnej, takiej jak internetowy interfejs API, należy podać wywołanie agenta 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;
  }
}

Te dwie funkcje to:

  • ask_agent: ta funkcja zwraca stan, aby umożliwić dodanie agenta LangChain.js do przepływu pracy wielu agentów LangChain.
  • get_answer: Ta funkcja zwraca tylko tekst odpowiedzi. Tę funkcję można wywołać z interfejsu API.

Rozwiązywanie problemów

Uprzątnij zasoby

Usuń grupę zasobów, która zawiera zasób usługi Azure AI Search i zasób usługi Azure OpenAI.