단일 에이전트의 한계
지금까지 본 에이전트는 깔끔했다. 하나, 두 개, 짧은 루프. 그런데 실제 제품에 붙이면 곧 깨진다. 이 장에서는 왜 깨지는지 세 갈래로 들여다본다 — , , .
한 명에게 다 시키면 무엇이 생기는가
엔지니어 한 명에게 “기획·설계·구현·테스트·배포·CS” 다 맡기면 어떻게 되는지 상상해 본다. 한 사람의 작업 기억은 좁고, 한 머리로 처리 가능한 행동 종류도 한정돼 있다. 도 똑같다.
세 가지 실패 양상이 거의 항상 함께 등장한다. 으로 컨텍스트가 흐려지고, 으로 선택이 망가지고, 로 어느 역할도 잘 못한다. 이 셋이 단일 에이전트의 천장이다.
컨텍스트 오염이 일어나는 메커니즘
은 이전 사이클의 잘못된 관찰이 컨텍스트에 박혀서, 이후 이 그 잘못을 전제로 깔리는 현상이다. 는 단순한 메모가 아니라 모델이 매번 다시 읽는 입력이라는 점이 핵심이다.
한 번 잘못 부른 도구의 응답이 다음 사이클의 으로 흘러간다. 그 추론이 또 잘못된 도구를 부른다. 의 출발점이 여기다.
컨텍스트는 메모리가 아니라 증거다. 잘못된 증거 한 줄이 판결을 뒤집는다.
컨텍스트 오염을 재현하기
도구가 일부러 조금 틀린 답을 주는 상황을 만들어 본다. 는 그 틀린 답을 사실로 받아들이고 이어 간다. 이 코드로 이 어떻게 누적되는지 눈으로 본다.
# 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": "lookup_price",
"description": "상품 가격 조회",
"input_schema": {"type": "object",
"properties": {"sku": {"type": "string"}},
"required": ["sku"]},
}]
def buggy_executor(name, args):
# 일부러 잘못된 응답 — 컨텍스트 오염 시연
if name == "lookup_price":
return f"{args['sku']} 가격: 1,000,000원 (실제는 10만원)"
return "?"
def trace_agent(goal):
msgs = [{"role": "user", "content": goal}]
for step in range(5):
r = client.messages.create(
model="claude-sonnet-4-6", max_tokens=400, tools=TOOLS, messages=msgs,
)
print(f"[step {step}] stop={r.stop_reason}")
if r.stop_reason == "end_turn":
return next(b.text for b in r.content if b.type == "text")
msgs.append({"role": "assistant", "content": r.content})
u = next(b for b in r.content if b.type == "tool_use")
msgs.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": u.id, "content": buggy_executor(u.name, u.input)}
]})
print(trace_agent("A-100 SKU 사면 예산 50만원 안에 들어가?"))모델은 1,000,000원이라는 잘못된 을 받고, 그걸 사실로 “예산 초과”라고 결론을 낸다. 사람이라면 한 번 의심해 봤겠지만, 단일 는 의심할 상대가 없다.
환각 연쇄 — 잘못이 줄줄이
는 잘못된 한 줄이 다음 추론을 오염시키고, 그 추론이 또 잘못된 을 만드는 도미노다. 이 정적인 상태라면, 환각 연쇄는 그 상태가 시간에 따라 확산되는 동작이다.
- 사이클 1: 가격을 잘못 조회 → 컨텍스트에 잘못된 가격이 박힘.
- 사이클 2: 잘못된 가격을 보고 “할인 쿠폰을 찾아 적용하자”고 결정 → 불필요한 호출.
- 사이클 3: 쿠폰 적용 결과를 보고 또 다른 잘못된 추론…
길어질수록 회복이 어렵다. 한 번 어긋난 길은 모델이 자기 답을 본 뒤에는 더 강해진다.
도구 폭증의 그래프
은 등록 수가 늘수록 정확도가 떨어지는 현상이다. 단순히 “옵션이 많아 헷갈린다”가 아니라, 이 넓어진 만큼 잘못된 도구에 확률이 분산된다.
경험상 도구 5개 안에서는 거의 빠짐없이 옳은 도구를 고른다. 10개 넘어가면 비슷한 두 도구 사이에서 흔들리기 시작하고, 30~50개를 넘기면 이름이 비슷한 도구를 잘못 부르는 빈도가 급증한다.
도구 5개 vs 50개 비교 실험
이 효과를 코드로 확인하는 방법은 간단하다. 같은 를 5개·50개 환경에서 N회 돌려 정답률을 비교하면 된다. 50개는 비슷한 이름의 더미를 채워 넣는다. 이 어떻게 정답률을 깎아 먹는지 직접 보여 주는 실험이다. 의 정량 증거다.
# 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()
def make_tools(n):
base = [{
"name": "get_user_email",
"description": "사용자 ID로 이메일을 조회",
"input_schema": {"type": "object",
"properties": {"user_id": {"type": "string"}},
"required": ["user_id"]},
}]
# 비슷한 이름의 디스트랙터 도구로 채우기
for i in range(n - 1):
base.append({
"name": f"get_user_field_{i}",
"description": f"사용자 ID로 임의 속성 {i}를 조회",
"input_schema": {"type": "object",
"properties": {"user_id": {"type": "string"}},
"required": ["user_id"]},
})
return base
def picked_correct(n_tools):
r = client.messages.create(
model="claude-sonnet-4-6", max_tokens=300,
tools=make_tools(n_tools),
messages=[{"role": "user", "content": "user_id=u42 의 이메일 줘"}],
)
use = next((b for b in r.content if b.type == "tool_use"), None)
return use and use.name == "get_user_email"
# 도구 5개·50개에서 각 20회 시도해 정답률 비교
for n in (5, 50):
correct = sum(picked_correct(n) for _ in range(20))
print(f"tools={n}: {correct}/20")결과 패턴은 일관된다 — 5개는 거의 만점, 50개는 정답률이 눈에 띄게 떨어진다. 체감상 절반 가까이 깎이는 케이스도 흔하다.
토큰 한도와 비용의 천장
는 단순한 숫자가 아니다. 는 한도 안에서만 이 일관성을 유지한다는 질적 의미도 함께 갖는다.
길어진 컨텍스트 = 비싼 호출 = 느린 응답. 이 세 가지가 한꺼번에 온다. 게다가 모델은 컨텍스트 중간 부분의 정보를 잘 못 본다는 보고도 일관되게 나온다. “lost in the middle” 현상이다. 단일 는 모든 정보를 한 컨텍스트에 욱여넣어야 하니, 이 천장에 가장 빨리 부딪힌다.
역할 과부하 — 한 명에게 너무 많은 모자
는 한 가 “코드 작성자 + 코드 리뷰어 + 보안 점검자 + 문서 작성자” 같은 상충하는 역할을 동시에 떠맡을 때 일어난다. 가 길어질수록 어느 역할도 제대로 못 한다.
특히 비평가 역할은 자기가 방금 쓴 코드를 자기가 평가해야 한다. 인간도 잘 못하는 일이다. 이 일어나기 시작한다 — 한 역할의 톤이 다른 역할 답변에 새어 들어간다.
단일 실패점
은 시스템 가용성 관점의 한계다. 가 하나밖에 없으면 그 모델 호출이 실패하거나 응답이 늦으면 전체가 멈춘다.
특히 안에서 한 사이클이 실패하면 그동안 쌓인 가 통째로 날아간다. 재시도해도 같은 컨텍스트에서 같은 오류가 반복되기 쉽다. 한 시스템은 루프가 끊기지 않게 설계해야 하는데, 단일 에이전트는 그 보장을 줄 수 없다.
세 한계가 함께 오는 이유
세 한계는 따로 떨어진 문제처럼 보이지만, 같은 뿌리를 공유한다 — 모든 것을 한 컨텍스트, 한 모델, 한 역할에 쑤셔 넣었기 때문이다.
| 한계 | 뿌리 |
|---|---|
| 모든 을 한 컨텍스트에 누적 | |
| 모든 를 한 에 노출 | |
| 모든 역할을 한 에 합침 | |
| 모든 책임이 한 에 집중 |
분리하면 푼다. 그게 멀티 의 출발점이다.
다음 장으로
세 한계를 진단했다. 이제 처방이다. 한 명에게 다 시키면 망한다는 결론이 나왔다면, 답은 분명하다 — 나눈다.
다음 장에서는 를 둘 이상으로 쪼개고 역할을 좁히는 멀티 에이전트 아키텍처가 어떻게 와 을 동시에 푸는지 본다.