
맹목적 신뢰의 문제: AI 에이전트의 환각 (Hallucinations)을 줄이는 방법 파트 2
요약
멀티 에이전트 아키텍처에서 발생하는 잘못된 형식의 출력과 그로 인한 환각 문제를 해결하기 위한 '출력 계약(Output Contracts)' 전략을 소개합니다. XML 엔벨로프와 JSON 스키마를 결합하여 에이전트 간 통신의 구조적 무결성을 보장하는 방법을 다룹니다.
핵심 포인트
- 에이전트의 잘못된 출력 형식이 침묵하는 실패와 환각을 유발함
- 출력 계약은 XML 봉투 내에 검증된 JSON 페이로드를 담는 구조임
- 시스템 프롬프트와 별개로 응답 구조의 모호성을 제거하는 역할 수행
- 계약 검증기, 기본 스키마, 에이전트별 스키마를 통한 검증 흐름 구축
전형적인 멀티 에이전트 (Multi-agent) 아키텍처에서, 오케스트레이터 (Orchestrator)는 전문화된 에이전트들에게 작업을 위임하고 그들의 응답을 처리합니다. 문제는 에이전트가 잘못된 형식의 출력을 반환할 때 발생합니다: 태그가 불균형한 XML, 유효하지 않은 JSON, 필수 필드 누락 또는 잘못된 데이터 타입 등이 이에 해당합니다. 이러한 경우, 오케스트레이터가 항상 오류를 신뢰성 있게 감지할 수 있는 것은 아니며, 이는 침묵하는 실패 (Silent failures)를 유발하고 환각 (Hallucinations)의 가능성을 높입니다.
환각을 줄이기 위한 구조화된 데이터 템플릿 및 출력 계약 (Output Contracts)
출력 계약 (Output Contracts)은 멀티 에이전트 아키텍처에서 에이전트와 오케스트레이터 간의 통신을 위한 구조화된 검증 전략입니다. 이는 각 에이전트의 응답을 해당 에이전트 전용 JSON 스키마 (JSON Schema)를 통해 검증된 JSON 페이로드 (Payload)를 포함하는 XML 봉투 (Envelope) 안에 캡슐화하는 방식으로 구성됩니다.
출력 계약은 에이전트가 생성해야 하는 형식을 명시적으로 정의합니다. 시스템 프롬프트 (System prompts)가 행동 규칙을 설정하는 반면, 출력 계약은 응답 구조에 대한 모호성을 제거합니다.
멀티 에이전트 시스템에서 하나의 출력은 다음 에이전트의 입력이 됩니다. 만약 그 출력이 알려진 형식을 준수하지 않는다면, 수신 에이전트는 정보를 잘못 해석할 수 있으며, 바로 그 해석 과정에서 행동 환각 (Action hallucinations)이 주로 나타납니다. 서브 에이전트(Sub-agents) 간에 형식 계약을 설정함으로써 이러한 해석의 여지를 없애고, 각 단계마다 추가적인 검증 로직을 더할 필요 없이 오류를 줄일 수 있습니다.
출력 계약을 구성하는 세 가지 블록은 다음과 같습니다:
아키텍처 (Architecture)
시스템은 다음과 같이 구성됩니다:
- 응답을 분석하고 검증하는 역할을 담당하는 Contract Validator (계약 검증기).
- 공통 필드를 포함하는 base schema (기본 스키마).
- 에이전트별 특정 schema (Developer, Planner, Reviewer, Orchestrator 등).
- 동작을 확인하기 위한 자동화된 테스트 세트.
검증 흐름 (Validation flow)은 다음과 같습니다:
- XML envelope (엔벨로프) 추출.
- JSON 파싱 (Parsing).
- base fields (기본 필드) 검증.
- 에이전트의 schema (스키마)에 따라 payload (페이로드) 검증.
- 계약이 유효한지 여부를 나타내는 결과 반환.
단계별 구현 (Step-by-step implementation)
1단계: base Schema (기본 스키마) 생성
첫 번째 단계는 에이전트에 의해 생성된 모든 응답에 반드시 포함되어야 하는 필수 필드를 포함하는 base Schema (기본 스키마)를 정의하는 것입니다. 이 스키마는 출력의 최소 구조를 나타내며, 모든 하위 에이전트가 각 역할에 따른 특정 검증을 적용하기 전에 일관된 형식을 공유할 수 있는 시작점 역할을 합니다.
2단계: 에이전트별 특정 Schema (스키마) 생성
각 에이전트는 자신의 system prompt (시스템 프롬프트)에 정의된 책임에 따라 서로 다른 정보를 보고해야 합니다. 따라서 각 에이전트는 base Schema (기본 스키마)를 확장하여 해당 역할에 필요한 필드만을 포함하는 특정 Schema (스키마)를 가집니다. 이를 통해 전문화된 응답을 검증하는 유연성을 잃지 않으면서도 모든 에이전트에 대해 공통된 구조를 유지할 수 있습니다.
단계 3: 에이전트의 시스템 프롬프트 수정 (envelope 형식)
이 단계에서는 Output Contract (출력 계약)에 의해 정의된 형식을 사용하여 응답해야 하는 에이전트들의 시스템 프롬프트 (system prompts)를 수정합니다. 이 예시에서는 Developer를 업데이트하여, 모든 응답이 설정된 envelope (봉투) 형식을 따라야 함을 명시적으로 지시하고, 유효한 출력을 생성할 수 있도록 에이전트 전용 특정 Schema (스키마)의 위치를 참조하도록 합니다.
단계 4: 검증기 (contractValidator.js) 생성
Schema (스키마)를 정의하고 system prompts (시스템 프롬프트)를 설정한 후, 다음 단계는 **Contract Validator (계약 검증기)**를 구현하는 것입니다. 이 컴포넌트는 에이전트의 응답을 수신하여 XML envelope (봉투)를 추출하고, JSON payload (페이로드)를 파싱하며, **기본 Schema (스키마)**와 에이전트 전용 Schema (스키마)를 모두 검증하는 역할을 담당합니다. 계약이 유효하면 응답이 실행 흐름을 계속 이어갈 수 있으며, 그렇지 않으면 해당하는 검증 오류가 반환됩니다.
이를 통해 각 에이전트의 응답이 정의된 계약을 준수하는지 실시간으로 검증할 수 있습니다.
이 구현은 예시일 뿐입니다. 검증기의 전체 내용을 확인하고 싶다면 contractValidator.js에서 확인할 수 있습니다.
단계 4.5: 검증기 호출
contractValidator.js를 사용하여 각 에이전트의 출력을 검증하는 데에는 두 가지 방법이 있습니다:
OpenCode의 네이티브 훅(native hook)인 tool.execute.after를 사용하는 방법입니다. 이는 에이전트의 각 응답 이후에 contractValidator.js를 자동으로 실행할 수 있게 해주므로 가장 깔끔하고 적절한 솔루션이 될 것입니다. 하지만 현재 이 옵션은 Blocked by OpenCode Issue #25918로 인해 차단되어 있어, 당분간은 실행이 불가능합니다.
시스템 프롬프트(system prompt)에서 검증을 수행하는 방법입니다. 임시 대안으로서, 에이전트 스스로가 자신의 출력을 검증받기 위해 contractValidator.js를 호출합니다. 실제로 이 전략은 tool.execute.after가 가질 동작을 모방하며, 해당 네이티브 훅을 사용할 수 있게 되면 교체될 수 있습니다.
에이전트가 응답을 생성할 때, 검증 프로세스는 다음 흐름을 따릅니다:
- 코드 블록에서 콘텐츠를 추출합니다.
- JSON 페이로드(payload)를 파싱(parse)합니다.
- 해당 에이전트에 맞는 스키마(Schema)를 로드합니다.
validatePayload(payload, schema)를 실행합니다.- 검증에 실패하면 실행을 중단하고 에이전트에게 응답을 다시 생성하도록 요청합니다.
왜 이렇게 하나요? 응답이 워크플로우(workflow) 내에서 계속 진행되기 전에 형식 오류를 감지할 수 있어, 에이전트 간의 일관성을 보장하고 오류의 전파를 줄일 수 있기 때문입니다.
단계 5: 에이전트 응답 검증
모든 것은 단일 텍스트 문자열(raw input), 즉 에이전트가 생성한 응답에서 시작됩니다. 이 출력값으로부터 검증기는 계약(contract)을 추출하고, 그 내용을 해석하며, 예상된 구조를 준수하는지 확인합니다.
다음으로 JWT 기반 인증 미들웨어(middleware)를 구현한 후 Developer가 생성한 응답의 실제 사례를 살펴보겠습니다.
이 예시에서는 출력 계약 (Output Contract)을 구성하는 부분들을 명확하게 식별할 수 있습니다:
-
XML 태그 (
<output>): 구조화된 블록의 시작과 끝을 나타냅니다. 이는 포맷팅 프롬프트 (formatting prompt)에 해당하며, 검증해야 할 콘텐츠를 신뢰할 수 있는 방식으로 찾아낼 수 있게 해줍니다. -
내부 JSON: 나머지 워크플로우가 신뢰할 수 있는 방식으로 소비할 수 있는 구조화된 정보가 담긴 페이로드 (payload)를 포함합니다.
-
고정 필드 (agent, timestamp, version 등): 기본 스키마 (Schema)에 속하며, 오케스트레이터 (Orchestrator)나 다른 에이전트가 워크플로우를 계속 진행하기 위해 항상 동일한 정보를 사용할 수 있도록 보장합니다.
이 전략을 통해 무엇을 얻을 수 있나요?
이전 (자유 텍스트만 사용 시)
출력 계약 (Output Contract)이 없으면 각 에이전트는 자연어로 응답하며, 오케스트레이터 (Orchestrator)는 그 의미를 해석해야 합니다. 이는 다음과 같은 여러 문제를 발생시킵니다:
- 응답에 대한 보장된 형식이 존재하지 않습니다.
- 각 에이전트마다 정보를 구조화하는 방식이 다를 수 있습니다.
- 유효하지 않은 JSON이나 형식이 잘못된 XML은 전체 워크플로우를 중단시킬 수 있습니다.
- 오케스트레이터 (Orchestrator)가 데이터를 추측하고 해석해야 하므로 환각 (hallucination) 가능성이 높아집니다.
- 오류가 다른 에이전트들에게 전파된 후에야 뒤늦게 발견되는 경우가 많습니다.
- 새로운 하위 에이전트 (sub-agent)를 통합하려면 해석 로직을 지속적으로 수정해야 합니다.
이후 (Markdown + 출력 계약 (Output Contract) 사용 시)
출력 계약 (Output Contract)을 사용하면 에이전트는 자유 텍스트로 응답하는 대신, 예측 가능하고 검증 가능한 구조를 전달합니다.
무엇이 바뀌나요?
- 오케스트레이터 (Orchestrator)는 이제 무슨 일이 일어났는지 알기 위해 텍스트를 해석할 필요가 없습니다.
- 실행 상태, 오류, 결과가 명시적인 필드 (fields)로 제공됩니다.
- 모든 응답이 정확히 동일한 형식을 따릅니다.
- 흐름을 계속하기 전에 계약 (contract)을 자동으로 검증할 수 있습니다.
- 소비 에이전트 (consumer agents)는 일관된 데이터를 수신하여, 환각 (hallucinations)과 연쇄 오류 (cascading errors)를 줄입니다.
오케스트레이터 (Orchestrator)는 응답의 의미를 "추측"하는 것을 멈추고, 구조화되고 신뢰할 수 있는 데이터로 작업하기 시작합니다. 이는 특히 아키텍처가 복잡해짐에 따라 에이전트 간의 통신을 더욱 견고하고, 예측 가능하며, 유지보수하기 쉽게 만듭니다.
결과
응답은 사람에게 여전히 동일하게 읽기 쉽습니다. 에이전트가 생성한 동일한 텍스트를 계속 읽게 됩니다. 차이점은 마지막에 오케스트레이터 (Orchestrator)가 내부적으로 처리하는 작은 구조화된 블록 (Output Contract)이 포함된다는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기






