Öğretici: Azure AI Search ile LangChain.js aracısı oluşturma

LangChain.js ve Azure hizmetlerini kullanarak akıllı bir İk yardımcısı oluşturun. Bu temsilci, kurgusal NorthWind şirketinde çalışanların şirket belgelerini arayarak insan kaynakları sorularına yanıt bulmasına yardımcı olur.

Uygun belgeleri bulmak için Azure AI Search'i ve doğru yanıtlar oluşturmak için Azure OpenAI'yi kullanacaksınız. LangChain.js çerçevesi, aracı düzenlemesinin karmaşıklığını ele alır ve özel iş gereksinimlerinize odaklanmanızı sağlar.

Öğrenecekleriniz:

  • Azure Geliştirici CLI'sı kullanarak Azure kaynaklarını dağıtma
  • Azure hizmetleriyle tümleşen bir LangChain.js aracısı oluşturma
  • Belge araması için geri getirim artırılmış oluşturma (RAG) uygulayın.
  • Aracınızı yerel olarak ve Azure'da test edip hatalarını ayıklama

Bu öğreticinin sonunda, şirketinizin belgelerini kullanarak insan kaynakları sorularını yanıtlayan çalışan bir REST API'ye sahip olacaksınız.

Mimariye genel bakış

LangChain.js aracısının iş akışını ve soruları yanıtlamak için İK belgelerini kullanmaya yönelik karar dalını gösteren diyagramın ekran görüntüsü.

NorthWind iki veri kaynağına dayanır:

  • tüm çalışanlar için erişilebilir İK belgeleri
  • Hassas çalışanlara ait verileri içeren gizli İK veritabanı.

Bu eğitici, bir çalışanın sorusunun kamuya açık İK belgeleri kullanılarak yanıtlanıp yanıtlanamayacağını belirleyen bir LangChain.js aracısı oluşturmaya odaklanır. Bu durumda, LangChain.js aracısı yanıtı doğrudan sağlar.

Önkoşullar

Bu örneği Codespace veya LangChain.js aracısını oluşturma ve çalıştırma da dahil olmak üzere yerel geliştirme kapsayıcısında kullanmak için aşağıdakilere ihtiyacınız vardır:

Örnek kodu bir geliştirme kapsayıcısı olmadan yerel olarak çalıştırırsanız şunları da yapmanız gerekir:

Azure kaynakları

Aşağıdaki Azure kaynakları gereklidir. Bu makalede, Azure Geliştirici CLI ve Bicep şablonları kullanılarak Azure Doğrulanmış Modüller (AVM) kullanılarak sizin için oluşturulmuştur. Kaynaklar, öğrenme amacıyla hem parolasız hem de anahtar erişimiyle oluşturulur. Bu öğreticide parolasız kimlik doğrulaması için yerel geliştirici hesabınız kullanılır:

Bağlantıları olan Container Apps, OpenAI, AI Search, Container Registry ve Yönetilen Kimlik bileşenleriyle RAG uygulamasını gösteren Azure mimari diyagramı.

Ajan mimarisi

LangChain.js çerçevesi, LangGraph olarak akıllı aracılar oluşturmaya yönelik bir karar akışı sağlar. Bu öğreticide, İnsan Kaynakları ile ilgili soruları yanıtlamak için Azure AI Search ve Azure OpenAI ile entegre olan bir LangChain.js aracısı oluşturacaksınız. Aracının mimarisi şu amaçla tasarlanmıştır:

  • Bir sorunun tüm çalışanların kullanımına sunulan genel İk belgeleriyle ilgili olup olmadığını belirleyin.
  • Kullanıcı sorgusuna göre Azure AI Search'ten ilgili belgeleri alın.
  • Alınan belgelere ve LLM modeline göre bir yanıt oluşturmak için Azure OpenAI kullanın.

Temel Bileşenler:

  • Graf yapısı: LangChain.js aracısı bir grafik olarak temsil edilir ve burada:

    • Düğümler karar alma veya veri alma gibi belirli görevleri gerçekleştirir.
    • Kenarlar düğümler arasındaki akışı tanımlar ve işlem sırasını belirler.
  • Azure AI Search tümleştirmesi:

    • Vektör oluşturmak için ekleme modeli kullanır.
    • İK belgelerini (*.md, *.pdf) vektör deposuna ekler. Belgeler şunlardır:
      • Şirket bilgileri
      • Çalışan el kitabı
      • Avantajlar el kitabı
      • Çalışan Rol Kitaplığı
    • Kullanıcı istemine göre ilgili belgeleri alır.
  • Azure OpenAI tümleştirmesi:
    • Aşağıdakiler için büyük bir dil modeli kullanır:
      • Bir sorunun kişisel olmayan İK belgelerinden yanıtlanabilir olup olmadığını belirler.
      • Belgelerden ve kullanıcı sorunundan bağlamı kullanarak istemle yanıt oluşturur.

Aşağıdaki tabloda, genel İnsan kaynakları belgelerinden ilgili ve yanıtlanabilir olmayan kullanıcı soruları örnekleri verilmiştir:

Soru Alakalı Explanation
Does the NorthWind Health Plus plan cover eye exams? Yes Çalışan el kitabı gibi İK belgeleri yanıt sağlamalıdır.
How much of my perks + benefits have I spent? Hayı Bu soru, bu aracının kapsamı dışında olan gizli çalışan verilerine erişim gerektirir.

LangChain.js çerçevesini kullanarak, genellikle aracılar ve Azure hizmet entegrasyonu için gerekli olan tekrarlayan kodun büyük bir kısmından kurtulur ve iş ihtiyaçlarınıza odaklanmanızı sağlarsınız.

Örnek kod deposunu kopyalama

Yeni bir dizinde örnek kod deposunu kopyalayın ve yeni dizine geçin:

git clone https://github.com/Azure-Samples/azure-typescript-langchainjs.git
cd azure-typescript-langchainjs

Bu örnek, güvenli Azure kaynakları oluşturmak, Azure AI Search ve Azure OpenAI ile LangChain.js aracısını oluşturmak ve aracıyı bir Node.js Fastify API sunucusundan kullanmak için ihtiyacınız olan kodu sağlar.

Azure CLI ve Azure Geliştirici CLI'sinde kimlik doğrulaması

Azure Geliştirici CLI'sı ile Azure'da oturum açın, Azure kaynaklarını oluşturun ve kaynak kodu dağıtın. Dağıtım işlemi hem Azure CLI hem de Azure Geliştirici CLI kullandığından Azure CLI'da oturum açın, ardından Azure Geliştirici CLI'sini Azure CLI'dan kimlik doğrulamanızı kullanacak şekilde yapılandırın:

az login
azd config set auth.useAzCliAuth true

Azure Geliştirici CLI ile kaynak oluşturma ve kod dağıtma

Komutunu çalıştırarak dağıtım işlemine azd up başlayın:

azd up

azd up Komut sırasında şu soruları yanıtlayın:

  • Yeni ortam adı: gibi langchain-agentbenzersiz bir ortam adı girin. Bu ortam adı, Azure kaynak grubunun bir parçası olarak kullanılır.
  • Bir Azure Aboneliği seçin: Kaynakların oluşturulduğu aboneliği seçin.
  • Bir bölge seçin: örneğin eastus2.

Dağıtım yaklaşık 10-15 dakika sürer. Azure Geliştirici CLI'sı, dosyada azure.yaml tanımlanan aşamaları ve kancaları kullanarak işlemi düzenler:

Sağlama aşaması (eşdeğeri azd provision):

  • içinde infra/main.biceptanımlanan Azure kaynaklarını oluşturur:
  • Sağlama sonrası kanca: Azure AI Search dizininin northwind zaten mevcut olup olmadığını kontrol eder
    • Dizin yoksa: LangChain.js PDF yükleyici ve ekleme istemcisi kullanarak İnsan Kaynakları belgelerini karşıya yüklemek için npm install ve npm run load_data komutlarını çalıştırır.
    • Dizin varsa: Yinelenenleri önlemek için veri yükleme işlemini atlar (dizini silerek veya çalıştırarak npm run load_datael ile yeniden yükleyebilirsiniz) Dağıtma aşaması (eşdeğeri azd deploy):
  • Ön dağıtım kancası: Fastify API sunucusu için Docker görüntüsünü derleyip Azure Container Registry'ye gönderir
  • Kapsayıcılı API sunucusunu Azure Container Apps'e dağıtır

Dağıtım tamamlandığında ortam değişkenleri ve kaynak bilgileri depo kökündeki dosyaya .env kaydedilir. Kaynakları Azure portalında görüntüleyebilirsiniz.

Kaynaklar, öğrenme amacıyla hem parolasız hem de anahtar erişimiyle oluşturulur. Bu giriş niteliğindeki öğretici, parolasız kimlik doğrulaması için yerel geliştirici hesabınızı kullanır. Üretim uygulamaları için yalnızca yönetilen kimliklerle parolasız kimlik doğrulaması kullanın. Parolasız kimlik doğrulaması hakkında daha fazla bilgi edinin.

Örnek kodu yerel olarak kullanma

Artık Azure kaynakları oluşturulduğuna göre, LangChain.js aracısını yerel olarak çalıştırabilirsiniz.

Bağımlılıkları yükleme

  1. Bu proje için Node.js paketlerini yükleyin.

    npm install 
    

    Bu komut, aşağıdakiler dahil olmak üzere dizindeki iki package.json dosyada packages-v1 tanımlanan bağımlılıkları yükler:

  2. İki paketi derleyin: API sunucusu ve yapay zeka aracısı.

    npm run build
    

    Bu komut, API sunucusunun yapay zeka aracısını çağırabilmesi için iki paket arasında bir bağlantı oluşturur.

API sunucusunu yerel olarak çalıştırma

Azure Geliştirici CLI'sı gerekli Azure kaynaklarını oluşturdu ve kök .env dosyada ortam değişkenlerini yapılandırdı. Bu yapılandırma, verilerin vektör deposuna yüklenmesi için bir dağıtım sonrası kancası içeriyor. Artık LangChain.js aracısını barındıran Fastify API sunucusunu çalıştırabilirsiniz. Fastify API sunucusunu başlatın.

npm run dev

Sunucu 3000 numaralı bağlantı noktasında başlatılır ve dinler. Web tarayıcınızda [http://localhost:3000] adresine giderek sunucuyu test edebilirsiniz. Sunucunun çalıştığını belirten bir karşılama iletisi görmeniz gerekir.

Soru sormak için API'yi kullanma

REST İstemcisi gibi bir araç kullanabilir veya curl sorunuzu içeren bir JSON gövdesiyle uç noktaya POST isteği /ask gönderebilirsiniz.

Dizinde packages-v1/server-api/http rest istemci sorguları bulunur.

Örnek curl kullanımı:

curl -X POST http://localhost:3000/answer -H "Content-Type: application/json" -d "{\"question\": \"Does the NorthWind Health Plus plan cover eye exams?\"}"

LangChain.js aracısından yanıtı içeren bir JSON yanıtı almanız gerekir.

{
  "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."
}

Dizinde packages-v1/server-api/http birkaç örnek soru bulunabilir. Dosyaları hızlı bir şekilde test etmek için VISUAL Studio Code'daREST İstemcisi ile açın.

Uygulama kodunu anlama

Bu bölümde, LangChain.js aracısının Azure hizmetleriyle nasıl tümleştir olduğu açıklanmaktadır. Deponun uygulaması, iki ana paket içeren bir npm çalışma alanı olarak düzenlenir:

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

Önemli mimari kararlar:

  • Monorepo yapısı: npm çalışma alanları paylaşılan bağımlılıklara ve bağlı paketlere izin verir
  • Endişelerin ayrılması: Aracı mantığı (langgraph-agent) API sunucusundan (server-api) bağımsızdır
  • Merkezi kimlik doğrulaması: içindeki ./langgraph-agent/src/azure dosyalar hem anahtar tabanlı hem de parolasız kimlik doğrulamasını ve Azure hizmet tümleştirmeyi işler

Azure Hizmetleri'ne kimlik doğrulaması

Uygulama, ortam değişkeni tarafından SET_PASSWORDLESS denetlenen anahtar tabanlı ve parolasız kimlik doğrulama yöntemlerini destekler. Azure Kimlik kitaplığındakiDefaultAzureCredential API,parolasız kimlik doğrulaması için kullanılır ve uygulamanın yerel geliştirme ve Azure ortamlarında sorunsuz bir şekilde çalışmasını sağlar. Bu kimlik doğrulamasını aşağıdaki kod parçacığında görebilirsiniz:

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

Azure OpenAI'ye erişmek için LangChain.js veya OpenAI kitaplığı gibi üçüncü taraf kitaplıkları kullanırken, kimlik bilgisi nesnesini doğrudan geçirmek yerine bir belirteç sağlayıcısı işlevine ihtiyacınız vardır. getBearerTokenProvider Azure Kimlik kitaplığındaki işlev, belirli bir Azure kaynak kapsamı (örneğin, "https://cognitiveservices.azure.com/.default") için OAuth 2.0 taşıyıcı belirteçlerini otomatik olarak getiren ve yenileyen bir belirteç sağlayıcısı oluşturarak bu sorunu çözer. Kurulum sırasında kapsamı bir kez yapılandırdığınızda belirteç sağlayıcısı tüm belirteç yönetimini otomatik olarak işler. Bu yaklaşım, yönetilen kimlik ve Azure CLI kimlik bilgileri de dahil olmak üzere tüm Azure Kimlik kitaplığı kimlik bilgileriyle çalışır. Azure SDK kitaplıkları DefaultAzureCredential doğrudan kabul ederken, LangChain.js gibi üçüncü taraf kitaplıklar, kimlik doğrulama boşluğunu kapatmak için bu belirteç sağlayıcı modelini gerektirir.

Azure AI Search entegrasyonu

Azure AI Search kaynağı belge eklemelerini depolar ve ilgili içerik için anlamsal aramayı etkinleştirir. Uygulama, dizin şemasını tanımlamanıza gerek kalmadan vektör deposunu yönetmek için LangChain'leri AzureAISearchVectorStore kullanır.

Vektör deposu hem yönetici (yazma) hem de sorgu (okuma) işlemleri için yapılandırmayla oluşturulur, böylece belge yükleme ve sorgulama farklı yapılandırmaları kullanabilir. Bu, ister anahtar kullanıyor olun ister yönetilen kimliklerle parolasız kimlik doğrulama yapın, önemlidir.

Azure Geliştirici CLI dağıtımı, belgeleri LangChain.js PDF yükleyici ve ekleme istemcisiyle vektör deposuna yükleyen bir dağıtım sonrası kancası içerir. Bu dağıtım sonrası işlev, Azure AI Search kaynağı oluşturulduktan sonra azd up komutunun son adımıdır. Belge yükleme betiği, hizmet hızı sınırlarını işlemek için toplu işlem ve yeniden deneme mantığını kullanır.

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

Azure Geliştirici CLI'sı tarafından oluşturulan kök .env dosyayı kullanın, Azure AI Search kaynağında kimlik doğrulaması yapabilir ve AzureAISearchVectorStore istemcisini oluşturabilirsiniz:

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;

Sorguladığınızda, vektör deposu kullanıcının sorgusunu eklemeye dönüştürür, benzer vektör gösterimlerine sahip belgeleri arar ve en uygun öbekleri döndürür.

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

Vektör deposu LangChain.jsüzerine oluşturulduğundan, vektör deposuyla doğrudan etkileşim kurmanın karmaşıklığını soyutlar. LangChain.js vektör deposu arabirimini öğrendikte, gelecekte diğer vektör deposu uygulamalarına kolayca geçebilirsiniz.

Azure OpenAI tümleştirmesi

Uygulama hem eklemeler hem de büyük dil modeli (LLM) özellikleri için Azure OpenAI kullanır. AzureOpenAIEmbeddingsLangChain.js sınıfı, belgeler ve sorgular için eklemeler oluşturmak için kullanılır. Eklemeler istemcisini oluşturduktan sonra LangChain.js eklemeleri oluşturmak için bunu kullanır.

Eklemeler için Azure OpenAI tümleştirmesi

Azure OpenAI kaynağında kimlik doğrulaması yapmak ve .env istemcisini oluşturmak için Azure Geliştirici CLI'sı tarafından oluşturulan kök dosyayı kullanın:

const shared = {
  azureOpenAIApiInstanceName: instance,
  azureOpenAIApiEmbeddingsDeploymentName: model,
  azureOpenAIApiVersion: apiVersion,
  azureOpenAIBasePath,
  dimensions: 1536, // for text-embedding-3-small
  batchSize: EMBEDDING_BATCH_SIZE,
  maxRetries: 7,
  timeout: 60000,
};

export const EMBEDDINGS_KEY_CONFIG = {
  azureOpenAIApiKey: key,
  ...shared,
};

export const EMBEDDINGS_CONFIG_PASSWORDLESS = {
  azureADTokenProvider: azureADTokenProvider_OpenAI,
  ...shared,
};

export const EMBEDDINGS_CONFIG =
  process.env.SET_PASSWORDLESS == "true"
    ? EMBEDDINGS_CONFIG_PASSWORDLESS
    : EMBEDDINGS_KEY_CONFIG;
export function getEmbeddingClient(): AzureOpenAIEmbeddings {
  return new AzureOpenAIEmbeddings({ ...EMBEDDINGS_CONFIG });
}

LLM için Azure OpenAI tümleştirmesi

Azure OpenAI kaynağında kimlik doğrulaması yapmak ve .env istemcisini oluşturmak için Azure Geliştirici CLI'sı tarafından oluşturulan kök dosyayı kullanın:

const shared = {
  azureOpenAIApiInstanceName: instance,
  azureOpenAIApiDeploymentName: model,
  azureOpenAIApiVersion: apiVersion,
  azureOpenAIBasePath,
  maxTokens: maxTokens ? parseInt(maxTokens, 10) : 1000,
  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;

Uygulama, Azure OpenAI modelleriyle etkileşime geçmek için LangChain.js AzureChatOpenAI sınıfını kullanır@langchain/openai.

export const callChatCompletionModel = async (
  state: typeof StateAnnotation.State,
  _config: RunnableConfig,
): Promise<typeof StateAnnotation.Update> => {
  const llm = new AzureChatOpenAI({
    ...LLM_CONFIG,
  });

  const completion = await llm.invoke(state.messages);
  completion;

  return {
    messages: [
      ...state.messages,
      {
        role: "assistant",
        content: completion.content,
      },
    ],
  };
};

LangGraph aracısı iş akışı

LangGraph'i kullanan ajan, bir sorunun İK belgeleri kullanılarak yanıtlanıp yanıtlanamayacağını belirleyen bir karar iş akışı tanımlar.

Graf yapısı:

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

İş akışı aşağıdaki adımlardan oluşur:

  • Başlangıç: Kullanıcı bir soru gönderir.
  • requires_hr_documents düğümü: LLM, sorunun genel İK belgelerinden yanıtlanabilir olup olmadığını belirler.
  • Koşullu yönlendirme:
    • Eğer evet, o zaman get_answer düğümüne geri devam eder.
    • Hayır ise, sorunun personel İK verileri gerektirdiğini belirten bir mesaj verir.
  • get_answer düğümü: Belgeleri alır ve yanıt oluşturur.
  • Bitiş: Kullanıcıya yanıt döndürür.

Tüm İK soruları genel belgelerden yanıtlanamayacağı için bu uygunluk kontrolü önemlidir. "Ne kadar PTO'm var?" gibi kişisel sorular, tek tek çalışan verilerini içeren çalışan veritabanlarına erişim gerektirir. Temsilci, önce ilgi düzeyini denetleyerek erişimi olmayan kişisel bilgilere ihtiyaç duyan soruların halüsinasyona neden olan yanıtlarından kaçınıyor.

Sorunun İK belgelerini gerektirip gerektirmediğine karar verin

Düğüm, requires_hr_documents kullanıcının sorusunun genel insan kaynakları belgeleri ile yanıtlanıp yanıtlanamayacağını belirlemek için bir LLM kullanır. Modelin, sorunun ilgisine göre YES veya NO ile yanıt vermesini isteyen bir komut şablonu kullanır. Yanıtı, iş akışı boyunca geçirilebilen yapılandırılmış bir iletide döndürür. Sonraki düğüm, iş akışını END veya ANSWER_NODE'e yönlendirmek için bu yanıtı kullanır.

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

Gerekli İK belgelerini alma

Sorunun İK belgeleri gerektirdiği belirlendikten sonra iş akışı, ilgili belgeleri vektör deposundan alır, bunları istem getAnswer içine ekler ve istemin tamamını LLM'ye iletir.

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

İlgili belgeler bulunmazsa, temsilci insan kaynakları belgelerinde bir yanıt bulamadığını belirten bir mesaj iletir.

Sorun giderme

Yordamla ilgili sorunlar için örnek kod deposunda bir sorun oluşturun

Kaynakları temizle

Azure AI Search kaynağını ve Azure OpenAI kaynağını barındıran kaynak grubunu silebilir veya Bu öğretici tarafından oluşturulan tüm kaynakları hemen silmek için Azure Geliştirici CLI'sini kullanabilirsiniz.

azd down --purge