검색을 도구로 만들면 다른 도구와 함께 쓸 수 있다
한 줄 요약
지금까지의 answer() 는 RAG 전용 함수입니다. 이걸 도구(tool) 로 만들면, 3~5장에서 만든 도구들과 똑같은 방식으로 에이전트에 붙일 수 있습니다. 8장으로 가는 다리입니다.
1. 지금 방식의 한계
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
r = answer("불량 상품 반품은 며칠 이내인가요?")
이 함수는 문서 질문만 처리합니다. 다른 질문이 섞이면 어떻게 될까요?
answer() 는 문서만 봅니다. 재고는 답할 수 없습니다.
반대로 5장의 CSV 에이전트는 문서를 못 봅니다.
둘을 한 에이전트에 붙이려면 검색도 "도구"여야 합니다.
2. 검색을 도구로 만들기
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
from langchain_core.tools.retriever import create_retriever_tool
policy_tool = create_retriever_tool(
vectorstore.as_retriever(search_kwargs={"k": 6}),
"policy_search",
"승승장구몰의 사내 규정과 제품 매뉴얼을 검색한다. "
"환불·교환·반품 기간, 멤버십 등급과 적립률, 제품 사양 같은 질문에 사용한다.",
)
create_retriever_tool(retriever, name, description) — 인자 셋입니다.
| 인자 | 무엇 | 3장으로 치면 |
|---|---|---|
| 첫 번째 | 검색기 | 함수 본체 |
| 두 번째 | 도구 이름 | 함수 이름 |
| 세 번째 | 설명 | 독스트링 |
세 번째가 독스트링 자리입니다. 3장 3절에서 배운 원칙이 그대로 적용됩니다.
3. 설명을 뜯어보기
"승승장구몰의 사내 규정과 제품 매뉴얼을 검색한다.
환불·교환·반품 기간, 멤버십 등급과 적립률, 제품 사양 같은 질문에 사용한다."
| 부분 | 역할 |
|---|---|
| "사내 규정과 제품 매뉴얼을 검색한다" | 무엇을 하는가 |
| "환불·교환·반품 기간, 멤버십 등급..., 제품 사양" | 언제 쓰는가 (구체적 예) |
구체적인 예를 나열한 것이 핵심입니다. 4장 4절에서 배운 경계선 긋기입니다.
8장에서 이 도구는 run_pandas·search_web 과 함께 쓰이게 됩니다. 그때 모델이 헷갈리지 않으려면 "이건 규정과 사양 질문에 쓴다" 가 분명해야 합니다.
4. k=6 인 이유
앞에서 만든 retriever 는 k=4 인데, 도구용은 k=6 입니다.
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # answer() 용
policy_tool = create_retriever_tool(
vectorstore.as_retriever(search_kwargs={"k": 6}), # 도구용
...)
같은 인덱스에서 검색기를 두 개 만든 것입니다. 인덱스를 두 번 만드는 게 아닙니다.
창고는 하나, 창구가 둘입니다. 창구마다 "몇 개씩 꺼내 줄지"(k)를 다르게 정한 것이죠.
왜 도구용은 더 넉넉한가 — 8장에서 이런 요청이 들어오기 때문입니다.
주제가 둘입니다. k=4 면 한쪽 문서의 조각들이 4자리를 다 차지해 다른 쪽을 놓칠 수 있습니다.
실제로 8장을 만들 때 이 문제가 났습니다.
[k=4 일 때]
규정담당 보고: 제품 사양은 ... (사양만 나옴)
환불·교환 기간에 대한 사내 규정은 문서에 없습니다. ← 틀림!
[k=6 일 때]
규정담당 보고: 제품 사양은 ...
단순 변심: 7일 이내 / 상품 불량·하자: 30일 이내 ← 맞음
이런 게 실제로 RAG를 손보는 일입니다. 모델을 바꾼 게 아니라 숫자 하나를 4에서 6으로 올렸습니다. 4절에서 "RAG 품질은 대부분 세 숫자에서 결정된다"고 한 것이 이런 뜻입니다.
5. 도구로 쓰면 무엇이 달라지나
answer() — 우리가 흐름을 정한다
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
docs = retriever.invoke(question) # 우리가 검색하고
context = ... # 우리가 합치고
body = llm.invoke(PROMPT...) # 우리가 프롬프트를 만든다
항상 검색합니다. 문서와 무관한 질문이 와도 검색합니다.
policy_tool — 모델이 판단한다
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
agent = create_agent(llm, tools=[policy_tool, run_pandas], system_prompt=...)
모델이 "이 질문에는 문서 검색이 필요하다"고 판단할 때만 부릅니다. 그리고 다른 도구와 섞어 쓸 수 있습니다.
질문: "P0003 재고가 몇 개이고, 반품 기간은 며칠인가요?"
[1단계] run_pandas(...) → 재고 3개
[2단계] policy_search(...) → 수령일로부터 30일 이내
[3단계] 두 결과를 합쳐 답변
4장의 ReAct 루프 그대로입니다. 도구 종류만 늘었습니다.
6. 잃는 것 — 출처 표시
도구로 만들면 출처를 붙이는 코드가 사라집니다.
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
# answer() 에는 있었던 것
sources.append(f"{key[0]} {key[1] + 1}쪽")
create_retriever_tool 은 조각의 본문만 모델에게 돌려줍니다. metadata 는 안 넘어갑니다.
7절의 두 갈래 그림에서 아래 갈래가 잘린 것입니다.
그래서 8장의 답변에는 "환불교환정책.pdf 1쪽" 같은 출처가 안 붙습니다. 대신 "(규정담당)" 처럼 누가 조사했는지가 출처 자리를 대신합니다 — 8장 7절에서 봅니다.
되찾으려면
create_retriever_tool 을 안 쓰고 직접 도구를 만들면 됩니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
@tool
def policy_search(query: str) -> str:
"""승승장구몰의 사내 규정과 제품 매뉴얼을 검색한다. ..."""
docs = vectorstore.as_retriever(search_kwargs={"k": 6}).invoke(query)
return "\n\n".join(
f"[{d.metadata['source']} {d.metadata['page']+1}쪽]\n{d.page_content}"
for d in docs)
출처를 본문 안에 넣어 돌려주는 것입니다. 그러면 모델이 답할 때 인용할 수 있습니다.
3장 3절의 원칙이 여기서 다시 쓰입니다 — 도구의 반환값은 "다음 사람이 읽을 보고서" 입니다. 출처를 보여 주고 싶으면 반환 문자열에 넣어야 합니다.
이 과정의 실습 코드는 단순함을 위해
create_retriever_tool을 썼습니다. 출처가 중요한 서비스라면 위 방식으로 바꾸세요.여기가 이 과정에서 출처 정보가 빠지는 지점입니다.
answer()는 출처를 붙이고, 도구로 만들면 사라집니다. 왜 사라지는지를 눈으로 본 것이 이 절에서 배운 점입니다 — 되찾는 법도 위에 있고요.
7. 네 종류가 모두 도구가 됐습니다
이 절로 준비가 끝났습니다.
| 에이전트 | 도구 | 장 |
|---|---|---|
| ① 사내 데이터 조회 | get_order_status |
3장 |
| ② 웹검색 | search_web |
4장 |
| ③ CSV 데이터분석 | run_pandas |
5장 |
| ④ RAG 문서검색 | policy_tool |
6장 |
전부 같은 모양입니다 — 문자열을 받아 문자열을 돌려주는 것.
이제 이것들을 한 에이전트에 다 붙일 수도 있고, 여러 에이전트에 나눠 줄 수도 있습니다. 어느 쪽이 나은지가 8장의 주제입니다.
미리 답을 말하면 — 4장 4절에서 봤듯 한 명에게 다 몰면 판단이 나빠집니다. 그래서 나눕니다.
스스로 해 보기
- 도구를 직접 불러 보세요.
python print(policy_tool.invoke({"query": "반품 기간"})[:300])
answer()의 결과와 무엇이 다른가요? - 위의 출처를 넣는 버전을 직접 만들어 실행해 보세요.
policy_tool과 5장의run_pandas를 한 에이전트에 붙여 보세요.
```python
from ch05_csv_agent import run_pandas
from langchain.agents import create_agent
from common import get_chat
llm = get_chat(temperature=0)
agent = create_agent(llm, tools=[policy_tool, run_pandas],
system_prompt="너는 승승장구몰 담당자다. 사실만 근거로 간결히 답한다.")
out = agent.invoke({"messages": [{"role": "user",
"content": "P0003 재고가 몇 개이고, 불량 반품 기간은 며칠인가요?"}]})
print(out["messages"][-1].text)
```
도구 두 개를 순서대로 쓰는지 확인하세요.
3번이 8장의 예고편입니다. 직접 해 보면 다음 질문이 자연스럽게 떠오릅니다 — "도구를 더 붙이면 어디까지 될까?"
핵심 정리
create_retriever_tool(retriever, name, description)으로 검색을 도구로 만듭니다.- 세 번째 인자가 독스트링 자리입니다. 언제 쓰는지 구체적인 예를 나열합니다.
- 도구용 검색기는
k=6으로 넉넉히 잡았습니다. 8장에서 두 주제를 한 번에 묻는 일이 있기 때문입니다. - 같은 인덱스에서 검색기를 여러 개 만들 수 있습니다.
answer()는 항상 검색하고,policy_tool은 모델이 필요하다고 판단할 때만 불립니다.- 도구로 만들면 출처 표시가 사라집니다. 되찾으려면 반환 문자열에 출처를 넣어 직접 도구를 만듭니다.
- 네 종류가 전부 도구가 됐습니다. 이제 한 명에게 다 줄지, 나눠 줄지가 8장의 주제입니다.