에이전트의 해부학

지난 장에서 루프 + 도구 + 자율 종료라고 정리했다. 이번에는 그 루프를 열어 본다. , , , 인터페이스 — 네 개의 부품으로 잘려 나간다.


네 부품 한눈에

한 장의 다이어그램부터 본다. 안에서 가 다음 단계를 떠올리고, 가 그 단계를 호출로 옮긴다. 결과는 에 기록되고, 다음 사이클의 입력이 된다.

다이어그램 로딩…

이 네 개를 분리해 보지 못하면 디버깅이 안 된다. 어디가 틀렸는지 짚을 수 있어야 고칠 수 있다.


플래너의 역할

는 사용자 목표를 받아 다음 한 수를 정한다. 거창한 계획 트리를 미리 만드는 게 아니라, 매 사이클마다 “지금 시점에서 무엇을 해야 하는가”를 한 줄로 답한다.

대부분의 에이전트는 별도 모듈을 두지 않는다. 그냥 같은 를 잘 짜서 시키면 된다. 핵심은 명시적으로 끌어내는 것 — “다음에 무엇을 해야 하는가”를 모델이 직접 쓰게 만든다.


실행기의 역할

가 “도시 기온 가져오기”라고 말했으면, 실제로 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. 사이클 1 — search_docs(query="신제품 출시")를 결정.
  2. 사이클 2 — 검색 결과를 보고 send_email(to="팀장", body="요약…")를 결정.
  3. 사이클 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,
)

결정 루프의 종료 조건들

언제 멈출지도 설계 포인트다. 보통 세 가지 종료 조건을 함께 둔다.

  1. 모델 종료stop_reason == "end_turn". 모델이 충분하다고 판단한 정상 종료.
  2. 최대 사이클 한도while을 무한히 돌리지 않도록 카운터로 강제 종료.
  3. 에러 한도가 N회 연속 실패하면 사람·상위 노드로 에스컬레이트.

도구 스키마와 JSON Schema의 관계

는 JSON Schema의 부분 집합을 빌려 쓴다. Anthropic의 input_schema, OpenAI의 parameters, MCP의 inputSchema — 이름은 달라도 본질은 같다. 의 명세는 결국 같은 언어로 적힌다.

핵심 키워드 다섯 개만 알면 거의 다 커버된다 — type, properties, required, enum, description. description은 단순 문서가 아니라 이 인자 값을 추론할 때 보는 입력이다. 빈약하게 적으면 모델이 헷갈린다.


자율성의 정도

해부학을 알면 얼마나 자율적이게 만들지가 손잡이라는 게 보인다. 은 0/1이 아니라 스펙트럼이다.

단계
워크플로사람이 단계 고정단순 실행정해진 순서
라우터모델이 분기 1번1회 호출분기 트리
풀 ReAct모델이 매 단계 결정반복 호출모델이 종료까지

대부분의 프로덕션은 풀 ReAct가 아닌 제한된 자율성이다. 단순하고 디버깅이 쉬워서.


다음 장으로

부품을 알고 나니 깔끔해 보인다. 그런데 실제로 굴려 보면 곧 한계가 드러난다. 이 넓어지면 가 헤매고, 가 길어지면 이 흐려진다.

다음 장에서는 단일 의 한계 — 컨텍스트 오염, 도구 폭증, 역할 과부하 — 를 실제 시나리오로 재현해 본다.