두 에이전트가 서로 문자열을 전달하는 것은 멀티 에이전트 시스템이 아니라 파이프라인이며, 이 차이는 중요합니다
요약
단순히 에이전트 간에 문자열을 전달하는 구조는 멀티 에이전트 시스템이 아닌 파이프라인에 가깝다는 점을 지적합니다. 진정한 멀티 에이전트 시스템을 구축하기 위해 필요한 아키텍처적 경계와 통신 방식의 중요성을 다룹니다.
핵심 포인트
- 단순 문자열 전달은 멀티 에이전트가 아닌 파이프라인임
- 에이전트 간 통신에는 공유 상태나 프로토콜이 필요함
- 단일 에이전트보다 루프를 통한 결과물 개선이 가능함
- 아키텍처 설계 시 에이전트 간 경계 설정이 핵심임
이전 두 개의 포스트에서 저는 최소한의 Claude 에이전트(Module 1)를 구축했고, 그 다음에는 여러 도구(Module 2)를 부여했습니다. 이것은 Module 3로, 첫 번째 에이전트의 작업물을 비평하고 승인될 때까지 루프를 도는 두 번째 에이전트를 추가하는 단계입니다. 시스템은 작동합니다. 결과물은 단일 에이전트(single-agent)보다 더 낫습니다. 하지만 이를 구축하면서 "멀티 에이전트 (multi-agent)"라는 단어가 실제로 무엇을 보장하는지에 대한 제 생각이 바뀌었으며, 실제 아키텍처(architectural) 상의 경계가 어디에 위치하는지 구체적으로 짚어보고 싶습니다.
설정 (The setup)
서로 다른 시스템 프롬프트(system prompt)를 사용하여 각각 단일 Anthropic API 호출을 수행하는 두 개의 Python 함수입니다:
def run_designer(game_idea: str, criticism: str = None) -> str:
if criticism:
messages = [
{"role": "user", "content": f"Design a game based on this idea: {game_idea}"},
{"role": "assistant", "content": "I'll design this game now..."},
{"role": "user", "content": f"A critic reviewed your design and said: {criticism}\n\nRevise the design addressing all criticism points."}
]
else:
messages = [
{"role": "user", "content": f"Design a game based on this idea: {game_idea}"}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
...
def run_critic(design: str) -> tuple[str, bool]:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="You are a brutal but fair game design critic. [...]. At the end of your review you MUST write either: VERDICT: APPROVED or VERDICT: NEEDS REVISION",
messages=[{"role": "user", "content": f"Review this game design:\n\n{design}"}]
)
review = response.content[0].text
approved = "VERDICT: APPROVED" in review
return review, approved
그리고 메인 루프:
pythoncriticism = None
for round_num in range(1, max_rounds + 1):
design = run_designer(game_idea, criticism)
review, approved = run_critic(design)
if approved:
save_final_design(game_idea, design)
break
criticism = review
이 단 하나의 줄 — criticism = review — 이 이 시스템의 "에이전트 간 통신"입니다. 비평가(Critic)의 응답 텍스트가 다음 반복에서 디자이너(Designer)의 입력 일부가 됩니다. 그 외에는 아무것도 없습니다. 공유된 상태도, 메시지 버스도, 프로토콜도, 오케스트레이터도 없습니다.
이것은 작동하며, 저는 이에 대해 솔직해지고 싶습니다.
제 Krenholm 테스트 실행에서 비평가는 1라운드와 2라운드를 거부하고 3라운드를 승인했습니다. 최종 문서는 Module 2가 생성한 것보다 의미 있게 더 좋았습니다 — 더 날카로운 범위 결정, 제작 제약 조건을 테마적 선택으로 재구성하는 영리함, 그리고 더 간결해진 주요 메커니즘이었습니다. 비평가의 리뷰는 실제 문제를 드러냈습니다. 디자이너의 수정 사항은 원본 초안을 방어하기보다는 실제로 그 문제들을 다루었습니다.
전문가 프롬프트가 서로를 검토하는 것에는 진정한 가치가 있습니다. 실제 문제를 찾도록 명시적인 지침이 있는 "비평가" 프롬프트는 같은 모델에게 하나의 프롬프트 내에서 자체 검토를 요청하는 것보다 더 유용한 피드백을 생성합니다. 이것은 실제로 유용하고 효과적인 패턴입니다.
또한, 기계적으로 볼 때 두 개의 상태 비저장(stateless) API 호출의 파이프라인입니다.
"멀티 에이전트(multi-agent)"라는 용어가 제가 구축한 것보다 더 큰 개념이었던 곳
이전에는 "멀티 에이전트 시스템"이라는 것을 읽을 때, 저는 다음과 같은 것을 의미한다고 가정했습니다:
서로 독립적인 내부 상태 (Internal state)를 유지하는 에이전트들
에이전트 간의 어떤 형태든 존재하는 통신 프로토콜 (Communication protocol)
특정 에이전트 내부에 존재하는 것이 아니라 에이전트들 사이에 존재하는 조정 로직 (Coordination logic)
흔히 나타나는 특징: 병렬성 (Parallelism), 동적 에이전트 생성 (Dynamic agent creation), 창발적 집단 행동 (Emergent collective behaviour)
이 시스템이 실제로 가지고 있는 것:
상태가 없음. 각 API 호출은 독립적입니다. 대화 기록 (Conversation history)은 매 반복마다 제가 새로 구성하는 Python 리스트일 뿐입니다.
프로토콜이 없음. 한 에이전트의 출력은 문자열 (String)입니다. 다음 에이전트는 문자열을 받습니다. 문자열 형식은 제가 f-string에 입력한 그대로입니다.
에이전트 간의 조정 로직이 없음. for 루프가 곧 조정 역할을 합니다. 이는 순차적으로 실행되며 하나의 키워드만을 확인합니다.
병렬성도, 동적 에이전트도, 공유 메모리 (Shared memory)도 없습니다.
모듈 3에서 제가 구축한 것은 이 중 어느 것도 갖추고 있지 않습니다. 그것은 피드백 루프 (Feedback loop)가 있는 2단계 파이프라인 (Pipeline)입니다. 유용하고, 배포 가능하며, 동적 오케스트레이션 (Dynamic orchestration)보다 훨씬 저렴합니다. 하지만 "구조화된 프롬프트 파이프라인 (Structured prompt pipeline)"과 "멀티 에이전트 시스템 (Multi-agent system)" 사이의 간극은 현재의 어휘가 인정하는 것보다 더 넓습니다.
기록할 만한 한 가지 구현 노트
비평가 (Critic)의 시스템 프롬프트 (System prompt) 끝에 있는 VERDICT: APPROVED / VERDICT: NEEDS REVISION 패턴이 매우 중요한 하중을 견디고 있습니다. 이것은 구조화된 출력 (Structured-output)을 위한 해킹 (Hack)입니다. 저는 제어 흐름 (Control flow)을 구동하기 위해 비평가의 자유 형식 텍스트 (Free-text) 응답에서 두 가지 리터럴 부분 문자열 (Literal substrings) 중 하나를 스캔합니다:
python approved = "VERDICT: APPROVED" in review
이 방식이 작동하는 이유는 시스템 프롬프트가 모델에게 항상 이 정확한 문자열 중 하나로 끝내도록 지시하기 때문입니다. 만약 그 지시를 제거한다면, 자유 형식의 텍스트를 더 주의 깊게 파싱 (Parsing)해야 하며, 제어 흐름은 빠르게 취약해집니다. 일반적인 프롬프트 기반 제어 흐름 (Prompt-driven control flow)의 경우, 모델이 알려진 위치에서 구조화된 태그 (Structured tag)를 출력하게 하는 것이 모델에게 "예 또는 아니오로 답하세요"라고 요청하는 것보다 훨씬 더 신뢰할 수 있습니다.
이것이 나아갈 방향
모듈 4에서는 실제 오케스트레이션 (Orchestration)을 시도할 것입니다. 즉, 목표를 수신하고 어떤 하위 작업 (Subtasks)이 존재하는지, 어떤 전문가 (Specialists)를 호출해야 하는지, 그리고 출력을 사용할 수 없을 때 무엇을 해야 하는지를 파악하는 에이전트 (Agent)를 만들 것입니다. 모듈 3에는 이러한 의사 결정 (Decision-making) 과정이 전혀 포함되어 있지 않습니다.
코드, 전체 3라운드 Krenholm 트랜스크립트 (Transcript), 최종 승인된 설계 문서: github.com/quietaidev-collab/zero-to-agent
만약 여러분이 프로덕션 (Production) 환경에서 동적 오케스트레이션을 구축해 보셨다면: 실제 코드는 얼마나 더 복잡한가요? 제가 시작하기 전에 그에 대한 조정 (Calibration)을 해보고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기