291 HENOINA

Ahoana ny fomba hananganana fanadihadiana maoderina - Miorina amin'ny OpenAI Embeddings (Chunking, Indexing, ary Searching)

ny Aymeric PINEAU13m2025/05/01
Read on Terminal Reader

Lava loatra; Mamaky

Ny hevitra fototra dia ny fikarohana ny antontan-taratasy amin'ny alàlan'ny fanapahan-kevitra azy ireo ho an'ny ampahany azo atao, ny famoronana embeddings amin'ny OpenAI, ary ny fanatanterahana fikarohana ny fahasamihafana mba hahita sy hiverina ny fampahalalana manan-danja indrindra amin'ny fangatahan'ny mpampiasa.
featured image - Ahoana ny fomba hananganana fanadihadiana maoderina - Miorina amin'ny OpenAI Embeddings (Chunking, Indexing, ary Searching)
Aymeric PINEAU HackerNoon profile picture
0-item

Hey ny rehetra! Tiako ny mizara ny fomba fijerin'ny famoronana chatbot "smart documentation" ho an'ny tetikasa izay miasa amin'ny.I’m not an AI expert, so any suggestions or improvements are more than welcome!


Ny tanjona amin'ity lahatsoratra ity dia tsy ny mamorona fampianarana hafa momba ny famoronana chatbot mifototra amin'ny OpenAI. Efa misy votoaty be amin'io lohahevitra io.index documentationAmin'ny alalan'ny fametrahana azy ireo amin'ny fitantananachunksNy famokaranaembeddingsAo amin'ny OpenAI, aryperforming a similarity searchmba hahita sy hiverina ny fampahalalana manan-danja indrindra amin'ny fangatahan'ny mpampiasa.


Amin'ny toe-javatra ahy, ny antontan-taratasy dia rakitra Markdown, fa mety ho endrika rehetra amin'ny lahatsoratra, rakitra angona, sns.

Ary nahoana ?

Satria indraindray dia sarotra ny mahita ny fampahalalana ilainao, tiako ny mamorona chatbot izay afaka mamaly fanontaniana momba ny lohahevitra manokana ary manome kontekst manan-danja avy amin'ny antontan-taratasy.


Ity mpanampy ity dia azo ampiasaina amin'ny fomba samihafa, toy ny:

  • Manome valiny haingana amin'ny fanontaniana matetika
  • Mitady dokotera / pejy toy ny Algolia
  • Manampy ny mpampiasa hahita ny fampahalalana ilainy ao amin'ny dokam-barotra manokana
  • Hahita ny fanontaniana / fanontaniana avy amin'ny mpampiasa amin'ny fitehirizana ny fanontaniana nangatahana

Ny famaritana

Eto ambany dia hanehoana ny ampahany telo lehibe amin'ny vahaolana aho:

  1. Vakio ny antontan-taratasy
  2. Indexing ny antontan-taratasy (chunking, overlap, ary embedding)
  3. Mitady ny antontan-taratasy (ary mampifandray azy amin'ny chatbot)

Ny hazo

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

Ny famakiana ireo antontan-taratasy

Ankoatra ny fanamafisana ny lahatsoratra momba ny antontan-taratasy, dia azonao atao ny mamantatra.mdNy fitaovana mampiasa fitaovana toy nyglob.

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

Amin'ny fomba hafa, azonao atao ny maka ny antontan-taratasy avy amin'ny tahirin-kevitra na CMS, sns.

Amin'ny fomba hafa, azonao atao ny maka ny antontan-taratasy avy amin'ny tahirin-kevitra na CMS, sns.


Indexing ny antontan-taratasy

Mba hamoronana ny fikarohana isika dia hampiasa ny OpenAINy vektor amin'ny APIMba hamoronana ny fihenjanana.


Ny vektor embeddings dia fomba hampiseho ny angon-drakitra amin'ny endrika nomerika, izay azo ampiasaina amin'ny fikarohana ny fahasamihafana (ao amin'ny tranga antsika, eo amin'ny fanontaniana mpampiasa sy ny fizarana ny antontan-taratasy).


Ity vektor ity, izay ahitana lisitry ny isan'ny lanjany, dia hampiasaina hanombohana ny fahasamihafana amin'ny fampiasana formula matematika.

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

Miorina amin'io hevitra io, dia naorina ny Database Vector. Noho izany, fa tsy mampiasa ny OpenAI API, dia azo ampiasaina ny angon-drakitra vektor toy ny Chroma, Qdrant na Pinecone.

Miorina amin'io hevitra io, dia naorina ny Database Vector. Noho izany, fa tsy mampiasa ny OpenAI API, dia azo ampiasaina ny angon-drakitra vektor toy ny Chroma, Qdrant na Pinecone.

2.1 Chunk ny tsirairay ny rakitra & Overlap

Ny bokotra lehibe amin'ny lahatsoratra dia afaka mihoatra ny fetra mifandray amin'ny modely na miteraka fanafihana tsy manan-danja kokoa, noho izany dia ilaina ny manavaka azy ireo ho an'ny ampahany mba hanatsarana ny fikarohana bebe kokoa. Na izany aza, mba hitazonana ny faharetan'ny sasany eo amin'ny ampahany, dia mifanandrify azy ireo amin'ny isan'ny token (na marika). Amin'izany fomba izany, ny fetran'ny ampahany dia tsy mety manapaka ny fetran'ny fifandirana manan-danja.

Ohatra amin'ny Chunking

Ao amin'ity ohatra ity dia manana lahatsoratra lava isika izay te-hametraka ao amin'ny ampahany kely kokoa. Amin'ity tranga ity, te-hamoronana ampahany amin'ny 100 marika ary mifanandrify amin'ny 50 marika.


Full Text (406 characters):

Ao am-pon'ny tanàna be dia be dia be, nisy boky tranainy izay efa adinoin'ny maro. Ny rafi-pivavahana dia feno boky avy amin'ny karazana rehetra azo eritreretina, tsirairay miteny tantara momba ny fanamby, ny zava-miafina, ary ny fahendrena mandrakizay. Isaky ny hariva, nanokatra ny varavaran'ny boky ny bokin'ny bokin'ny mpandray anjara, hankalaza ny saina mahaliana te-hikaroka ny fahalalana goavana ao anatiny.


  • 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.

Ny code dia

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

Raha te hahalala bebe kokoa momba ny fanapahana, ary ny fiantraikany amin'ny habeny amin'ny fametrahana, dia azonao jerena ity lahatsoratra ity.

Raha te hahalala bebe kokoa momba ny fanapahana, ary ny fiantraikany amin'ny habeny amin'ny fametrahana, dia azonao jerena ity lahatsoratra ity.

2.2 Ny taranak'olombelona

Rehefa voafafa ny rakitra, dia mamokatra ny vektor embeddings ho an'ny tsirairay amin'ny fampiasana ny API OpenAI (ohatra,text-embedding-3-largeNy

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 Ny famoronana sy ny famonjena embeddings ho an'ny rakitra manontolo

Mba hisorohana ny fanavaozana ny embeddings isaky ny fotoana, dia hamonjy ny embeddings. Mety ho voatahiry ao amin'ny tahirin-kevitra. Fa amin'ity tranga ity, dia hamonjy azy io amin'ny rakitra JSON eo an-toerana.


Ny code manaraka dia tsotra:

  1. Mifanohitra amin'ny antontan-taratasy tsirairay,
  2. Tsindrio ny antontan-taratasy ao amin'ny tranonkala,
  3. Izany dia mahatonga ny tsindry ho an'ny tsirairay,
  4. Ampiasao ny rakitra ao amin'ny rakitra JSON.
  5. Ampio ny vektorStore miaraka amin'ny fidirana ampiasaina amin'ny fikarohana.
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);
  }
};

Hitady ny antontan-taratasy

3.1 Ny vektor dia mitovy amin'ny

Mba hamaly ny fanontaniana avy amin'ny mpampiasa isika, dia manomboka amin'ny famoronanauser's questionary avy eo dia manomana ny fahasamihafana eo amin'ny fanadihadiana sy ny fanadihadiana tsirairay. dia manasitrana ny zavatra rehetra etsy ambany ny fetran'ny fahasamihafana sasany ary mitazona ny fitsipika X ambony ihany.

/**
 * 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 Fanamafisana ny OpenAI amin'ny Chunks manan-danja

Aorian'ny fanodinana, dia manome sakafo nytopNy ChatGPT dia mahita ny ampahany manan-danja indrindra amin'ny dokam-barotra tahaka ny efa voasoratra azy ireo tao amin'ny fifandraisana. Avy eo dia mamela ny ChatGPT mamorona valiny ho an'ny mpampiasa.

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

Ampiasao ny OpenAI API ho an'ny Chatbot amin'ny fampiasana Express

Mba hanatanterahana ny rafitra, dia hampiasa server Express.js isika. Indreto ny ohatra amin'ny endpoint Express.js kely mba hanatanterahana ny fanontaniana:

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

UI: Manao ny chatbot interface

Ao amin'ny frontend, namorona singa kely React miaraka amin'ny fifandraisana mitovy amin'ny chat aho. Maniraka hafatra amin'ny backend Express aho ary mampiseho ny valiny. Tsy misy zavatra be dia be, noho izany dia hiala amin'ny antsipirihany izahay.


Ny tempoly code

Izaho dia nanaoNy tempoly codeho anao ho toy ny fototra ho an'ny chatbot anao.

Ny Demo velona

Raha te hanandrana ny fampiharana farany amin'ity chatbot ity ianao, jereo ityNy pejy demo.

Ny pejy demo

Ny code demo dia

  • Ny fanontaniana dia ny fanontaniana.ts
  • Frontend: ChatBot ny singa

Mandehana bebe kokoa

Ao amin'ny YouTube, jereo izanySary avy amin'i Adrien TwarogIzany dia mampiasa ny OpenAI Embeddings sy ny Vector Databases.


Izaho koa dia niankohoka tamin'nyNy dokam-barotra amin'ny OpenAI Assistants File SearchIzany dia mety ho zava-dehibe raha tianao ny fomba fiasa hafa.


Ny famaranana

Manantena aho fa izany dia hanome anao hevitra momba ny fomba handray ny dokam-barotra indexing ho an'ny chatbot:

  • Amin'ny fampiasana chunking + overlap mba hahita ny fifandraisana tsara,
  • Ny famokarana ny embeddings sy ny fitehirizana azy ireo ho an'ny fikarohana haingana amin'ny endriky ny vektor,
  • Amin'ny farany, nanome azy ho an'ny ChatGPT aho miaraka amin'ny kontekst manan-danja.


Tsy manam-pahaizana momba ny AI aho; izany ihany no vahaolana izay hitako fa miasa tsara ho an'ny zavatra ilaiko.please let me knowTiako ny hihaino valiny momba ny vektor fametrahana vahaolana, chunking stratejika, na fampisehoana hafa torohevitra.


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

HANG TAGS

ITY ARTICLE ITY NO NARESAKA TAMIN'NY...

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks
OSZAR »