create_agent 한 줄이 대신해 주는 것
한 줄 요약
create_agent(llm, tools, system_prompt) 한 줄이 4장의 루프 30줄을 대신합니다. 무엇을 대신해 주고 무엇은 여전히 우리 일인지 분명히 해 둡니다.
1. 한 줄
아래는 6절에서 만들 code/ch05_csv_agent.py 의 일부를 미리 보는 것입니다 — 지금 붙여 넣지 않아도 됩니다.
from langchain.agents import create_agent
llm = get_chat(temperature=0)
agent = create_agent(llm, tools=[run_pandas], system_prompt=SYSTEM)
인자 셋입니다.
| 인자 | 무엇 | 4장에서는 |
|---|---|---|
| 첫 번째 | 모델 객체 | client + GEMINI_MODEL |
tools |
도구 목록 | TOOLS 딕셔너리 |
system_prompt |
시스템 지시 | config 의 system_instruction |
4장에서 config 에 넣던 것과 같은 재료입니다. 다만 루프는 안 짭니다.
2. 부르는 법
아래는 6절에서 만들 code/ch05_csv_agent.py 의 일부를 미리 보는 것입니다 — 지금 붙여 넣지 않아도 됩니다.
def ask(question: str) -> str:
"""에이전트에게 한국어로 묻고 답을 받는다."""
out = agent.invoke(
{"messages": [{"role": "user", "content": question}]},
# 4장의 MAX_STEPS 에 해당한다. 단위가 달라서(노드 1회 = 1) 두 배쯤 잡는다.
# 안 넣으면 기본값이 10007 이라 사실상 무제한이다.
{"recursion_limit": 10},
)
return out["messages"][-1].text.strip() # 마지막 메시지가 최종 답변
두 번째 인자 {"recursion_limit": 10} 은 반복 상한입니다. 6번에서 따로 봅니다. 지금은 첫 번째 인자만 보세요.
넣는 것
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
{"messages": [{"role": "user", "content": question}]}
messages 라는 키에 대화 목록을 넣습니다. 2장의 history 와 같은 자리입니다.
지금은 한 개짜리지만, 7장에서 이 목록이 자동으로 쌓이게 됩니다. 그게 멀티턴이죠.
받는 것
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
out["messages"][-1].text
돌아오는 것도 messages 목록입니다. 질문 하나를 넣었는데 여러 개가 돌아옵니다.
out["messages"] = [
HumanMessage("카테고리별 매출 상위 3개는?"),
AIMessage(도구 호출 요청), ← 모델의 결정
ToolMessage("가전 90229700\n전자기기 ..."), ← 도구 실행 결과
AIMessage("가전제품 90,229,700원, ..."), ← 최종 답변
]
4장에서 우리가 history 에 쌓던 것과 똑같습니다. 라이브러리가 대신 쌓아 준 것을 통째로 돌려줍니다.
평범한 함수 하나가 무엇이 되어 어디로 들어가고, 무엇이 되어 돌아오는지 하나의 흐름으로 살펴보세요.
넣는 것은 하나인데 받는 것은 네 개 — 그 사이에 4장의 루프가 통째로 들어 있기 때문입니다.
그래서 마지막 것([-1])이 최종 답변입니다.
여기서 4장을 직접 구현해 본 보람을 느낄 수 있습니다.
out["messages"]안에 왜 4개가 들어 있는지,ToolMessage가 무엇인지 — 손으로 쌓아 본 사람에게는 설명이 필요 없습니다.
3. 안에서 무슨 일이 일어나나
create_agent 가 만든 것은 사실 그래프입니다.
4장의 for 루프와 같은 그림입니다. if not resp.function_calls: return 이 "없다 → 끝" 화살표죠.
그래프(graph)란 — 할 일을 상자로 그리고 "다음엔 어디로"를 화살표로 이어 놓은 것입니다. 위 그림이 그래프입니다 — 상자 둘과 갈림길 하나. 라이브러리 이름 LangGraph 의 "Graph"가 이 뜻입니다.
그리고 상자 하나하나를 노드(node)라고 부릅니다. 위 그림에는
[모델]과[도구실행], 노드가 둘입니다. 한 바퀴는 노드 두 개를 지나므로, 4장의 "1단계" = 노드 2회입니다. 6절의recursion_limit표가 이 셈에서 나옵니다."그래프"라는 말이 나온 이유 —
create_agent는 LangGraph 위에 만들어져 있습니다. 7장에서 우리가 직접 그래프를 조립할 때, "아,create_agent도 이런 거였구나" 하게 됩니다. 그때 이 그림을 다시 보게 됩니다.
4. 무엇을 대신해 주나
| 4장에서 손으로 | create_agent 가 |
|---|---|
history = [question] |
자동 |
for step in range(...) |
자동 |
contents=history 로 매번 전송 |
자동 |
if not resp.function_calls 판정 |
자동 |
history.append(모델의 결정) |
자동 |
TOOLS[fc.name](**args) 실행 |
자동 |
Part.from_function_response 기록 |
자동 |
| 도구 실행 중 오류 처리 | 자동 (오류 메시지를 모델에게 전달) |
루프 관리 전부입니다.
5. 무엇은 여전히 우리 일인가
이쪽이 더 중요합니다.
| 여전히 사람이 | 왜 |
|---|---|
| 어떤 도구를 만들 것인가 | 라이브러리는 도구를 만들어 주지 않는다 |
| 도구 안의 로직 | pandas 식을 어떻게 실행할지 |
| 독스트링 | 모델이 읽는 설명서 |
| 시스템 지시 | 업무 규칙 |
| 안전장치 | 위험한 코드를 어떻게 막을지 (다음다음 절) |
| 반복 상한 | recursion_limit 로 지정 |
| 결과 검증 | 나온 숫자가 말이 되는지 |
"돌아가는데 왜 알아야 하나"에 대한 답이 여기 있습니다.
create_agent는 루프를 대신해 주지, 판단을 대신해 주지 않습니다. 도구를 잘못 만들면 잘못된 답이 아주 매끄럽게 나옵니다. 그리고 그건 라이브러리 탓이 아닙니다.
6. 반복 상한 걸기
4장의 MAX_STEPS 에 해당하는 것입니다. 실습 파일에 이미 들어 있습니다.
아래는 code/ch05_csv_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
out = agent.invoke(
{"messages": [{"role": "user", "content": question}]},
# 4장의 MAX_STEPS 에 해당한다. 단위가 달라서(노드 1회 = 1) 두 배쯤 잡는다.
# 안 넣으면 기본값이 10007 이라 사실상 무제한이다.
{"recursion_limit": 10},
)
하는 일은 비슷하지만 세는 단위가 다릅니다.
| 무엇을 세나 | |
|---|---|
4장 MAX_STEPS |
루프 한 바퀴 (모델 호출 + 도구 실행) |
recursion_limit |
노드 실행 횟수 — 모델 1 + 도구 1 = 2 |
그래서 MAX_STEPS=5 에 해당하는 값이 recursion_limit=10 입니다.
안 넣으면 기본값이 10007 입니다. "무한 반복은 안 된다"는 말은 맞지만, 도구가 계속 오류를 내며 반복 호출되면 한 질문에 모델을 5,000번쯤 부를 수 있습니다. 요금 관점에서는 무한과 다를 바 없죠.
그래서 실습 코드에 넣어 두었습니다. 4장에서
MAX_STEPS를 손으로 걸어 본 사람만 이 설정의 이름을 보고 무엇에 대응하는지 압니다.
7. 중간 단계를 다시 보고 싶다면
out["messages"] 를 전부 찍으면 됩니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
out = agent.invoke({"messages": [{"role": "user", "content": q}]})
for m in out["messages"]:
print(f" {type(m).__name__:14s} {str(m.content)[:70]}")
HumanMessage 카테고리별 매출 상위 3개를 알려주세요.
AIMessage
ToolMessage category
가전 90229700
전자기기 53967950
스포츠레저 53259600
AIMessage 가전제품 90,229,700원, 전자기기 53,967,950원, 스포츠/레저 53,259,600원으로 ...
4장의 단계별 출력과 같은 정보입니다. 두 번째 AIMessage 의 content 가 비어 있는 것도 눈여겨보세요 — 그 메시지는 글이 아니라 도구 호출 요청을 담고 있습니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
print(out["messages"][1].tool_calls)
[{'name': 'run_pandas', 'args': {'expr': "orders[~orders['status']..."}, ...}]
3장의 resp.function_calls 와 같은 것입니다. 이름만 tool_calls 로 바뀌었습니다.
디버깅할 때 여기를 보세요. 에이전트가 이상한 답을 내면
tool_calls를 찍어 모델이 어떤 식을 만들었는지 확인하는 것이 가장 빠릅니다.
핵심 정리
create_agent(모델, tools=[...], system_prompt=...)한 줄이 4장의 루프 30줄을 대신합니다.- 부를 때는
agent.invoke({"messages": [...]}), 답은out["messages"][-1].text. - 돌아오는
messages에는 질문 · 도구 호출 요청 · 실행 결과 · 최종 답변이 전부 들어 있습니다. 4장의history와 같습니다. create_agent는 내부적으로 그래프입니다 — 상자(노드)와 화살표로 그린 흐름. 7장에서 직접 조립하게 됩니다.- 대신해 주는 것은 루프 관리 전부입니다.
- 여전히 사람의 일 — 도구 구성, 도구 로직, 독스트링, 시스템 지시, 안전장치, 결과 검증.
- 반복 상한은
{"recursion_limit": 10}으로 겁니다. 세는 단위가 달라MAX_STEPS=5≈recursion_limit=10이고, 기본값이 10007 이라 명시하지 않으면 사실상 무제한입니다. - 중간 단계는
out["messages"]와.tool_calls로 다시 볼 수 있습니다. 디버깅의 출발점입니다.