0Pricing
AI Engineering Academy · 강의

핵심 RAG 및 에이전트 기능 구현

과정 전반에서 배운 패턴에 따라 문서 수집 처리 과정, 벡터 저장소 색인, 재순위화를 적용한 검색, 에이전트 도구 통합을 구현합니다.

핵심 RAG 및 에이전트 기능 구현은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

구현 순서와 종속성

시스템은 아래에서 위로 구축하세요. 외부 종속성이 없는 구성 요소부터 시작한 다음, 해당 구성 요소에 의존하는 구성 요소를 계층적으로 추가합니다. RAG + 에이전트 시스템의 순서는 다음과 같습니다. (1) 벡터 저장소 스키마, (2) 수집 파이프라인, (3) 검색기, (4) 기본 질의응답 체인, (5) 스트리밍 엔드포인트, (6) 도구를 사용하는 에이전트, (7) 캐싱 계층, (8) 추적 계측입니다. 각 구성 요소를 파이프라인에 통합하기 전에 독립적으로 테스트하세요.

# Build order:
IMPL_ORDER = [
    'pgvector_schema',      # prerequisite for everything
    'document_ingestion',   # populate the vector store
    'hybrid_retriever',     # test retrieval in isolation
    'qa_chain_basic',       # integrate LLM with retrieval
    'streaming_endpoint',   # expose via API
    'function_calling',     # add agent tool calls
    'semantic_cache',       # reduce repeat API calls
    'langsmith_tracing',    # add after core works
    'injection_filter',     # harden before load testing
]

벡터 저장소 설정

문서를 수집하기 전에 올바른 스키마로 pgvector 테이블을 생성하세요. 벡터, 청크 텍스트, 모든 메타데이터 필드, 선택적 재색인을 위한 updated_at 타임스탬프 열을 포함합니다. 임베딩 열에 벡터 색인(HNSW 또는 IVFFlat)을 즉시 생성하세요. 수백만 개의 행이 쌓인 후 색인을 추가하는 것은 빈 테이블에 미리 추가하는 것보다 훨씬 느립니다.

-- PostgreSQL schema with pgvector
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE document_chunks (
    id          BIGSERIAL PRIMARY KEY,
    doc_id      TEXT NOT NULL,
    chunk_text  TEXT NOT NULL,
    embedding   VECTOR(1536) NOT NULL,
    source_file TEXT,
    page_number INT,
    section     TEXT,
    doc_type    TEXT,
    tenant_id   TEXT NOT NULL,  -- for data isolation
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_chunks_hnsw ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

CREATE INDEX idx_chunks_tenant ON document_chunks(tenant_id);

수집 파이프라인 구축

파일 경로 또는 URL을 받아 색인된 청크 수를 반환하는 단일 비동기 함수로 수집을 구현하세요. 다양한 파일 유형에는 LangChain의 문서 로더를 사용하고, 청크 분할에는 재귀적 문자 텍스트 분할기를 사용하세요. 2048토큰 입력 제한을 지키고 API 호출을 수천 번에서 수십 번으로 줄이려면 임베딩 호출을 일괄 처리하세요.

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from openai import AsyncOpenAI

async def ingest_document(file_path: str, doc_id: str, tenant_id: str) -> int:
    loader = PyPDFLoader(file_path)
    pages = loader.load()
    splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100)
    chunks = splitter.split_documents(pages)
    # Batch embed
    texts = [c.page_content for c in chunks]
    client = AsyncOpenAI()
    embeddings_response = await client.embeddings.create(
        model='text-embedding-3-small',
        input=texts
    )
    embeddings = [e.embedding for e in embeddings_response.data]
    await insert_chunks_to_pgvector(chunks, embeddings, doc_id, tenant_id)
    return len(chunks)

하이브리드 검색기 구축

밀집 벡터 검색과 BM25 키워드 검색을 결합하고 순위 상호 융합을 사용해 결과를 병합하세요. 검색기는 단일 retrieve(query, tenant_id, top_k) 메서드를 가진 클래스로 구현합니다. 내부에서는 asyncio.gather를 사용해 두 검색을 동시에 실행하고, 순위 목록을 RRF로 병합하며, 청크 ID를 기준으로 중복을 제거한 뒤 소스 메타데이터와 함께 상위 top_k 결과를 반환하세요.

import asyncio
from rank_bm25 import BM25Okapi

class HybridRetriever:
    def __init__(self, pool, k_rrf: int = 60):
        self.pool = pool
        self.k_rrf = k_rrf

    async def retrieve(self, query: str, tenant_id: str, top_k: int = 10) -> list:
        dense_results, sparse_results = await asyncio.gather(
            self._dense_search(query, tenant_id, top_k * 3),
            self._bm25_search(query, tenant_id, top_k * 3)
        )
        merged = self._rrf_merge(dense_results, sparse_results)
        return merged[:top_k]

    def _rrf_score(self, rank: int) -> float:
        return 1.0 / (self.k_rrf + rank + 1)

핵심 QA 체인 구현

LangChain LCEL을 사용해 핵심 질의응답 체인을 구축하세요. 체인은 질문과 검색된 청크를 받아 제공된 컨텍스트만 사용하라는 지침이 포함된 보강 프롬프트를 구성하고 응답을 스트리밍합니다. 모델이 문서 이름과 페이지 번호로 출처를 인용하고, 검색된 컨텍스트에 답이 없을 때 'I don't know'라고 말하도록 명시적인 지침을 추가하세요.

from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser

RAG_PROMPT = ChatPromptTemplate.from_messages([
    ('system', 'You are a precise assistant. Answer ONLY using the provided context. '
               'Cite sources as [DocName, p.N]. If the answer is not in the context, say "I don\'t have information about that."'),
    ('user', 'Context:\n{context}\n\nQuestion: {question}')
])

llm = ChatOpenAI(model='gpt-4o', temperature=0, streaming=True)
qa_chain = RAG_PROMPT | llm | StrOutputParser()

async def answer_question(question: str, chunks: list) -> str:
    context = '\n\n'.join(f'[{c["source"]}]\n{c["text"]}' for c in chunks)
    return await qa_chain.ainvoke({'context': context, 'question': question})

에이전트에 함수 호출 추가

실시간 데이터나 계산이 필요한 질의를 처리할 수 있도록 기본 질의응답 시스템을 함수 호출로 확장하세요. 다음 도구를 정의합니다. 최신 정보를 웹에서 검색하는 도구, 안전한 읽기 전용 데이터베이스에서 SQL 질의를 실행하는 도구, ID로 특정 레코드를 조회하는 도구입니다. 에이전트는 질문에 따라 호출할 도구를 결정하고, 도구를 실행한 뒤 결과를 최종 답변에 반영합니다.

from openai import AsyncOpenAI
import json

TOOLS = [
    {
        'type': 'function',
        'function': {
            'name': 'search_knowledge_base',
            'description': 'Search the internal document knowledge base for relevant information',
            'parameters': {
                'type': 'object',
                'properties': {
                    'query': {'type': 'string', 'description': 'Search query'},
                    'top_k': {'type': 'integer', 'default': 5}
                },
                'required': ['query']
            }
        }
    }
]

async def agent_with_tools(question: str, tenant_id: str) -> str:
    client = AsyncOpenAI()
    messages = [{'role': 'user', 'content': question}]
    while True:
        resp = await client.chat.completions.create(
            model='gpt-4o', messages=messages, tools=TOOLS)
        if resp.choices[0].finish_reason != 'tool_calls':
            return resp.choices[0].message.content
        tool_call = resp.choices[0].message.tool_calls[0]
        args = json.loads(tool_call.function.arguments)
        result = await dispatch_tool(tool_call.function.name, args, tenant_id)
        messages.append({'role': 'tool', 'tool_call_id': tool_call.id, 'content': result})

에이전트 응답 스트리밍

에이전트를 서버 전송 이벤트를 사용하는 FastAPI StreamingResponse로 감싸 프런트엔드에서 토큰이 도착하는 즉시 표시하도록 하십시오. 도구 호출이 포함된 에이전트 응답의 경우 도구가 실행되는 동안 진행 상태 표시(‘지식 베이스를 검색하는 중...’)를 스트리밍한 다음, 최종 답변을 토큰 단위로 스트리밍하십시오. 이렇게 하면 도구 실행 지연 시간 동안 페이지가 멈춘 것처럼 보이는 문제를 방지할 수 있습니다.

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

async def event_stream(question: str, tenant_id: str):
    yield 'data: {"type": "start"}\n\n'
    chunks = await retriever.retrieve(question, tenant_id)
    yield 'data: {"type": "retrieving", "count": ' + str(len(chunks)) + '}\n\n'
    async for token in qa_chain.astream({'context': format_context(chunks), 'question': question}):
        yield f'data: {{"type": "token", "content": {repr(token)}}}\n\n'
    yield 'data: {"type": "done"}\n\n'

@app.post('/query/stream')
async def stream_query(question: str, tenant_id: str):
    return StreamingResponse(event_stream(question, tenant_id), media_type='text/event-stream')

Semantic Cache 연결하기

쿼리 파이프라인에서 검색 전에 수행하는 단계로 시맨틱 캐시를 추가하십시오. 검색기와 LLM을 호출하기 전에 사용자 질문을 임베딩하고 캐시를 확인하십시오. 유사도가 임계값을 초과하는 캐시 적중이 발생하면 cached: true 플래그와 함께 캐시된 답변을 즉시 반환하십시오. 캐시 미적중인 경우 전체 파이프라인을 진행하고, 그 결과로 생성된 답변을 이후의 유사한 쿼리에 사용할 수 있도록 캐시에 저장하십시오.

async def query_pipeline(question: str, tenant_id: str) -> dict:
    # 1. Check semantic cache
    cache_hit = await semantic_cache.lookup(question, tenant_id, threshold=0.92)
    if cache_hit:
        return {'answer': cache_hit.answer, 'cached': True, 'sources': cache_hit.sources}

    # 2. Retrieve
    chunks = await retriever.retrieve(question, tenant_id, top_k=5)
    chunks = await reranker.rerank(question, chunks, top_n=3)

    # 3. Generate
    answer = await answer_question(question, chunks)
    sources = [c['source'] for c in chunks]

    # 4. Cache result
    await semantic_cache.store(question, tenant_id, answer, sources)

    return {'answer': answer, 'cached': False, 'sources': sources}

LangSmith 추적 추가하기

두 개의 환경 변수를 설정하여 LangSmith로 파이프라인을 계측하십시오. 모든 LangChain 체인 호출은 토큰 수, 지연 시간, 입력, 출력 및 모든 오류와 함께 자동으로 추적됩니다. 사용자 지정 LangChain 외부 코드(검색, 재순위 지정)의 경우 호출을 @traceable 데코레이터로 감싸 추적에 포함하십시오. 이를 통해 모든 파이프라인 단계를 처음부터 끝까지 완전히 파악할 수 있습니다.

import os
from langsmith import traceable

os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_API_KEY'] = os.environ['LANGSMITH_API_KEY']
os.environ['LANGCHAIN_PROJECT'] = 'document-qa-production'

# Wrap non-LangChain steps with @traceable
@traceable(name='hybrid_retrieval')
async def traced_retrieval(question: str, tenant_id: str, top_k: int) -> list:
    return await retriever.retrieve(question, tenant_id, top_k)

@traceable(name='cohere_reranking')
async def traced_reranking(question: str, chunks: list) -> list:
    return await reranker.rerank(question, chunks)

# LangChain LCEL chains are automatically traced — no extra code needed

통합 테스트 실행하기

질문 입력부터 최종 답변까지 전체 쿼리 파이프라인을 실행하는 통합 테스트를 작성하십시오. 알려진 문서가 포함된 작은 테스트 벡터 저장소를 사용하면 어떤 청크가 검색되어야 하는지와 답변에 무엇이 포함되어야 하는지에 대해 결정론적인 검증을 작성할 수 있습니다. 운영 인프라를 그대로 반영하되 별도의 벡터 저장소와 LLM 키를 사용하는 스테이징 환경에서 통합 테스트를 실행하십시오.

import pytest

@pytest.mark.asyncio
async def test_full_pipeline_returns_grounded_answer():
    # Setup: ingest known document
    await ingest_document('tests/fixtures/policy.pdf', 'policy_v1', 'test_tenant')

    # Query with a question that has a known answer in the document
    result = await query_pipeline(
        question='What is the cancellation policy?',
        tenant_id='test_tenant'
    )

    assert result['answer'] is not None
    assert len(result['answer']) > 50
    assert '24 hours' in result['answer']  # known fact in document
    assert 'policy.pdf' in str(result['sources'])
    assert result['cached'] is False  # fresh query

검색 기준 품질 측정하기

무엇이든 최적화하기 전에 검색 기준선을 설정하십시오. 평가 테스트 세트를 사용하여 적중률(정확한 문서가 상위 5개 결과에 나타나는가?)과 MRR(몇 위에 검색되는가?)을 측정하십시오. 초기 벡터 저장소를 설정한 후, 재순위 지정이나 하이브리드 검색을 추가하기 전에 이 기준선을 측정하십시오. 기준선이 있으면 각 최적화가 실제로 어떤 개선을 가져오는지 확인할 수 있으므로, 유지할 가치가 있는 기법에 대한 근거를 확보할 수 있습니다.

async def measure_retrieval_baseline(test_cases: list) -> dict:
    hits = 0
    reciprocal_ranks = []
    for case in test_cases:
        results = await retriever.retrieve(case['question'], case['tenant_id'], top_k=5)
        result_docs = [r['doc_id'] for r in results]
        if case['relevant_doc'] in result_docs:
            hits += 1
            rank = result_docs.index(case['relevant_doc']) + 1
            reciprocal_ranks.append(1.0 / rank)
        else:
            reciprocal_ranks.append(0.0)
    return {
        'hit_rate_at_5': hits / len(test_cases),
        'mrr': sum(reciprocal_ranks) / len(reciprocal_ranks)
    }

빠른 확인

핵심 RAG 및 에이전트 기능 구축에 대한 이해도를 확인하십시오.

레슨 요약

이 레슨에서는 다음을 배웠습니다. 상향식 구현 순서를 따르면 통합하기 전에 각 구성 요소를 독립적으로 테스트할 수 있고, 동시 밀집 검색과 BM25 검색을 결합한 하이브리드 검색을 RRF로 통합하면 가장 우수한 검색 품질을 얻을 수 있으며, @traceable 데코레이터를 사용한 LangSmith 추적은 파이프라인 전체를 완전히 파악할 수 있게 해 줍니다. 다음으로 보안, 캐싱 및 안정성 패턴을 적용하여 시스템을 강화하겠습니다.

자주 묻는 질문

“핵심 RAG 및 에이전트 기능 구현” 강의는 무료인가요?

네 — “핵심 RAG 및 에이전트 기능 구현” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“핵심 RAG 및 에이전트 기능 구현”에서 뭘 배우나요?

과정 전반에서 배운 패턴에 따라 문서 수집 처리 과정, 벡터 저장소 색인, 재순위화를 적용한 검색, 에이전트 도구 통합을 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“핵심 RAG 및 에이전트 기능 구현” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 프로덕션 아키텍처 설계
  2. 핵심 RAG 및 에이전트 기능 구현
  3. 강화하기: 보안, 캐싱, 안정성
  4. 평가, 배포, 회고
← AI Engineering Academy(으)로 돌아가기