Tool Call 루프와 Multi-Agent 패턴
Tool Call의 본질
모델은 함수를 직접 실행하지 못한다. 대신 응답에 "이 함수를 이 인자로 호출해줘"라는 요청을 담아 돌려보낸다. 실행은 항상 클라이언트(우리) 몫이고, 결과를 다시 messages에 담아 보내줘야 모델이 다음 판단을 할 수 있다.
Tool Call 루프 구조
while True:
response = api.call(messages)
if response.finish_reason == "stop":
break # 모델이 최종 응답 완료
if response.finish_reason == "tool_calls":
for tool_call in response.tool_calls:
result = execute(tool_call)
messages.append(assistant_message) # 모델 응답 추가
messages.append(tool_result_message) # 실행 결과 추가
# 루프 계속 → 모델이 결과 보고 다음 판단
흔한 버그
- 무한루프: 루프 안에서
response를 갱신하지 않으면, 루프 조건을 항상 같은 오래된 응답으로 평가하게 된다. - 불필요한 이중 호출: 루프 종료 후 다시 API를 호출하면, 이미 완료된 응답을 한 번 더 만들게 된다.
Orchestrator-Agent 패턴
Single agent는 하나의 messages 배열이 계속 자라는 구조다. 주제가 섞이면 모델이 이전 맥락을 끌고 다녀서 노이즈가 생긴다.
Multi-agent는 agent마다 독립된 context를 가진다:
Orchestrator
│
├── run_agent(task_A, tools) → result_A
├── run_agent(task_B, tools) → result_B
└── aggregate(result_A, result_B) → 최종 출력
run_agent()는 독립된 messages 배열을 가진 Tool Call 루프다. 호출할 때마다 새 context에서 시작한다.
설계에서 핵심 결정 3가지
1. Context 공유 vs 격리
- agent 간: 격리 (각자 독립된 맥락)
- Orchestrator: agent 결과를 모두 수집해서 통합
2. Sequential vs Parallel
| 패턴 | 언제 | Python |
|------|------|--------|
| Sequential | 앞 결과가 다음 input이 될 때 | 순서대로 await |
| Parallel | agent들이 독립적일 때 | asyncio.gather() |
다중 주제 검색처럼 서로 의존성이 없는 경우는 parallel이 자연스럽다.
3. 실패 처리 경계
- agent 내부에서 1차 처리 (tool call 에러, timeout 등)
- Orchestrator는 "이 agent 전체가 실패했다"만 알면 충분
- 세부 에러를 Orchestrator까지 올리면 orchestration 로직이 복잡해진다
run_agent() 추상화
def run_agent(task: str, tools: list, system_prompt: str = "") -> str:
messages = [{"role": "user", "content": task}]
if system_prompt:
messages.insert(0, {"role": "system", "content": system_prompt})
while True:
response = client.chat.completions.create(
model=MODEL, messages=messages, tools=tools
)
if response.choices[0].finish_reason == "stop":
return response.choices[0].message.content
# tool call 처리 후 messages에 추가
...
Orchestrator는 이걸 여러 번 호출하는 역할만 한다.
다음 단계
model_test에 적용할 때 결정할 포인트:
- 검색 agent들을 parallel로 돌릴지 sequential로 돌릴지
- 에러 처리 경계를 어디에 둘지
- Orchestrator가 결과를 어떻게 통합할지 (단순 concatenation vs 모델에 한 번 더 위임)
→ 구체 설계는 설계하자 프로젝트에서
Sequential에서 Context는 정말 커지나?
Single agent sequential은 하나의 messages 배열에 모든 대화가 쌓여서 진짜로 커진다.
Multi-agent sequential은 다르다. 각 agent는 독립된 messages 배열로 시작하고, 이전 agent의 내부 사고 과정과 tool call 왕복은 버려진다. 다음 agent에게 넘어가는 건 압축된 결과만이다.
Agent A
messages: [task_A + tool call 왕복...]
→ result_A (최종 출력만 추출)
Agent B
messages: [result_A를 input으로, task_B...] ← A의 내부는 없음
→ result_B
핵심 Trade-off: 다음 agent에게 뭘 얼마나 넘길 것인가
| 넘기는 것 | context 크기 | 정보 보존 | |-----------|-------------|----------| | 결론만 | 작음 | 손실 위험 | | 중간 추론 포함 | 중간 | 부분 보존 | | 전체 raw output | 큼 | 사실상 single agent |
판단 기준: 다음 agent가 앞 agent의 결론만 필요한가, 과정도 필요한가
task 성격이 이 결정을 이끈다. 설계 시 태스크별로 따져야 한다.