에이전트의 해부학
지난 장에서 는 루프 + 도구 + 자율 종료라고 정리했다. 이번에는 그 루프를 열어 본다. , , , 인터페이스 — 네 개의 부품으로 잘려 나간다.
네 부품 한눈에
한 장의 다이어그램부터 본다. 안에서 가 다음 단계를 떠올리고, 가 그 단계를 호출로 옮긴다. 결과는 에 기록되고, 다음 사이클의 입력이 된다.
이 네 개를 분리해 보지 못하면 디버깅이 안 된다. 어디가 틀렸는지 짚을 수 있어야 고칠 수 있다.
플래너의 역할
는 사용자 목표를 받아 다음 한 수를 정한다. 거창한 계획 트리를 미리 만드는 게 아니라, 매 사이클마다 “지금 시점에서 무엇을 해야 하는가”를 한 줄로 답한다.
대부분의 에이전트는 별도 모듈을 두지 않는다. 그냥 같은 에 를 잘 짜서 시키면 된다. 핵심은 을 명시적으로 끌어내는 것 — “다음에 무엇을 해야 하는가”를 모델이 직접 쓰게 만든다.
실행기의 역할
가 “도시 기온 가져오기”라고 말했으면, 실제로 get_weather("Seoul")을 부르는 건 다. 도구 이름 매칭, 인자 파싱, 실패 처리 — 모두 여기서 일어난다.
입장에서는 가 얇은 디스패처에 가깝다. 30줄을 넘기지 않는 게 좋다. 진짜 비즈니스 로직은 함수 안에 둔다. 이 단순해야 디버깅이 산다.
도구 스키마 — JSON Schema와 직결
는 단순한 함수가 아니다. 이 호출 인자를 생성하려면, 함수 명세가 모델이 읽을 수 있는 포맷이어야 한다. 그게 — 결국 을 정의하는 JSON Schema다.
# Verified against: https://platform.claude.com/docs/en/docs/build-with-claude/tool-use
# Verified at: 2026-06-02
tool_schema = {
"name": "search_docs",
"description": "사내 문서를 키워드로 검색해 상위 3건 반환",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "검색 키워드"},
"top_k": {"type": "integer", "minimum": 1, "maximum": 10, "default": 3},
},
"required": ["query"],
},
}description이 모델이 언제 이 도구를 부를지 정하는 단서다. 한 줄짜리 설명이 정확도를 좌우한다.
스크래치패드 — 짧은 기억의 보관소
는 사이클 동안 모인 ··도구 결과를 차곡차곡 쌓아 두는 영역이다. Anthropic API에서는 별도 필드가 아니라 messages 배열에 차례로 쌓이는 메시지들 자체가 스크래치패드 역할을 한다.
쌓이는 순서는 보통 이렇다 — user(질문) → assistant(reasoning+tool_use) → user(tool_result) → assistant(reasoning+tool_use) → .... 매 사이클마다 모델은 이 전체를 다시 본다. 은 단순하지만, 그만큼 누적된다.
통합 — 4부품으로 짠 에이전트
이제 네 부품을 한 함수에 묶는다. 함수 자체는 짧다. 어디가 고 어디가 고 어디가 인지 주석으로 표시한다. 두 개를 가진 다.
# Verified against: https://platform.claude.com/docs/en/docs/build-with-claude/tool-use
# Verified at: 2026-06-02
from anthropic import Anthropic
client = Anthropic()
TOOLS = [
{"name": "search_docs", "description": "문서 검색",
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}},
{"name": "send_email", "description": "이메일 전송",
"input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "body": {"type": "string"}}, "required": ["to", "body"]}},
]
def executor(name, args): # 실행기
if name == "search_docs": return f"검색 결과: {args['query']} 관련 3건"
if name == "send_email": return f"전송 완료 → {args['to']}"
return "unknown"
def agent(goal: str) -> str:
scratchpad = [{"role": "user", "content": goal}] # 스크래치패드
while True:
resp = client.messages.create( # 플래너 = LLM 호출
model="claude-sonnet-4-6", max_tokens=512, tools=TOOLS, messages=scratchpad,
)
if resp.stop_reason == "end_turn":
return next(b.text for b in resp.content if b.type == "text")
scratchpad.append({"role": "assistant", "content": resp.content})
use = next(b for b in resp.content if b.type == "tool_use")
result = executor(use.name, use.input)
scratchpad.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": use.id, "content": result}
]})한 사이클을 늘려 보기
가 도는 모습을 한 케이스로 따라간다. 사용자가 “신제품 출시 문서 찾아서 팀장에게 메일 보내줘”라고 했다고 치자. 는 두 개를 차례로 부른다.
- 사이클 1 — 가
search_docs(query="신제품 출시")를 결정. - 사이클 2 — 검색 결과를 보고
send_email(to="팀장", body="요약…")를 결정. - 사이클 3 — 두 도구가 끝났음을 보고 자연어 응답으로 종료.
이 흐름이 곧 의 실전 모습이다.
시스템 프롬프트가 결정하는 행동 공간
는 모델의 을 좁히는 가장 효과적인 손잡이다. “도구를 부르기 전에 추론을 한 줄 적어라”, “확실하지 않으면 사용자에게 되물어라” 같은 짧은 규칙 몇 줄이 을 크게 바꾼다.
# Verified against: https://platform.claude.com/docs/en/api/messages
# Verified at: 2026-06-02
SYSTEM = """너는 사내 문서 도우미 에이전트다.
- 도구를 부르기 전에 한 줄 추론을 적는다.
- 확실하지 않으면 search_docs로 먼저 확인한다.
- 외부 메일은 사용자 확인 후 보낸다.
"""
client.messages.create(
model="claude-sonnet-4-6", max_tokens=512,
system=SYSTEM, tools=TOOLS, messages=scratchpad,
)결정 루프의 종료 조건들
를 언제 멈출지도 설계 포인트다. 보통 세 가지 종료 조건을 함께 둔다.
- 모델 종료 —
stop_reason == "end_turn". 모델이 충분하다고 판단한 정상 종료. - 최대 사이클 한도 —
while을 무한히 돌리지 않도록 카운터로 강제 종료. - 에러 한도 — 가 N회 연속 실패하면 사람·상위 노드로 에스컬레이트.
도구 스키마와 JSON Schema의 관계
는 JSON Schema의 부분 집합을 빌려 쓴다. Anthropic의 input_schema, OpenAI의 parameters, MCP의 inputSchema — 이름은 달라도 본질은 같다. 의 명세는 결국 같은 언어로 적힌다.
핵심 키워드 다섯 개만 알면 거의 다 커버된다 — type, properties, required, enum, description. description은 단순 문서가 아니라 이 인자 값을 추론할 때 보는 입력이다. 빈약하게 적으면 모델이 헷갈린다.
자율성의 정도
해부학을 알면 얼마나 자율적이게 만들지가 손잡이라는 게 보인다. 은 0/1이 아니라 스펙트럼이다.
| 단계 | |||
|---|---|---|---|
| 워크플로 | 사람이 단계 고정 | 단순 실행 | 정해진 순서 |
| 라우터 | 모델이 분기 1번 | 1회 호출 | 분기 트리 |
| 풀 ReAct | 모델이 매 단계 결정 | 반복 호출 | 모델이 종료까지 |
대부분의 프로덕션은 풀 ReAct가 아닌 제한된 자율성이다. 단순하고 디버깅이 쉬워서.
다음 장으로
부품을 알고 나니 깔끔해 보인다. 그런데 실제로 굴려 보면 곧 한계가 드러난다. 이 넓어지면 가 헤매고, 가 길어지면 이 흐려진다.
다음 장에서는 단일 의 한계 — 컨텍스트 오염, 도구 폭증, 역할 과부하 — 를 실제 시나리오로 재현해 본다.