아키텍처 리뷰에 채팅이 아닌 계약(Contracts)이 필요한 이유
요약
LLM을 아키텍처 리뷰 워크플로우에 통합할 때, 단순한 대화형 피드백을 넘어 기계가 실행 가능한 구조화된 결과물을 생성하는 방법을 다룹니다. PydanticAI를 활용해 계약(Contracts) 기반의 스키마를 정의함으로써 멀티 에이전트 시스템의 신뢰성을 높이는 전략을 제시합니다.
핵심 포인트
- 단순 산문 형태의 피드백은 엔지니어링 워크플로우 자동화에 한계가 있음
- PydanticAI를 통한 스키마 정의로 기계가 실행 가능한(machine-actionable) 출력 강제
- 멀티 에이전트 구조를 통해 보안, 확장성 등 다양한 관점의 리뷰 수행
- 구조화된 아티팩트(심각도, 근거, 권장 사항 포함) 생성의 중요성
아키텍처 리뷰에는 번역(translation) 문제가 있습니다.
사람은 "X를 고려하세요"라거나 "Y는 어떤가요?"와 같은 대화의 흐름을 남기고 나머지는 회의에서 해결할 수 있습니다. 하지만 PR(Pull Requests), ADR(Architecture Decision Records), 티켓팅(ticketing), CI 게이트(CI gates)와 같이 엔지니어링과 유사한 워크플로우에 LLM(Large Language Model)을 참여시키고 싶다면, 유창한 피드백만으로는 충분하지 않습니다. 다운스트림 시스템(downstream systems)과 사람 모두가 신뢰할 수 있게 행동할 수 있는 출력이 필요합니다.
대부분의 "LLM 아키텍처 리뷰" 데모는 설득력 있는 산문(prose) 단계에서 멈춥니다. 그 결과물은 숙련된 엔지니어가 쓴 것처럼 읽히지만, 아티팩트(artifact)의 형태를 갖추고 있지는 않습니다. 즉, 수동으로 한 번 더 확인하지 않고서는 순위를 매기거나, 경로를 지정하거나, 중복을 제거하거나, 작업으로 전환하기가 어렵습니다.
아키텍처 리뷰는 보안(security), 확장성(scalability), 운영성(operability), 비용(cost), 데이터 무결성(data integrity), 장애 복구(failure recovery)와 같이 각각 고유한 휴리스틱(heuristics)과 임계값(thresholds)을 가진 여러 관점의 집합체이므로 멀티 에이전트(Multi-agent) 방식이 도움이 됩니다. 하지만 진정한 차별점은 "단일 에이전트 대 다수 에이전트"가 아닙니다. 그것은 바로 **계약(contracts)**입니다. PydanticAI를 사용하면 시스템이 반드시 출력해야 하는 스키마(schema)를 정의하고 모든 응답을 검증할 수 있습니다. Claude는 추론(reasoning)을 제공하지만, 계약(contract)은 이를 기계가 실행 가능한 형태(machine-actionable shape)로 강제합니다.
이 글에서는 **구조화된 리뷰 아티팩트(structured review artifact)**를 생성하는 멀티 에이전트 아키텍처 리뷰어를 구축하는 방법을 보여줍니다. 이 아티팩트는 심각도(severity), 근거(evidence), 권장 사항(recommendations)이 포함된 정규화된 결과물과 함께, 명확한 질문 및 명시적인 "인간의 판단 필요(needs human judgment)" 플래그를 포함합니다. 챗봇보다는 리뷰 보고서에 더 가깝다고 생각하십시오.
전체 실행 가능한 예제는 companion repository에 있습니다.
다음 섹션에서는 최소한의 토폴로지(topology)(planner → specialists → synthesizer), 공유 계약(shared contracts), 그리고 설계 문서에서 구조화된 보고서에 이르는 엔드 투 엔드(end-to-end) 과정을 정의할 것입니다.
멀티 에이전트가 제공하는 것 (그리고 비용이 드는 것)
멀티 에이전트 시스템(Multi-agent systems)은 과하게 사용하기 쉽습니다. 만약 여러분에게 필요한 것이 단 한 번의 피드백—예를 들어 “이 설계 문서에서 명백한 리스크를 스캔해줘” 또는 “이 스토리지 계층에 대한 대안을 제안해줘”와 같은 것—뿐이라면, 구조화된 출력 스키마(structured output schema)를 갖춘 잘 구성된 프롬프트 하나만으로도 복잡성을 대폭 줄이면서 대부분의 가치를 얻을 수 있습니다. 에이전트를 추가하는 순간, 여러분은 오케스트레이션(orchestration), 상태(state), 그리고 장애 처리(failure handling)를 추가하게 됩니다. 만약 명확한 보상이 따르지 않는다면, 여러분은 단지 단일 호출(single call)보다 더 느리고 더 비싼 버전을 만든 것에 불과합니다.
그렇다면 아키텍처 리뷰에서 멀티 에이전트는 실제로 무엇을 제공할까요? 주로 **관심사의 분리(separation of concerns)**와 **병렬적 관점(parallel perspectives)**입니다. “이 아키텍처를 리뷰해줘”라는 단일 프롬프트는 몇 가지 일반적인 패턴으로 수렴되는 경향이 있습니다. 즉, 여러 카테고리에 걸쳐 동일한 리스크를 반복하고, 도메인 특화된 엣지 케이스(edge cases)를 놓치며, “질문하기”와 “사실 주장하기”를 모호하게 섞어버립니다. 작업을 역할(roles)로 나누면 각 에이전트에게 더 날카로운 지침과 좁은 컨텍스트 윈도우(context windows)를 제공할 수 있으며, 불확실성을 표현할 수 있는 자연스러운 지점(“보안 리뷰어로서, Y를 결론짓기 위해 X가 필요합니다”)을 마련해 줍니다.
모놀리식 프롬프트(Monolithic prompts) 또한 예측 가능한 방식으로 실패합니다. 이들은 순위를 매기기 어려운 긴 출력을 생성하고, 신뢰도가 높은 이슈와 추측성 이슈를 뒤섞으며, 스스로 모순되기도 하고, 입력값과 연결된 추적 가능한 근거(traceable evidence)가 부족한 경우가 많습니다. 이러한 실패는 단순히 미적인 문제가 아닙니다. 이는 다운스트림 자동화(downstream automation)를 신뢰할 수 없게 만듭니다. 무엇이 “P0”이고 무엇이 “P3”인지, 혹은 무엇이 요구사항(requirement)이고 무엇이 제안(suggestion)인지 일관되게 구분할 수 없다면, 리뷰를 엔지니어링 워크플로우(engineering workflow)로 전환할 수 없습니다.
그 비용은 실재합니다. 팬아웃 (Fan-out, 여러 전문가를 실행하는 것)은 토큰 소비와 지연 시간 (latency)을 증가시키며, 조정 오버헤드 (coordination overhead)를 유발합니다. 즉, 공유 컨텍스트 (shared context)를 관리하고, 중복을 피하며, 의견 불일치를 하나의 보고서로 병합해야 합니다. 순차적 파이프라인 (Sequential pipelines)은 중복을 줄이고 컨텍스트를 더 긴밀하게 유지할 수 있지만, 초기 실수를 증폭시킬 수 있습니다 (만약 첫 번째 단계에서 범위를 잘못 좁히면, 이후의 모든 단계가 그 오류를 상속받게 됩니다). 실제로 여러분은 단일 모델 호출을 소규모 분산 시스템 (distributed system)과 맞바꾸고 있는 것입니다.
린 (lean)한 아키텍처 리뷰어를 위해서는 최소한의 토폴로지 (topology)가 효과적입니다:
- 플래너 (planner): 입력을 읽고 어떤 관점 (lenses)을 적용할지 (보안, 확장성, 운영성, 데이터, 비용), 그리고 명확히 해야 할 질문들을 결정합니다.
- 소규모 전문가 (specialists) 세트: 각자 하나의 관점을 실행하고 공유 스키마 (shared schema)에 따라 결과물을 출력합니다.
- 신시사이저 (synthesizer): 결과물을 병합합니다. 중복을 제거하고, 순위를 매기며, 모순을 해결하거나 (또는 이견을 명시적으로 보존하며), 최종적인 구조화된 리뷰 결과물 (artifact)을 생성합니다.
의사결정 휴리스틱 (heuristic)은 간단합니다: 진정으로 다른 관점이 필요한 경우가 아니라면 린 (lean)하게 유지하세요. 만약 리뷰 결과물이 지속적으로 반복적이거나, 피상적이거나, 내부적으로 일관성이 없다면 전문가 역할을 도입하는 것이 도움이 될 수 있습니다. 만약 이미 단일 호출에서 고품질의 구조화된 출력물을 얻고 있다면, 에이전트 (agents)를 추가하지 마세요. 대신 더 나은 계약 (contracts), 더 나은 증거 요구 사항, 그리고 더 나은
아키텍처 리뷰 (Architecture review)는 모호함으로 가득 차 있기 때문에 이 문제에 특히 민감합니다. 어떤 결과물은 필수 요구 사항(“이것은 컴플라이언스 제약 조건을 위반합니다”)이고, 어떤 것은 조건부(“트래픽이 10배 급증할 수 있다면, 백프레셔 (backpressure)가 필요합니다”)이며, 어떤 것은 단순히 질문(“RTO/RPO가 무엇입니까?”)입니다. 이러한 카테고리를 구분하고, 모델이 증거를 첨부하거나 미지의 상태를 명시적으로 표시하도록 강제하는 계약 (contract)이 없다면, 그럴듯하게 들리지만 운영 측면에서는 사용할 수 없는 산문(prose)을 얻게 될 뿐입니다.
Pydantic 관점에서 보면, 엔지니어들이 실제로 리뷰를 소비하는 방식에 맞춰 출력을 작은 타입 (types) 세트로 모델링해야 합니다. 전형적인 핵심 구조에는 다음과 같은 것들이 포함될 수 있습니다:
- 최상위
ArchitectureReview아티팩트 (메타데이터, 전반적인 리스크, 요약). title,severity,category,recommendation과 같은 필드를 가진Finding객체 리스트.- 주장을 입력값의 무언가와 연결하거나 추론으로 표시하는
evidence또는references필드. - 확신 있는 결론을 내리는 데 방해가 되는 누락된 정보를 위한 별도의
questions리스트.
정확한 필드 구성보다 더 중요한 것은 규율입니다: 모든 주장은 반드시 명시적인 어딘가에 위치해야 합니다. 심각도 (Severity)가 형용사 속에 파묻혀 있어서는 안 됩니다. 권장 사항 (Recommendations)이 여러 단락에 흩어져 있어서도 안 됩니다. 불확실성은 암시되어서는 안 되며, 스키마 (schema) 내에 자리가 있어야 합니다.
계약 (Contracts)은 입력값 (inputs)에도 적용됩니다. 각 에이전트 (agent) 역할은 어떤 컨텍스트 (context)를 가정해도 되는지, 그리고 무엇을 미지의 상태로 취급해야 하는지에 대한 명확한 정의를 가져야 합니다. 이것이 전문가가 기본적으로 “mTLS를 사용하세요”라고 말하는 것과, “멀티 테넌트 경계가 있거나 신뢰할 수 없는 네트워크가 있다면 mTLS를 고려하십시오. 그렇지 않다면 왜 불필요한지 정당화하십시오”라고 말하는 것의 차이입니다. 입력값(제약 조건, 트래픽 가정, 데이터 분류, SLO)에 대해 더 명시적일수록, 에이전트가 추측해야 하는 일은 줄어듭니다.
계약 (contracts)을 갖추고 나면, 검증 (validation) 및 복구 루프 (repair loops)를 통해 시스템을 탄력적으로 만들 수 있습니다. PydanticAI 스타일의 구조화된 출력 (structured outputs)을 사용하면 다음과 같은 작업이 가능합니다:
- 모든 모델 응답을 스키마 (schema)에 따라 검증 (Validate) 합니다.
- 검증 실패 시 타겟팅된 지침(예: "각 결과에 대해
evidence를 포함해야 합니다")을 사용하여 재시도 (Retry) 합니다. - 모델이 규격을 거의 충족하지만 완전히 일치하지 않을 때(필드 누락, 잘못된 열거형 (enum) 값 등) 작은 "수정 (repair)" 단계를 적용합니다.
이는 결벽증적인 태도를 취하려는 것이 아닙니다. 이것은 "LLM 출력"을 다운스트림 (downstream)으로 라우팅할 수 있는 "타입화된 데이터 (typed data)"로 바꾸는 과정입니다. 즉, 이슈 트래커에 이슈를 생성하거나, PR에 요약된 댓글을 게시하거나, ADR 체크리스트를 생성하거나, 조직이 계속해서 재발견하고 있는 아키텍처 리스크의 반복적인 유형을 추적하는 대시보드에 데이터를 공급할 수 있게 해줍니다.
멀티 에이전트 오케스트레이션 (Multi-agent orchestration)은 모든 구성원이 동일한 언어를 사용할 때만 제대로 작동합니다. 계약 (contract)이 바로 그 언어입니다.
모델링, 검증, 그리고 Pydantic 계약을 프로덕션 환경에서 신뢰할 수 있게 만드는 패턴에 대해 더 깊이 알고 싶다면, Practical Pydantic을 참고하세요. 핵심 개념부터 실제 API 및 파이프라인에 이르기까지 Python 데이터 검증을 다루는 실무 가이드입니다.
코드 예시: 최소한의 계약 세트
models.py의 작동 예제는 계획 (planning), 전문가 리뷰 (specialist review), 그리고 종합 (synthesis)을 위한 "에이전트 API" 역할을 하는 작은 Pydantic 모델 세트를 정의합니다:
from enum import Enum
from typing import Literal
...
역할 설계: Planner, Specialists, Synthesizer
멀티 에이전트 시스템을 망치는 가장 빠른 방법은 모든 에이전트에게 "아키텍처를 리뷰하세요"와 같은 동일하고 모호한 업무를 부여하는 것입니다. 그러면 여러 번의 호출 비용을 지불하면서도 중복되고 일반적인 출력만을 얻게 될 것입니다. 역할 설계 (Role design)는 멀티 에이전트가 프롬프트 트릭 (prompt trick)이 아닌 엔지니어링 도구가 되는 지점입니다.
유용한 규칙은 다음과 같습니다: 각 역할은 반드시 필요하며 서로 중복되지 않는 한 문장의 직무 기술서 (job description)를 가져야 합니다. 만약 두 역할이 동일한 종류의 출력을 생성할 수 있다면, 이는 관심사 분리 (separation of concerns)를 만든 것이 아니라 중복 (redundancy)을 만든 것입니다.
가벼운 아키텍처 리뷰어를 위해서는 세 가지 역할이면 충분합니다:
Planner
Planner의 역할은 입력값과 제약 조건을 고려하여 어떤 리뷰가 수행되어야 하는지 결정하는 것입니다. 전체 리뷰를 생성하지는 않습니다. 대신 다음을 생성합니다:
- 적용할 렌즈 (Lenses) (보안 (security), 확장성 (scalability), 운영성 (operability), 비용 (cost), 데이터 무결성 (data integrity), 컴플라이언스 (compliance) 등).
- 확신 있는 리뷰를 방해하는 명확화 질문 (Clarifying questions) ("예상되는 피크 RPS는 얼마인가요?", "데이터 분류 (data classification)는 무엇인가요?").
- 선택적인 범위 설정 노트 (Scoping notes) ("장애 복구 및 멀티 리전 (multi-region)에 집중하고, UI 관련 사항은 무시하세요.").
이 역할은 낭비되는 작업을 방지하는 단계입니다. 만약 아키텍처가 공개 인그레스 (public ingress)가 없는 배치 파이프라인 (batch pipeline)이라면, 심층적인 웹 보안 검토는 소음 (noise)에 불과합니다. 반대로 멀티 테넌트 (multi-tenant) SaaS라면, 테넌트 경계를 무시하는 것은 태만입니다. Planner는 이러한 우선순위를 명시적으로 설정합니다.
Specialists
각 Specialist의 역할은 하나의 렌즈를 실행하고 공유된 계약 (shared contract)에 따라 결과물을 내놓는 것입니다. Specialist는 다음을 수행해서는 안 됩니다:
- 리뷰 범위 재설정 ("운영성도 함께 검토해야 한다고 생각합니다").
- 누락된 컨텍스트를 마치 사실인 것처럼 임의로 만들어내기.
- 합성기 (Synthesizer)가 병합할 수 없는 긴 서술형 산문 작성하기.
Specialist는 자신의 렌즈 범위 내에서는 주관을 가져야 하지만, 불확실성에 대해서는 절제되어야 합니다. 훌륭한 Specialist의 결과물은 높은 신호 (high-signal)를 가진 발견 사항과, 핵심 컨텍스트가 누락되었을 때의 명확한 질문을 포함해야 합니다. 계약 (contract)은 강제 기제 (forcing function) 역할을 합니다. 각 발견 사항에는 카테고리, 심각도 (severity), 근거, 그리고 권장 사항이 반드시 포함되어야 합니다.
Synthesizer
Synthesizer의 역할은 최종 ArchitectureReview 산출물 (artifact)을 생성하는 것입니다. 즉, 다음을 수행합니다:
- 여러 Specialist 사이에서 중복되는 발견 사항을 제거 (Deduplicate).
- 가능한 경우 모순을 해결하거나, 중요한 경우 이견을 보존 ("X가 아니라면 보안상 P0 이슈임; 확장성 측면에서는 Y라면 허용 가능하다고 판단됨").
- 심각도와 예상 영향도에 따라 순위를 매기고 우선순위를 지정.
- 독립적인 "새로운" 리뷰가 아니라, 구조화된 발견 사항과 일치하는 간결한 요약 생성.
합성기(Synthesizer)는 또한 심각도 정의(severity definitions), 하우스 스타일(house style), 그리고 무엇을 수용 가능한 증거로 간주할 것인지와 같은 글로벌 정책(global policy)을 강제하는 곳이기도 합니다. 즉, 합성기는 렌즈(lens)별로 파편화된 의견들을 엔지니어링 팀이 실행에 옮길 수 있는 단일 보고서로 변환합니다.
프롬프트 경계 (Prompt boundaries)
프롬프트 경계(Prompt boundaries)는 단순히 장식적인 요소가 아닙니다. 이는 범위 확장(scope creep)과 환각된 권위(hallucinated authority)를 방지합니다. 각 역할(role)은 명시적인 "해서는 안 될(must not)" 제약 조건을 가져야 합니다. 예시는 다음과 같습니다:
- 플래너(planner)는 발견 사항(findings)을 내보내서는 안 됩니다.
- 전문가(specialists)는 계약(contract)을 재작성하거나 누락된 사실을 지어내서는 안 됩니다.
- 합성기(synthesizer)는 전문가의 출력물이나 입력 증거에 의해 뒷받침되지 않은 새로운 발견 사항을 추가해서는 안 됩니다 (낮은 신뢰도를 가진 추론(inference)으로 명시적으로 표시되지 않는 한).
역할이 명확할 때 오케스트레이션(orchestration)은 단순해집니다. 각 단계에 어떤 입력이 필요한지, 그리고 어떤 출력을 생성할 수 있는지 알 수 있기 때문입니다. 역할이 모호하면 에이전트(agent) 간의 불일치를 추적하고 책임을 전가하는 데 시간을 허비하게 될 것입니다.
코드 예시: 역할 에이전트 정의하기
작동 예제(reviewer.py)에서 각 역할은 output_type이 계약(contract) 중 하나로 설정된 Agent이며, 모든 역할은 설계 문서(design doc)를 담고 있는 동일한 의존성 타입(ReviewDeps)을 공유합니다.
deps=ReviewDeps(design_doc=...)만 전달하는 것으로는 충분하지 않습니다. PydanticAI는 프롬프트에 의존성을 자동으로 주입하지 않기 때문입니다. 모든 실행에 문서를 첨부하려면 동적 지침(dynamic instructions)을 사용하십시오:
from __future__ import annotations
import os
...
라우팅 토폴로지(Routing Topology): 팬아웃(Fan-Out), 시퀀스(Sequence), 그리고 중단 시점
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기