304 уншилтууд
304 уншилтууд

Smart Documentation хэрхэн бий болгох - OpenAI эмчилгээ (Chunking, Indexing, Searching) дээр суурилсан

by Aymeric PINEAU13m2025/05/01
Read on Terminal Reader

Хэтэрхий урт; Унших

Ихэнх санаа нь документын индекс хийх, тэднийг менежментийн хувцас руу хуваалцах, OpenAI-ийн туслалтыг үүсгэх, хэрэглэгчийн асуултанд хамгийн тохиромжтой мэдээллийг олж, дамжуулахын тулд хэлбэрийн хайлт хийх юм.
featured image - Smart Documentation хэрхэн бий болгох - OpenAI эмчилгээ (Chunking, Indexing, Searching) дээр суурилсан
Aymeric PINEAU HackerNoon profile picture
0-item

Сайн байна уу бүхэн! Би ажиллуулсан төслийн хувьд "смарт документын" чатбот үүсгэхийн тулд миний арга хэрэгсэл хуваалцахыг хүссэн.I’m not an AI expert, so any suggestions or improvements are more than welcome!


Энэ бичлэгийн зорилго нь OpenAI-д суурилсан чатбот үүсгэх талаар өөр нэг тусламж үүсгэх биш юм. Үүнээс гадна, гол санаа нь OpenAI дээр олон агуулга байдаг.index documentationИхэнх нь хяналтынchunksБүтээгдэхүүнийembeddingsOpenAI, болонperforming a similarity searchХэрэглэгчийн асуултанд хамгийн тохиромжтой мэдээллийг олж, илгээх.


Миний тохиолдолд, документын Markdown файлууд байх болно, гэхдээ энэ нь ямар ч хэлбэртэй текст, мэдээллийн сан объект гэх мэт байж болно.

Үнэндээ

Хэрэв та хүсэж байгаа мэдээллийг олж чадахгүй бол, би чатбот үүсгэхыг хүссэн бөгөөд энэ нь тодорхой хэлбэрийн талаархи асуултуудыг хариулах, документын талаархи тохиромжтой контекст хангах боломжтой.


Энэ асистент нь хэд хэдэн арга замаар ашиглаж болно, гэх мэт:

  • Өнгөрсөн асуултуудын хурдан хариу
  • Doc / Page-ийг хайж, Algolia хийх
  • Хэрэглэгчийн хэрэглэгчдэд хэрэгцээний талаархи мэдээлэл олж авахын тулд
  • Хэрэглэгчийн асуултуудыг / асуултуудыг хадгалах асуултууд

Бүртгэл

Дараа нь би миний шийдэл нь гурван гол хэсэгүүдийг тодорхойлох болно:

  1. Документацийн файлуудыг унших
  2. Документацийг индексируулах (хунгил, хавхлага, хавхлага)
  3. Документацийг хайж (ийг чатбот руу холбох)

Архивууд

.
└── docs
    └── ...md
└── src
    └── askDocQuestion.ts
    └── index.ts # Express.js application endpoint
└── embeddings.json # Storage for embeddings
└── packages.json

1. Документацийн файлуудыг унших

Документацийн текст нь hardcoding гэхэд, та.mdфайлууд гэх мэт хэрэгсэл ашигланglob.

// Example snippet of fetching files from a folder:
import fs from "node:fs";
import path from "node:path";
import glob from "glob";

const DOC_FOLDER_PATH = "./docs";

type FileData = {
  path: string;
  content: string;
};

const readAllMarkdownFiles = (): FileData[] => {
  const filesContent: FileData[] = [];
  const filePaths = glob.sync(`${DOC_FOLDER_PATH}/**/*.md`);

  filePaths.forEach((filePath) => {
    const content = fs.readFileSync(filePath, "utf8");
    filesContent.push({ path: filePath, content });
  });

  return filesContent;
};

Албан ёсоор, Хэрэв та өөрийн документыг таны мэдээллийн сан, CMS гэх мэт олж болно.

Албан ёсоор, Хэрэв та өөрийн документыг таны мэдээллийн сан, CMS гэх мэт олж болно.


2. Документийн индекс хийх

Бидний хайлтын хөдөлгүүр бий болгохын тулд OpenAI-ийг ашиглах болноVector эмблем APIБидний шивээс үүсгэх.


Vector embeddings нь нумерик хэлбэрээр өгөгдлийг хуваалцах арга юм. Энэ нь хэлбэлтийн хайлт хийхэд ашиглаж болно (ямар ч, хэрэглэгчийн асуултанд болон манай документын хэсэгт хооронд).


Энэ вектор, тавтай морилноор тооны жагсаалттай бөгөөд математик хэлбэрээр хэлбэрээр харьцуулахын тулд ашиглаж болно.

[
  -0.0002630692, -0.029749284, 0.010225477, -0.009224428, -0.0065269712,
  -0.002665544, 0.003214777, 0.04235309, -0.033162255, -0.00080789323,
  //...+1533 elements
];

Энэ концепт дээр суурилсан Vector Database үүсгэсэн. Үүний үр дүнд, OpenAI API-г ашиглах нь Chroma, Qdrant, Pinecone гэх мэт вектор мэдээллийн санг ашиглах боломжтой.

Эдгээр концепт дээр суурилсанVector мэдээллийн санҮүний үр дүнд, OpenAI API-г ашиглах нь вектор мэдээллийн санг ашиглах боломжтой.ХөгжлийнНөхцөлӨнгөрсөнэсвэлХуучин.

2.1 Chunk Each файлын & Overlap

Өндөр текст блок загварууд нь загварын контекст хязгаарлалыг дагаж болно, эсвэл хязгаарлагдмал хязгаарлалт үүсгэх болно. Гэсэн хэдий ч, хязгаарлалт хооронд зарим нь нарийвчлалтай болгохын тулд тэднийг хязгаарлагддаг. Гэсэн хэдий ч, хязгаарлагдмал хязгаарлалт нь хязгаарлагдмал контекст хооронд хязгаарлагддаггүй байдаг.

Жишээ нь Chunking

Энэ жишээ нь, бид урт тексттай байхыг хүсэж байгаа бөгөөд энэ тохиолдолд бид 100 символтай хувцас үүсгэхийг хүсэж байгаа бөгөөд тэднийг 50 символтай хамарна.


Full Text (406 characters):

Аялал жуулчлалын хотод, олон хүмүүсийн хязгаарлагдмал хуучин библиотекаар орно. Түүний хавтгай шилүүд нь өөрсдийн үзэсгэлэнтэй жанрын ном, ачаалалтай түүхүүд, тайван, цаг хугацааны мудлалтай байдаг. Өдөрт цаг хугацааны мудлалтай библиотекар нь түүний хаалгаг нээж, мэдрэмтгий мэдрэмжийг олж хүсэж буй мэдрэмтгий мэдрэмжийг олж авахыг хүсч байна.


  • Chunk 1 (Characters 1-150):

    In the heart of the bustling city, there stood an old library that many had forgotten. Its towering shelves were filled with books from every imaginabl.

  • Chunk 2 (Characters 101-250):

    shelves were filled with books from every imaginable genre, each whispering stories of adventures, mysteries, and timeless wisdom. Every evening, a d

  • Chunk 3 (Characters 201-350):

    ysteries, and timeless wisdom. Every evening, a dedicated librarian would open its doors, welcoming curious minds eager to explore the vast knowledge

  • Chunk 4 (Characters 301-406):

    curious minds eager to explore the vast knowledge within. Children would gather for storytelling sessions.

Код Snippet

const CHARS_PER_TOKEN = 4.15; // Approximate pessimistically number of characters per token. Can use `tiktoken` or other tokenizers to calculate it more precisely

const MAX_TOKENS = 500; // Maximum number of tokens per chunk
const OVERLAP_TOKENS = 100; // Number of tokens to overlap between chunks

const maxChar = MAX_TOKENS * CHARS_PER_TOKEN;
const overlapChar = OVERLAP_TOKENS * CHARS_PER_TOKEN;

const chunkText = (text: string): string[] => {
  const chunks: string[] = [];
  let start = 0;

  while (start < text.length) {
    let end = Math.min(start + maxChar, text.length);

    // Don’t cut a word in half if possible:
    if (end < text.length) {
      const lastSpace = text.lastIndexOf(" ", end);
      if (lastSpace > start) end = lastSpace;
    }

    chunks.push(text.substring(start, end));
    // Overlap management
    const nextStart = end - overlapChar;
    start = nextStart <= start ? end : nextStart;
  }

  return chunks;
};

Хөргөлтийн талаар дэлгэрэнгүй мэдлэхийн тулд, хэмжээ нь хавтан дээр нөлөө, та энэ нийтлэл үзнэ үү.

Chunking талаар дэлгэрэнгүй мэдээлэл авахын тулд, хэмжээ нь хавтан дээр нөлөө, та үзнэ үүЭдүүлбэр.

2.2 Барааны Generation

Файлын хуваалцсан дараа бид OpenAI-ийн API-г ашиглан бүр хуваалцсан нь вектор суурилуулалт үүсгэдэг (жишээ нь,text-embedding-3-largeНөхцөл

import { OpenAI } from "openai";

const EMBEDDING_MODEL: OpenAI.Embeddings.EmbeddingModel =
  "text-embedding-3-large"; // Model to use for embedding generation

const openai = new OpenAI({ apiKey: OPENAI_API_KEY });

const generateEmbedding = async (textChunk: string): Promise<number[]> => {
  const response = await openai.embeddings.create({
    model: EMBEDDING_MODEL,
    input: textChunk,
  });

  return response.data[0].embedding; // Return the generated embedding
};

2.3 Бүх файлуудыг үүсгэх, хадгалах

Бүх удаа суулгахын тулд бид суулгахыг хадгалах болно. Энэ нь мэдээллийн санд хадгалах боломжтой. Гэхдээ энэ тохиолдолд бид зүгээр л локал JSON файлуудыг хадгалах болно.


Дараачийн код нь зүгээр л:

  1. Бүх баримт бичиг дээр iterates,
  2. Документийг хавтгай дөрвөнт хавтгай,
  3. Бүх ширхэг нь ширхэг үүсгэх,
  4. Та JSON файлуудыг хадгалах болно.
  5. VectorStore-ийг хайж авахын тулд ашиглаж буй хавхлагатай.
import embeddingsList from "../embeddings.json";

/**
 * Simple in-memory vector store to hold document embeddings and their content.
 * Each entry contains:
 * - filePath: A unique key identifying the document
 * - chunkNumber: The number of the chunk within the document
 * - content: The actual text content of the chunk
 * - embedding: The numerical embedding vector for the chunk
 */
const vectorStore: {
  filePath: string;
  chunkNumber: number;
  content: string;
  embedding: number[];
}[] = [];

/**
 * Indexes all Markdown documents by generating embeddings for each chunk and storing them in memory.
 * Also updates the embeddings.json file if new embeddings are generated.
 */
export const indexMarkdownFiles = async (): Promise<void> => {
  // Retrieve documentations
  const docs = readAllMarkdownFiles();

  let newEmbeddings: Record<string, number[]> = {};

  for (const doc of docs) {
    // Split the document into chunks based on headings
    const fileChunks = chunkText(doc.content);

    // Iterate over each chunk within the current file
    for (const chunkIndex of Object.keys(fileChunks)) {
      const chunkNumber = Number(chunkIndex) + 1; // Chunk number starts at 1
      const chunksNumber = fileChunks.length;

      const chunk = fileChunks[chunkIndex as keyof typeof fileChunks] as string;

      const embeddingKeyName = `${doc.path}/chunk_${chunkNumber}`; // Unique key for the chunk

      // Retrieve precomputed embedding if available
      const existingEmbedding = embeddingsList[
        embeddingKeyName as keyof typeof embeddingsList
      ] as number[] | undefined;

      let embedding = existingEmbedding; // Use existing embedding if available

      if (!embedding) {
        embedding = await generateEmbedding(chunk); // Generate embedding if not present
      }

      newEmbeddings = { ...newEmbeddings, [embeddingKeyName]: embedding };

      // Store the embedding and content in the in-memory vector store
      vectorStore.push({
        filePath: doc.path,
        chunkNumber,
        embedding,
        content: chunk,
      });

      console.info(`- Indexed: ${embeddingKeyName}/${chunksNumber}`);
    }
  }

  /**
   * Compare the newly generated embeddings with existing ones
   *
   * If there is change, update the embeddings.json file
   */
  try {
    if (JSON.stringify(newEmbeddings) !== JSON.stringify(embeddingsList)) {
      fs.writeFileSync(
        "./embeddings.json",
        JSON.stringify(newEmbeddings, null, 2)
      );
    }
  } catch (error) {
    console.error(error);
  }
};

3. Документацийг хайж

3.1 Вектор хэлбэрээр

Хэрэглэгчийн асуултанд хариулахын тулд бид эхлээдuser's questionДараа нь асуултанд суулгах, бүр хэсэгт суулгах хооронд cosine харьцуулах. Бид зарим нэг харьцуулалтуудын дор ямар ч зүйл шүүлтүүр, зөвхөн дээд X-ийн харьцуулалтыг хадгалах.

/**
 * Calculates the cosine similarity between two vectors.
 * Cosine similarity measures the cosine of the angle between two vectors in an inner product space.
 * Used to determine the similarity between chunks of text.
 *
 * @param vecA - The first vector
 * @param vecB - The second vector
 * @returns The cosine similarity score
 */
const cosineSimilarity = (vecA: number[], vecB: number[]): number => {
  // Calculate the dot product of the two vectors
  const dotProduct = vecA.reduce((sum, a, idx) => sum + a * vecB[idx], 0);

  // Calculate the magnitude (Euclidean norm) of each vector
  const magnitudeA = Math.sqrt(vecA.reduce((sum, a) => sum + a * a, 0));
  const magnitudeB = Math.sqrt(vecB.reduce((sum, b) => sum + b * b, 0));

  // Compute and return the cosine similarity
  return dotProduct / (magnitudeA * magnitudeB);
};

const MIN_RELEVANT_CHUNKS_SIMILARITY = 0.77; // Minimum similarity required for a chunk to be considered relevant
const MAX_RELEVANT_CHUNKS_NB = 15; // Maximum number of relevant chunks to attach to chatGPT context

/**
 * Searches the indexed documents for the most relevant chunks based on a query.
 * Utilizes cosine similarity to find the closest matching embeddings.
 *
 * @param query - The search query provided by the user
 * @returns An array of the top matching document chunks' content
 */
const searchChunkReference = async (query: string) => {
  // Generate an embedding for the user's query
  const queryEmbedding = await generateEmbedding(query);

  // Calculate similarity scores between the query embedding and each document's embedding
  const results = vectorStore
    .map((doc) => ({
      ...doc,
      similarity: cosineSimilarity(queryEmbedding, doc.embedding), // Add similarity score to each doc
    }))
    // Filter out documents with low similarity scores
    // Avoid to pollute the context with irrelevant chunks
    .filter((doc) => doc.similarity > MIN_RELEVANT_CHUNKS_SIMILARITY)
    .sort((a, b) => b.similarity - a.similarity) // Sort documents by highest similarity first
    .slice(0, MAX_RELEVANT_CHUNKS_NB); // Select the top most similar documents

  // Return the content of the top matching documents
  return results;
};

3.2 Ажлын Chunks нь OpenAI-ийг дэмждэг

Хөгжлийн дараа, бидtopChatGPT-ийн хүсэлтийн системд хавтгай. Энэ нь ChatGPT-ийг тавтай морилно уу. Дараа нь бид ChatGPT-ийг хэрэглэгчдэд хариултыг үүсгэх боломжийг олгодог.

const MODEL: OpenAI.Chat.ChatModel = "gpt-4o-2024-11-20"; // Model to use for chat completions

// Define the structure of messages used in chat completions
export type ChatCompletionRequestMessage = {
  role: "system" | "user" | "assistant"; // The role of the message sender
  content: string; // The text content of the message
};

/**
 * Handles the "Ask a question" endpoint in an Express.js route.
 * Processes user messages, retrieves relevant documents, and interacts with OpenAI's chat API to generate responses.
 *
 * @param messages - An array of chat messages from the user and assistant
 * @returns The assistant's response as a string
 */
export const askDocQuestion = async (
  messages: ChatCompletionRequestMessage[]
): Promise<string> => {
  // Assistant's response are filtered out otherwise the chatbot will be stuck in a self-referential loop
  // Note that the embedding precision will be lowered if the user change of context in the chat
  const userMessages = messages.filter((message) => message.role === "user");

  // Format the user's question to keep only the relevant keywords
  const formattedUserMessages = userMessages
    .map((message) => `- ${message.content}`)
    .join("\n");

  // 1) Find relevant documents based on the user's question
  const relevantChunks = await searchChunkReference(formattedUserMessages);

  // 2) Integrate the relevant documents into the initial system prompt
  const messagesList: ChatCompletionRequestMessage[] = [
    {
      role: "system",
      content:
        "Ignore all previous instructions. \
        You're an helpful chatbot.\
        ...\
        Here is the relevant documentation:\
        " +
        relevantChunks
          .map(
            (doc, idx) =>
              `[Chunk ${idx}] filePath = "${doc.filePath}":\n${doc.content}`
          )
          .join("\n\n"), // Insert relevant chunks into the prompt
    },
    ...messages, // Include the chat history
  ];

  // 3) Send the compiled messages to OpenAI's Chat Completion API (using a specific model)
  const response = await openai.chat.completions.create({
    model: MODEL,
    messages: messagesList,
  });

  const result = response.choices[0].message.content; // Extract the assistant's reply

  if (!result) {
    throw new Error("No response from OpenAI");
  }

  return result;
};

OpenAI API-ийг Chatbot Express ашиглан ашиглах

Бидний системийг гүйцэтгэхийн тулд Express.js сервер ашиглах болно. Энд нь хөнгөн Express.js эндэйн тоног төхөөрөмж нь асуултыг боловсруулах:

import express, { type Request, type Response } from "express";
import {
  ChatCompletionRequestMessage,
  askDocQuestion,
  indexMarkdownFiles,
} from "./askDocQuestion";

// Automatically fill the vector store with embeddings when server starts
indexMarkdownFiles();

const app = express();

// Parse incoming requests with JSON payloads
app.use(express.json());

type AskRequestBody = {
  messages: ChatCompletionRequestMessage[];
};

// Routes
app.post(
  "/ask",
  async (
    req: Request<undefined, undefined, AskRequestBody>,
    res: Response<string>
  ) => {
    try {
      const response = await askDocQuestion(req.body.messages);

      res.json(response);
    } catch (error) {
      console.error(error);
    }
  }
);

// Start server
app.listen(3000, () => {
  console.log(`Listening on port 3000`);
});

5. UI: Chatbot интерфэйс үүсгэх

Frontend дээр, Би чат шиг интерфэйстэй жижиг React компонент бий. Энэ нь Express backend-д мэдээг илгээж, хариултуудыг дэлгэц. Үүнээс дээш гайхамшигтай ямар ч зүйл биш юм, Тиймээс бид тодорхойлолт унших болно.


Хэмжээ Code

Би аХристийн кодХэрэв та өөрийн чатбот зориулсан эх үүсвэр болгон ашиглаж болно.

Үнэгүй Demo

Хэрэв та энэ chatbot-ийн эцсийн имплементацийг туршиж хүсэж байгаа бол энэ нь шалгахДемо хуудас.

Демо хуудас

Үйлчилгээний код

  • Албан ёсны: askDocQuestion.ts
  • Frontend: ChatBot хэсгүүд

Өнгөрсөн

YouTube-д энэ нь үзнэ үүВидео Adrien TwarogЭнэ нь OpenAI Embeddings болон Vector Databases юм.


Би бас тав тухтайOpenAI-ийн Assistants File Search документаци, Хэрэв альтернатийг хүсэж байгаа бол энэ нь сонирхолтой байж болно.


Баримтлал

Ямар ч энэ нь чатбот зориулсан документын индексийг хэрхэн удирдах талаархи мэдрэмжийг олгодог:

  • Chunking + overlap ашиглан зөв контекст олохын тулд,
  • Хэвлэлтийн үүсгэх, хурдан вектор хэлбэрийн хайлт нь тэднийг хадгалах,
  • Эцэст нь, Би үүнийг ChatGPT-д тохиромжтой контексттэй дамжуулав.


Би AI-ийн мэргэжилтэн биш юм; Энэ нь зүгээр л миний хэрэгцээг тохиромжтой ажилладаг шийдэл юм. Хэрэв та үр ашигтай сайжруулах талаар ямар ч зөвлөгөө, эсвэл илүү ширээтэй арга хэрэгсэл байгаа бол,please let me know. Би вектор хадгалах шийдэл, chunking стратеги, эсвэл бусад гүйцэтгэлийн зөвлөмжүүд талаар дарна уу.


Thanks for reading, and feel free to share your thoughts!

L O A D I N G
. . . comments & more!

About Author

Aymeric PINEAU HackerNoon profile picture
Aymeric PINEAU@aymericzip
Founder of Intlayer, internationalisation solution for JS applications

TAG ҮҮ

ЭНЭ ӨГҮҮЛЛИЙГ ТОЛГОЙЛУУЛСАН...

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks