Delphi에서 RAG: 자신의 문서에 LLM을 근거시키기

· 컴포넌트

빠른 답변: 검색 증강 생성(RAG)은 두 가지 아이디어를 결합한 것입니다. 먼저 문서를 한 번 임베딩하고 그 결과 벡터를 저장합니다. 그런 다음 질문이 들어올 때마다 질문을 임베딩하고, 의미상 가장 가까운 청크를 검색해, 이를 컨텍스트로 LLM에 전달하여 답변이 모델의 학습 기억이 아닌 자신의 데이터에서 나오도록 합니다. sgcWebSockets에서는 전체 파이프라인이 세 개의 컴포넌트로 이루어집니다. 텍스트를 벡터로 바꾸는 TsgcAIOpenAIEmbeddings, 이를 저장하고 검색하는 TsgcAIDatabaseVectorFile 또는 TsgcAIDatabaseVectorPinecone, 그리고 최종적으로 근거 있는 답변을 작성하는 TsgcHTTP_API_OpenAI입니다.

범용 채팅 모델은 세상에 대해 많은 것을 알지만 당신의 제품 설명서, 지원 티켓, 지난 분기 내부 보고서에 대해서는 아무것도 모릅니다. 그런 것을 물어보면 모델은 거절하거나, 더 나쁘게는 그럴듯한 무언가를 지어냅니다. RAG는 아무것도 재학습하지 않고 이를 해결합니다. 모델은 그대로 두고, 질문 시점에 자신의 코퍼스에서 적절한 구절을 모델에 전달합니다. 아래는 실제 컴포넌트 이름과 함께 Delphi에서 처음부터 끝까지 이어지는 전체 루프입니다.

RAG가 실제로 하는 일

임베딩은 텍스트 조각의 의미를 담은 숫자의 목록입니다. 같은 주제를 다루는 두 구절은 공통된 키워드가 없더라도 그 숫자 공간에서 서로 가까이 자리합니다. 벡터 데이터베이스는 그 숫자들을 저장하고, 쿼리 벡터가 주어지면 유사도 순으로 정렬된 가장 가까운 항목을 반환합니다. RAG는 이를 한데 엮습니다.

단계일어나는 일sgcWebSockets 구성 요소
1. 수집 (한 번)문서를 청크로 분할하고, 각 청크를 임베딩하고, 벡터를 저장TsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile
2. 저장벡터를 로컬 파일이나 클라우드 인덱스에 보관TsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone
3. 검색 (질문마다)질문을 임베딩하고, 가장 가까운 청크를 찾기GetEmbeddingQueryData
4. 답변청크를 프롬프트에 넣고, 모델에 질문TsgcHTTP_API_OpenAI._CreateChatCompletion

1단계와 2단계는 데이터가 바뀔 때 실행됩니다. 3단계와 4단계는 사용자 질문마다 실행됩니다. 각 단계를 만들어 보겠습니다.

1단계 — 문서 임베딩하기

TsgcAIOpenAIEmbeddings를 생성하고, OpenAI 키를 지정하고, 그 Database 속성이 벡터 저장소를 가리키게 한 다음, CreateEmbeddingsFromFile을 호출합니다. 그 한 번의 호출이 파일을 읽고, 청크로 분할하고(EmbeddingsOptions.ChunkSize로 제어), 각 청크를 임베딩하고, BeginAddData / AddData / EndAddData 시퀀스를 통해 벡터를 저장소에 알아서 기록합니다.

uses
  sgcAI, sgcAI_OpenAI_Embeddings,
  sgcAI_DB_Vector, sgcAI_DB_Vector_File, sgcAI_DB_Vector_Pinecone;

var
  Embeddings: TsgcAIOpenAIEmbeddings;
  DBFile: TsgcAIDatabaseVectorFile;
begin
  Embeddings := TsgcAIOpenAIEmbeddings.Create(nil);
  Embeddings.OpenAIOptions.ApiKey := 'sk-...';

  // local, file-based vector store
  DBFile := TsgcAIDatabaseVectorFile.Create(nil);
  DBFile.VectorFileOptions.InputFilename  := 'corpus.sgcif';
  DBFile.VectorFileOptions.VectorFilename := 'corpus.sgcvf';

  Embeddings.Database := DBFile;
  Embeddings.CreateEmbeddingsFromFile('docs.txt');
end;

이것이 수집 단계의 전부입니다. 한 번 실행하거나, 문서가 바뀔 때마다 실행하세요. 기본 임베딩 모델은 text-embedding-3-small입니다. 다른 모델이 필요하면 EmbeddingsOptions.Model로 변경하세요. 더 자세한 내용은 Embeddings 컴포넌트 페이지에 있습니다.

2단계 — 벡터가 저장될 위치 선택하기

두 백엔드 모두 같은 기본 컴포넌트인 TsgcAIDatabaseVector에서 파생되므로 서로 교체할 수 있습니다. 하나를 다른 것으로 바꿔도 수집 및 쿼리 코드는 바뀌지 않습니다. 유일한 차이는 데이터가 어디에 자리하는가입니다.

데스크톱 앱, 오프라인 도구, 또는 더 작은 코퍼스에는 TsgcAIDatabaseVectorFile이 외부 서비스 없이 모든 것을 로컬 파일에 보관합니다. 인덱스가 크거나, 여러 프로세스나 사용자 간에 공유되어야 하거나, 한 대의 머신을 넘어 확장되어야 한다면, 관리형 Pinecone REST API를 통해 모든 청크를 업서트하는 TsgcAIDatabaseVectorPinecone으로 전환하세요.

var
  DBPinecone: TsgcAIDatabaseVectorPinecone;
begin
  DBPinecone := TsgcAIDatabaseVectorPinecone.Create(nil);
  DBPinecone.PineconeOptions.ApiKey         := 'pc-...';
  DBPinecone.PineconeIndexOptions.IndexName := 'sgc-embeddings';

  Embeddings.Database := DBPinecone;
  Embeddings.CreateEmbeddingsFromFile('docs.txt');
end;

수집 줄이 1단계와 동일하다는 점에 주목하세요. 그것이 바로 공유 기본 클래스의 핵심입니다. 파일 백엔드는 벡터 데이터베이스 페이지를, 클라우드 백엔드는 Pinecone 페이지를 참고하세요.

3단계 — 검색하고 답변하기

이제 질문별 경로입니다. 사용자의 질문을 임베딩하고 가장 가까운 저장된 청크를 한 번의 호출로 찾습니다. GetEmbedding은 텍스트를 임베딩하고 데이터베이스의 QueryData를 통해 실행하여, 코퍼스에서 가장 관련성 높은 구절을 반환합니다. 그 구절들이 당신의 컨텍스트입니다. 이를 질문과 연결하여 전체를 채팅 모델에 보냅니다.

var
  Question, Context, Prompt, Answer: string;
  OpenAI: TsgcHTTP_API_OpenAI;
begin
  Question := 'How do I enable the WatchDog reconnect?';

  // retrieve the closest chunks from your own data
  Context := Embeddings.GetEmbedding(Question, '');

  // build a grounded prompt
  Prompt :=
    'Answer the question using only the context below.' + sLineBreak +
    'If the context does not contain the answer, say you do not know.' +
    sLineBreak + sLineBreak +
    'Context:' + sLineBreak + Context + sLineBreak + sLineBreak +
    'Question: ' + Question;

  // ask the model
  OpenAI := TsgcHTTP_API_OpenAI.Create(nil);
  OpenAI.OpenAIOptions.ApiKey := 'sk-...';
  Answer := OpenAI._CreateChatCompletion('gpt-4o-mini', Prompt);

  Memo1.Lines.Text := Answer;
end;

이것이 RAG입니다. 모델은 학습 중에 당신의 문서를 본 적이 없지만, 요청 시점에 관련 구절을 모델 앞에 제시했기 때문에 그 문서로부터 답변합니다. 코퍼스를 바꾸면 답변도 함께 바뀌며, 미세 조정은 전혀 필요 없습니다. 컨텍스트가 비어 있을 때 거절하라는 지시가 모델이 추측하지 않고 정직하게 유지되게 합니다.

로컬이든 클라우드든, 같은 코드

한 번 더 강조할 만한 세부 사항이 있습니다. 파일 저장소와 Pinecone 사이의 선택은 되돌릴 수 있습니다. TsgcAIDatabaseVectorFileTsgcAIDatabaseVectorPineconeTsgcAIDatabaseVector 기반을 공유하기 때문에, 로컬 파일(인프라 제로, 오프라인 실행)로 프로토타입을 만들고 나중에 Embeddings.Database에 할당하는 컴포넌트만 바꿔 Pinecone으로 옮길 수 있습니다. 수집이나 쿼리 코드에서 바뀌는 것은 없습니다. 마지막의 LLM도 마찬가지입니다. 최종 답변을 작성할 다른 모델을 선호한다면 TsgcHTTP_API_OpenAI_CreateChatCompletion을 Anthropic이나 Gemini 컴포넌트로 교체할 수 있습니다.

청킹과 품질에 관한 메모

검색 품질은 문서를 어떻게 분할하는지에 달려 있습니다. 더 작은 청크는 매칭을 더 정밀하게 하지만 컨텍스트를 잃을 수 있고, 더 큰 청크는 컨텍스트를 유지하지만 매칭을 희석합니다. EmbeddingsOptions.ChunkSizeCreateEmbeddingsFromFile에 대해 이를 제어하므로, 자료에 맞게 조정해 볼 가치가 있습니다. 더 세밀한 제어를 원하면 CreateEmbeddings로 개별 문자열을 임베딩하고 수집하기 전에 청크를 직접 구성할 수도 있습니다.

시작하기

세 컴포넌트 모두 sgcWebSockets에 포함되어 있습니다. 무료 평가판을 받아서, 임베딩과 벡터 저장소 컴포넌트를 폼에 놓고, 텍스트 파일을 가리키게 하면, 백 줄도 훨씬 안 되는 코드로 동작하는 RAG 루프를 갖게 됩니다. AI 빌딩 블록 전체 세트는 AI & LLM 컴포넌트 허브에서 둘러보세요.

자신의 코퍼스에 이를 적용하는 것에 대한 질문이 있나요? 문의하기 — 코드를 작성한 사람들로부터 답변을 받게 됩니다.

관련 글