6장 · 에이전트 ④ RAG 문서검색 — 절 목차01. 표가 아니라 긴 글에 답이 있을 때02. 왜 문서를 잘게 잘라야 하나 청킹03. 의미가 비슷하면 숫자도 비슷하다 임베딩04. 따라하기 정책 PDF를 잘라 벡터로 만들기05. 따라하기 인덱스를 저장하고 다시 불러오기06. 지어내지 않게 만드는 한 문장07. 따라하기 출처와 함께 답하는 검색 에이전트08. 검색을 도구로 만들면 다른 도구와 나란히 선다09. CSV 분석과 RAG 무엇을 언제 쓰나10. 정리 체크리스트
정리 & 체크리스트
한 줄 요약
에이전트 네 종류를 모두 만들었습니다. 1장에서 못 풀던 세 질문도 전부 풀렸습니다. 7장에서는 여기에 기억을 붙이고, 8장에서 하나의 팀으로 묶습니다.
1. 6장에서 만든 것
code/ch06_rag_agent.py — 에이전트 ④ RAG 문서검색
연결 대상 : 사내 문서 PDF (긴 글)
핵심 기술 : 임베딩 · 벡터검색
대표 질문 : "불량 상품 반품 기간이 며칠이죠?"
| 만든 것 | 하는 일 |
|---|---|
build_index() |
PDF → 조각 → 벡터 인덱스 (있으면 불러오기) |
retriever |
질문 → 관련 조각 4개 |
PROMPT |
"문서에 적힌 것만 근거로" |
answer(question) |
답변 + 출처 |
policy_tool |
8장에서 쓸 도구 형태 |
2. 이 장의 핵심 내용 — 말로 설명할 수 있어야 할 것들
- 표에 든 숫자는 계산하고, 문서에 쓰인 문장은 검색해서 근거로 쓴다. 이 구분이 흐려지면 환각이다.
- 문서를 통째로 넣는 것은 양이 늘면 무너진다 — 비용 · 정확도 · 출처 세 가지 문제.
- RAG = 검색 + 증강 + 생성. 필요한 조각만 골라 프롬프트에 넣는다.
- 검색이 어려운 이유는 단어가 안 겹치기 때문이다. "반품"과 "청약철회"처럼.
- 임베딩은 문장을 숫자 목록(벡터)으로 바꾼다. 뜻이 비슷하면 숫자도 비슷하다.
- 비슷한 정도는 코사인 유사도로 잰다. 점수의 절대값이 아니라 순위를 본다.
chunk_size는 검색 정확도와 근거의 양 사이의 균형을 정한다. 기준은 "답 하나가 들어갈 만한 크기".chunk_overlap은 문장이 경계에서 잘려도 뜻이 남게 하는 안전망이다.k는 근거를 빠짐없이 찾는 것과 관련 없는 내용을 줄이는 것 사이의 균형을 정한다. 검색은 항상k개를 돌려준다 — "없다"고 말해 주지 않는다.- 인덱스는 파일로 저장한다. 문서·
chunk_size·임베딩 모델이 바뀌면 반드시 다시 만든다. - 캐시의 함정 — 원본이 바뀌었는데 안 지우면 오류 없이 옛날 답이 나온다.
- "문서에 없으면 '찾을 수 없습니다'라고 답한다" 한 문장이 환각을 크게 줄인다.
- 검색에는 LLM이 안 쓰인다. LLM은 찾은 것을 문장으로 정리하는 데만 쓴다. 정확도의 대부분은 검색 단계에서 결정된다.
- 출처를 함께 보여 주면 사람이 검증할 수 있다. 마지막 방어선이다.
create_retriever_tool로 검색을 도구로 만들면 다른 도구와 함께 쓸 수 있다.- 도구로 만들면 출처 표시가 사라진다. 되찾으려면 반환 문자열에 출처를 넣는다.
3. 체크리스트
- [ ]
python code/ch06_rag_agent.py가 끝까지 돌아간다 - [ ] 첫 실행과 두 번째 실행의 속도 차이를 확인했다
- [ ]
data/policy_index폴더가 만들어진 것을 확인했다 - [ ] 검색 결과만 따로 찍어 봤다 (LLM 없이)
- [ ] "해외 배송"처럼 문서에 없는 질문에 "찾을 수 없다"고 하는 것을 확인했다
- [ ] 답의 근거를 실제 PDF에서 대조해 봤다
- [ ] CSV 분석과 RAG를 언제 쓰는지 설명할 수 있다
4. 진행 상황 — 네 종류 완성
[에이전트 네 종류]
✓ ① 사내 데이터 조회 3장 함수 도구
✓ ② 웹검색 4장 외부 API + ReAct 루프
✓ ③ CSV 데이터분석 5장 코드 생성·실행 + LangChain
✓ ④ RAG 문서검색 6장 임베딩 · 벡터검색
[1장에서 못 푼 세 질문]
✓ 사내 데이터 "주문 O001203은?" → 3장
✓ 최신 정보 "요즘 시장 어때요?" → 4장
✓ 사내 문서 "반품 며칠 이내?" → 6장
1장에서 그린 지도를 다 채웠습니다.
5. 아직 남은 것 둘
① 대화가 이어지지 않는다
고객: P0003 스마트워치 재고가 얼마나 남았나요?
봇 : 3개 남았습니다.
고객: 그거 재주문 기준은요?
봇 : "그거"가 무엇인지 모르겠습니다.
지금까지 만든 네 에이전트는 전부 한 번 묻고 답하면 끝입니다.
2장에서 손으로 대화를 쌓아 봤죠. 실제 서비스에서는 손님이 수백 명이라 손님마다 기록을 따로 보관하고 꺼내 오는 일을 직접 짜야 합니다.
→ 7장에서 LangGraph가 이걸 대신합니다.
② 한 종류만으로는 답이 안 나오는 질문
"승승 스마트워치 Fit 5, 지금 어떻게 대응해야 할까요?"
- CSV만 보면 → "재고 3개, 평점 3.8" 에서 끝
- 문서만 보면 → "배터리 7일/18시간" 에서 끝
- 웹만 보면 → "시장 동향" 에서 끝
셋을 나란히 놓아야 판단이 나옵니다.
→ 8장에서 하나의 팀으로 묶습니다.
6. 7장 예고 — 기억을 붙입니다
7장에서는 LangGraph 로 넘어갑니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
chatbot = graph.compile(checkpointer=InMemorySaver())
핵심은 세 가지입니다.
| 개념 | 무엇 | 2장으로 치면 |
|---|---|---|
| 상태(State) | 계속 들고 다니는 값 | history 리스트 |
| 노드(node) | 상태를 받아 일을 하는 함수 | chat_multi() |
| 체크포인터 | 상태를 자동 저장 | history.append(...) |
그리고 thread_id 로 손님을 구분합니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
chatbot.invoke({"messages": [...]}, {"configurable": {"thread_id": "손님-오지우"}})
2장에서 손으로 하던 일을 라이브러리에게 넘기는 장입니다. 그래서 2장에서 일부러 손으로 해 봤습니다.
그리고 5장에서 만든 CSV 에이전트에 기억을 붙이면 — 한 줄입니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
create_agent(llm, tools=[run_pandas], system_prompt=SYSTEM,
checkpointer=InMemorySaver()) # ← 이 한 줄
손님: P0003 스마트워치 재고가 얼마나 남았나요?
봇 : P0003 스마트워치 재고는 3개 남았습니다.
손님: 그거 재주문 기준은요?
봇 : P0003 스마트워치의 재주문 기준은 30개입니다.
손님: 그럼 지금 발주해야 하나요?
봇 : 네, 발주해야 합니다.
"그거"가 통합니다.