Lab 07: 멀티에이전트 파이프라인 설계
고급
마감: 2026-04-22
schemas/planner_output.json
schemas/coder_output.json
2.
base_agent.py
3.
planner_agent.py
4.
coder_agent.py
pipeline_design.md
목표
- 멀티에이전트 시스템의 설계 원칙 적용
- 5단계 파이프라인(Planner → Researcher → Coder → QA → Reviewer) 아키텍처 설계
- 에이전트 간 통신을 위한 JSON 스키마 정의
- Planner→Coder 2단계 파이프라인 실제 구현
멀티에이전트 파이프라인 아키텍처
단일 에이전트는 복잡한 작업에서 한계를 보인다. 역할 분리를 통해 각 에이전트가 좁고 명확한 책임을 갖도록 하면 품질과 추적성이 높아진다.
사용자 요청 │ ▼[Planner] ──spec.md──▶ [Researcher] ──context.json──▶ [Coder] │ 코드 변경 │ [QA] ◀──────────────┘ │ 테스트 결과 │ [Reviewer] ──▶ 최종 승인/거부구현 요구사항
1. 에이전트 간 통신 JSON 스키마
모든 에이전트는 정해진 스키마로 데이터를 주고받는다.
{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "PlannerOutput", "type": "object", "required": ["task_id", "objective", "subtasks", "constraints"], "properties": { "task_id": { "type": "string", "pattern": "^task-[0-9]{4}$" }, "objective": { "type": "string", "minLength": 10 }, "subtasks": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["id", "description", "assignee", "depends_on"], "properties": { "id": { "type": "string" }, "description": { "type": "string" }, "assignee": { "type": "string", "enum": ["researcher", "coder", "qa", "reviewer"] }, "depends_on": { "type": "array", "items": { "type": "string" } } } } }, "constraints": { "type": "object", "properties": { "max_iterations": { "type": "integer", "minimum": 1, "maximum": 20 }, "forbidden_packages": { "type": "array", "items": { "type": "string" } }, "target_files": { "type": "array", "items": { "type": "string" } } } } }}{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "CoderOutput", "type": "object", "required": ["task_id", "changes", "test_command", "status"], "properties": { "task_id": { "type": "string" }, "changes": { "type": "array", "items": { "type": "object", "required": ["file", "action", "description"], "properties": { "file": { "type": "string" }, "action": { "enum": ["create", "modify", "delete"] }, "description": { "type": "string" } } } }, "test_command": { "type": "string" }, "status": { "enum": ["complete", "partial", "blocked"] }, "blocker": { "type": "string" } }}2. base_agent.py — 공통 에이전트 기반 클래스
import anthropicimport jsonfrom abc import ABC, abstractmethodfrom pathlib import Pathfrom jsonschema import validate, ValidationError
class BaseAgent(ABC): """모든 파이프라인 에이전트의 공통 기반 클래스."""
def __init__(self, name: str, schema_path: str | None = None): self.name = name self.client = anthropic.Anthropic() self.schema = self._load_schema(schema_path) if schema_path else None self.messages: list[dict] = []
def _load_schema(self, path: str) -> dict: return json.loads(Path(path).read_text())
def _validate_output(self, data: dict) -> bool: if self.schema is None: return True try: validate(instance=data, schema=self.schema) return True except ValidationError as e: print(f"[{self.name}] 스키마 검증 실패: {e.message}") return False
def _call(self, system: str, user: str) -> str: self.messages.append({"role": "user", "content": user}) response = self.client.messages.create( model="claude-sonnet-4-6", max_tokens=4096, system=system, messages=self.messages, ) text = response.content[0].text self.messages.append({"role": "assistant", "content": text}) return text
def _extract_json(self, text: str) -> dict: """응답 텍스트에서 JSON 블록을 추출한다.""" import re match = re.search(r"```json\s*([\s\S]+?)\s*```", text) if match: return json.loads(match.group(1)) # 코드 블록 없이 순수 JSON인 경우 return json.loads(text.strip())
@abstractmethod def run(self, input_data: dict) -> dict: pass3. planner_agent.py — 플래너 에이전트
from base_agent import BaseAgentimport uuid
PLANNER_SYSTEM = """You are a software planning agent. Your job is to decompose a user requestinto a structured plan that other agents can execute.
Output ONLY a valid JSON object matching the PlannerOutput schema.Do not include any explanation outside the JSON block."""
class PlannerAgent(BaseAgent): def __init__(self): super().__init__( name="Planner", schema_path="schemas/planner_output.json" )
def run(self, input_data: dict) -> dict: objective = input_data["objective"] codebase_summary = input_data.get("codebase_summary", "")
user_prompt = f"""Objective: {objective}
Codebase context:{codebase_summary}
Create a task plan. Use task_id format "task-XXXX".Assign subtasks to: researcher, coder, qa, reviewer.""" response_text = self._call(PLANNER_SYSTEM, user_prompt)
try: plan = self._extract_json(response_text) except Exception as e: print(f"[Planner] JSON 파싱 실패: {e}") # 폴백 플랜 plan = { "task_id": f"task-{uuid.uuid4().hex[:4]}", "objective": objective, "subtasks": [ { "id": "st-01", "description": objective, "assignee": "coder", "depends_on": [] } ], "constraints": {"max_iterations": 5, "forbidden_packages": [], "target_files": []} }
if self._validate_output(plan): print(f"[Planner] 계획 생성 완료: {len(plan['subtasks'])}개 서브태스크") return plan4. coder_agent.py — 코더 에이전트
import subprocessfrom base_agent import BaseAgent
CODER_SYSTEM = """You are a coding agent. You receive a task plan and implement the code changes.After making changes, run the specified test command to verify.
Output a JSON object matching the CoderOutput schema."""
class CoderAgent(BaseAgent): def __init__(self): super().__init__( name="Coder", schema_path="schemas/coder_output.json" )
def run(self, input_data: dict) -> dict: plan = input_data["plan"] coder_tasks = [ t for t in plan["subtasks"] if t["assignee"] == "coder" ]
user_prompt = f"""Task ID: {plan['task_id']}Objective: {plan['objective']}
Your subtasks:{chr(10).join(f"- {t['description']}" for t in coder_tasks)}
Constraints:- Max iterations: {plan['constraints'].get('max_iterations', 5)}- Forbidden packages: {plan['constraints'].get('forbidden_packages', [])}
Implement the changes and report what you did.""" response_text = self._call(CODER_SYSTEM, user_prompt)
# Claude Code를 헤드리스로 실행해 실제 코드 변경 수행 result = subprocess.run( ["claude", "--print", "--no-color", "--dangerously-skip-permissions", response_text], capture_output=True, text=True, timeout=300 )
return { "task_id": plan["task_id"], "changes": [], # 실제로는 git diff 파싱 "test_command": "pytest tests/ -q", "status": "complete" if result.returncode == 0 else "partial", "claude_output": result.stdout[:500] }schemas/디렉터리에 두 스키마 파일 생성base_agent.py구현 (pip install jsonschema)planner_agent.py구현 및 단독 테스트coder_agent.py구현- 두 에이전트를 연결하는
pipeline.py작성 및 실행
5단계 파이프라인 설계 문서
구현한 2단계를 바탕으로 나머지 3단계의 설계를 문서화한다.
## Researcher (미구현)- 입력: PlannerOutput- 역할: 코드베이스 탐색, 관련 파일 목록화, 의존성 파악- 출력: context.json (관련 파일 + 핵심 함수 요약)
## QA (Lab 09에서 구현)- 입력: CoderOutput- 역할: 테스트 실행, 코드 커버리지 측정, 회귀 감지- 출력: qa_report.json
## Reviewer (미구현)- 입력: QA 보고서 + CoderOutput- 역할: 코드 품질 검토, 승인/거부 결정- 출력: review_decision.json제출물
assignments/lab-07/[학번]/에 PR:
-
schemas/planner_output.json,schemas/coder_output.json -
base_agent.py— 공통 기반 클래스 -
planner_agent.py— 완전히 동작하는 플래너 -
coder_agent.py— 완전히 동작하는 코더 -
pipeline.py— Planner→Coder 연결 실행 스크립트 -
pipeline_design.md— 5단계 전체 설계 문서 -
README.md— 실행 결과 및 파이프라인 확장 시 예상 과제