Agent Mentor Learn
멀티 에이전트 협업 · 6 / 6강

레슨 6: 실습: 두 에이전트 리뷰 파이프라인 만들기

학습 목표:

  • Claude API로 정말로 실행 가능한 프로듀서-리뷰어 두 에이전트 파이프라인을 쓰기
  • 리뷰어가 뭉뚱그린 "괜찮아 보임" 대신 구조화되고 점검 가능한 리뷰 결과를 돌려주게 하기
  • 프로듀서와 리뷰어가 끝없이 서로 다듬지 않도록 루프에 안전밸브를 달기

전제: 레슨 1~5를 마쳤고, 기본적인 JavaScript/Node.js를 읽을 수 있으며, 동작하는 Claude API 키가 있다 | 이전: 레슨 5 <<

먼저, 결과: 한 번의 완전한 실행

이것이 레슨 끝까지 가면 실행하게 될 것입니다. 터미널에 과제를 건네면, 두 에이전트가 리뷰를 통과하거나 라운드 제한에 도달할 때까지 번갈아 합니다.

$ node review-pipeline.js "Write an API change announcement for developers: the v2 endpoint changes the user_id field from a number to a string"
[Producer v1]The v2 endpoint is here! Hugely improved experience — please switch to the new version soon.
[Reviewer round 1] Rejected. Issues:- Doesn't spell out the specific field this change affects (never mentions that user_id goes from number to string)- Gives no migration advice; developers don't know how to update their code- "Hugely improved experience" is an unverifiable, exaggerated claim with no concrete basis
[Producer v2]v2 API change notice: the user_id field type changes from number to string.Check every piece of code that parses this field and switch the read logic from numeric to string,to avoid parse failures caused by the type mismatch. This change takes effect in v2.1.0.
[Reviewer round 2] Approved
Final draft (approved in round 2):v2 API change notice: the user_id field type changes from number to string.Check every piece of code that parses this field and switch the read logic from numeric to string,to avoid parse failures caused by the type mismatch. This change takes effect in v2.1.0.

첫 버전은 리뷰어에게 반려되며, 이유가 각 구체적 기준에 묶여 나옵니다. 프로듀서는 두 번째 버전으로 수정하고, 리뷰어가 다시 보며, 이번에는 통과합니다. 이것이 레슨 4의 프로듀서-리뷰어 패턴을 코드로 옮긴 것입니다. "one LLM call generates a response while another provides evaluation and feedback in a loop."1 (한 LLM 호출이 응답을 생성하고, 다른 하나가 루프 안에서 평가와 피드백을 제공합니다.)

전체적인 형태: 실행 루프와 같은 골격

이 시리즈에서 Agent Tool Calling: Getting Agents to Actually Do Things 코스를 들었다면, 이 파이프라인의 골격은 낯익을 것입니다. 루프, 라운드마다 한 번의 판단, 계속할지 정하는 결과, 그리고 무한 루프를 막는 안전밸브. 유일한 차이는 그 판단이 무엇을 판단하느냐입니다 — 그 코스의 도구 실행 루프는 "모델이 아직 도구를 호출하고 싶어 하는가"를 판단하지만(루프의 의미는 그 코스의 레슨 The Full Round-Trip of a Tool Call과 그 공식 출처에 있습니다), 여기서는 "리뷰어가 통과라고 했는가"를 판단합니다. 같은 골격, 루프 본문의 내용만 다릅니다.

파이프라인 전체는 세 함수를 꿰맨 것입니다. runProducer는 텍스트를 생성하거나 수정하고, runReviewer는 기준에 비추어 점수를 매기고 구체적 메모를 주며, runPipeline은 둘을 루프로 잇고 안전밸브로 라운드 상한을 둡니다.

1단계: 프로듀서 — 과제를 받아 텍스트를 만든다

첫 실행에서 프로듀서는 과제 자체만 갖습니다. 반려 후 두 번째 실행에서는 이전 버전 전체와 리뷰 메모까지 지니므로, 프로듀서는 처음부터 자유롭게 다시 쓰는 것이 아니라 메모에 따라 이전 버전 위에서 수정합니다.

프로듀서의 프롬프트는 자기완결적입니다. 레슨 3에서 다뤘듯, 서브에이전트는 오케스트레이터 쪽에서 무슨 일이 있었는지 볼 수 없고, 지난번에 어떻게 리뷰받았는지도 볼 수 없습니다2. 그래서 매 호출은 "과제가 무엇인지", "이전 버전이 무엇이라 했는지", "(있다면) 지난 라운드의 문제가 무엇이었는지"를 이번 호출의 프롬프트에 그대로 씁니다. 프로듀서 자신의 이전 초안조차 명시적으로 넘겨줘야 한다는 점에 유의하세요 — 이것이 자기완결 원칙에서 가장 놓치기 쉬운 절반입니다. Messages API는 무상태이고, 모든 요청은 필요한 이력 전체를 담아야 하며, 서버는 요청 사이에 아무것도 보관하지 않습니다3. "이전 버전을 수정하라"는 이전 버전이 실제로 이 프롬프트에 쓰여 있을 때만 의미가 있습니다.

2단계: 리뷰어 — 구체적 기준에 비추어 점수 매기기, 뭉뚱그린 판정 금지

리뷰어는 모델에게 그냥 "이거 괜찮아?"라고 묻지 않습니다. 레슨 5에서 다뤘듯, 검증은 인상 기반 점수가 아니라 구체적이고 점검 가능한 기준에 착지해야 합니다4. 여기서 리뷰어는 명시적 체크리스트를 받고, 고정된 JSON 형식으로 답하도록 요구받습니다.

approvedissues 필드가 함께 구조화된 리뷰 결과를 이룹니다. 단 하나의 "괜찮아"가 아니라 "통과 또는 실패" 더하기 "실패한 각 기준 뒤의 구체적 문제"입니다. 프로듀서는 issues를 손에 넣으면, 뭉뚱그린 판정에서 어디로 가야 할지 넘겨짚는 대신 그 구체적 문제들을 수정합니다.

3단계: 리뷰 결과를 맹목적으로 믿지 마라 — 파싱 실패는 반려로 취급한다

runReviewer는 실제 JSON 객체가 아니라 문자열을 돌려주므로, 여전히 파싱해야 합니다. 리뷰어가 "엄격히 JSON으로 답하라"는 지시를 받았어도, 구조화 출력 제약이 없으면 모델은 여전히 구문적으로 잘못된 JSON을 만들거나, 필드를 빠뜨리거나, JSON을 코드 블록으로 감싸고 그 주위에 설명 몇 줄을 두를 수 있습니다5. 여기서의 함정은 이것입니다. 파싱이 실패하면 무슨 일이 일어나는가? 게으른 길 — 파싱 실패 시 기본적으로 통과시키기 — 을 택하면, "리뷰어가 제 일을 하지 않았다"는 실패를 조용히 "리뷰 통과"로 바꿔 버립니다. 그것이 바로 레슨 5가 지적한 요점입니다. "완료된 것처럼 보이는" 출력이 실제로 정확한 출력과 같지 않으며, 검증할 수 없는 것은 출시해서는 안 됩니다6. 여기서는 정반대로 합니다. 파싱 실패는 언제나 반려로 세고, 결코 통과로 세지 않습니다.

typeof parsed.approved !== "boolean"!Array.isArray(parsed.issues) 줄은 같은 생각을 확장한 것입니다 — JSON.parse가 성공하더라도 파싱된 필드가 올바른 형태인지 여전히 확인하고, 필드 타입이 틀린 것도 반려로 셉니다. "적어도 유효한 JSON이잖아"라고 경계를 늦추지 마세요.

한마디 덧붙이면, 응답이 스키마에 엄격히 맞도록 샘플링 수준에서 보장하는 공식 구조화 출력 기능이 있습니다5. 이 레슨은 모델 출력을 맹목적으로 믿을 수 없다는 것을 직접 느끼게 하려고 일부러 "맨 호출 더하기 직접 짠 방어적 파싱" 방식을 씁니다. 프로덕션에서는 구조화 출력을 써서 이 함정을 통째로 없앨 수 있습니다.

4단계: 루프로 엮고, 안전밸브를 달기

runProducer, runReviewer, parseReview를 손에 쥐고, runPipeline이 셋을 엮습니다. 그리고 MAX_ROUNDS가 여기 유일한 안전밸브입니다 — 프로듀서와 리뷰어가 이론상 끝없이 다듬을 수 있으므로 상한이 있어야 합니다.

MAX_ROUNDS에 도달했는데도 통과하지 못하면, runPipeline은 억지로 "통과" 판정을 내리지 않습니다. 마지막 초안과 아직 풀리지 않은 문제들을 정직하게 사람의 리뷰에 넘깁니다 — 이 역시 레슨 5의 요점을 마무리 단계에 적용한 것입니다. 결과 통합 단계가 판단할 수 없는 것에 부딪히면, 코드로 스스로 결정해 덮어 버려서는 안 됩니다.

정리

  • 프로듀서-리뷰어 파이프라인의 골격은 실행 루프와 같은 것이다: 루프, 라운드마다 한 번의 판단, 계속할지 정하는 결과, 그리고 무한 루프를 막는 안전밸브. 이 패턴의 공식 정의가 바로 "one LLM call generates a response while another provides evaluation and feedback in a loop"1이며 — 여기서 판단이 "도구를 호출해야 하는가"에서 "리뷰어가 통과라고 했는가"로 바뀐다.
  • 프로듀서의 프롬프트는 자기완결적이다: 매 호출은 과제, 이전 버전 전체, (있다면) 지난 라운드의 구체적 문제를 프롬프트에 그대로 쓴다 — Messages API는 무상태이고, 모든 요청은 이력 전체를 담아야 하며, 요청 사이에 아무것도 보관되지 않으므로3, 모델이 지난 라운드에 무슨 일이 있었는지 스스로 기억하리라 기대할 수 없다2.
  • 리뷰어는 구체적이고 점검 가능한 기준에 비추어 항목별로 점수를 매기고, 뭉뚱그린 판정이 아니라 구조화된 {approved, issues}를 돌려준다4.
  • 리뷰어가 돌려주는 것도 맹목적으로 믿을 수 없다 — 파싱 실패나 필드 형태 오류는 조용한 통과가 아니라 반려로 세야 한다6. 이 원칙은 "서브에이전트가 하는 말을 믿는 것"뿐 아니라 "서브에이전트가 돌려주는 데이터 형식을 믿는 것"에도 적용된다.
  • 최대 라운드 수에 도달했는데도 통과하지 못하면, 파이프라인은 코드로 스스로 통과를 결정하지 말고 마지막 초안과 풀리지 않은 문제를 정직하게 사람의 리뷰에 넘겨야 한다.

이것으로 이 코스의 여섯 레슨을 모두 마쳤습니다. "왜 여러 에이전트인가"에서 출발해, 오케스트레이터와 서브에이전트가 어떻게 일을 나누는지, 위임 프롬프트를 어떻게 쓰는지, 어떤 협업 패턴이 어떤 시나리오에 맞는지, 실패를 어떻게 다루는지를 거쳐, 마지막으로 동작하는 프로듀서-리뷰어 파이프라인을 직접 만드는 것으로 끝냈습니다. 다음에 할 가장 값어치 있는 일은 설명을 다시 읽는 것이 아니라, 손에 든 작고 실제적인 과제 하나를 골라 이 파이프라인 골격에 떨어뜨리고, 리뷰 기준을 손보고, 돌려서 초안을 반려하는지 몇 번이나 반려하는지 보는 것입니다. 리뷰 기준을 직접 한 번 조율해 보는 것이 이론을 열 번 다시 읽는 것보다 낫습니다.

Footnotes

  1. Building effective agents (Anthropic Engineering) — https://www.anthropic.com/engineering/building-effective-agents 2

  2. Create custom subagents (Claude Code Docs) — https://code.claude.com/docs/en/sub-agents 2

  3. Using the Messages API (Claude API) — https://platform.claude.com/docs/en/build-with-claude/working-with-messages 2

  4. How we built our multi-agent research system (Anthropic Engineering) — https://www.anthropic.com/engineering/multi-agent-research-system 2

  5. Structured outputs (Claude API) — https://platform.claude.com/docs/en/build-with-claude/structured-outputs 2

  6. Best practices for Claude Code (Claude Code Docs) — https://code.claude.com/docs/en/best-practices 2

연습

01

이 레슨의 코드를 review-pipeline.js로 조립하고, npm install @anthropic-ai/sdk, npm pkg set type=module을 실행하고, ANTHROPIC_API_KEY를 설정한 뒤, 이 레슨의 예시 과제를 한 번 실행하세요. "Approved"를 보기 전에 적어도 한 번의 "Rejected" 라운드를 보는지 확인하세요. (프로듀서의 첫 버전이 그대로 통과해 버리면, 걸려 넘어지기 쉬운 과제로 바꾸세요 — 예를 들어 일부러 "a very short announcement"를 요청하되 얼마나 짧아야 하는지는 말하지 않는 식으로.)

레벨 1: 일단 돌린 다음, 리뷰 기준을 하나 추가하기

돌아가면, REVIEW_CRITERIA에 새 기준을 하나 추가하세요: "Does the text mention the specific version number where the change takes effect?" 다시 실행해서 리뷰어의 issues에 이제 이 새 기준에 묶인 메모가 들어가는지 확인하세요.

완료 기준 · 로컬에서 확인
02

아래 parseReview에는 문제가 있습니다. 먼저 이것이 실제로는 리뷰된 적 없는 초안을 "approved"로 통과시키게 되는 상황을 설명한 다음, 고친 코드를 제시하세요.

레벨 2: 일부러 망가뜨린 다음, 고치기
완료 기준 · 로컬에서 확인