
Qwen Cloud에서 감사 가능한 환경 준수 에이전트 구축하기 — 실제 qwen-plus 출력으로 테스트했을 때 변화된 점
요약
Qwen Cloud의 qwen-plus 모델을 활용하여 페루의 환경 규제 준수 이력을 조사하는 감사 가능한 에이전트 시스템 구축 사례를 소개합니다. 오프라인 우선 설계의 중요성과 실제 모델 출력 분포로 인해 발생하는 에이전트 시스템의 실패 대응 과정을 다룹니다.
핵심 포인트
- Qwen qwen-plus와 Mastra를 활용한 에이전트 기반 애플리케이션 구축
- 오프라인 우선(offline-first) 설계를 통한 테스트 및 재현성 확보
- 모델 출력 분포 차이로 인해 발생하는 에이전트 시스템의 실질적 실패 사례 분석
- 구조화된 출력 검증 및 감사 가능한 워크플로우 설계의 중요성
Qwen Cloud에서 감사 가능한 환경 준수 에이전트 구축하기 — 실제 qwen-plus 출력으로 테스트했을 때 변화된 점
AgentOps Debugger는 페루의 환경 준수 이력을 조사하기 위한 에이전트 기반 (agentic) 애플리케이션입니다.
아이디어는 간단합니다. 페루의 환경 규제 기관인 OEFA의 규제를 받는 기업에 대해 스페인어 또는 영어로 질문하면, 시스템이 공개된 제재 기록과 규제 문서를 검색하고, 인용된 답변을 생성하며, 인간의 승인 단계를 거쳐 구조화된 보고서 초안을 작성하고, 답변이 생성된 전체 추적 과정을 보여줍니다.
기술 스택은 다음과 같습니다:
- DashScope의 OpenAI 호환 엔드포인트를 통한 Qwen Cloud의 Qwen qwen-plus
- AI SDK v5를 사용한 Mastra
- Hono + TypeScript 백엔드
- React 워크스페이스
- Alibaba Cloud ECS 상의 Docker 배포
아키텍처: 코디네이터(Coordinator)가 타입이 지정된 작업(typed tasks)을 계획하면, 전문 Qwen 에이전트들이 이를 실행하며, 모든 단계는 감사 원장(audit ledger)에 저장됩니다.
잘 작동했던 전략 — 라이브 모델 출력이 시스템에 들어오기 전까지는
처음부터 저는 이 프로젝트를 오프라인 우선 (offline-first) 방식으로 설계했습니다.
전체 시스템은 API 키 없이도 실행될 수 있습니다: 시드 기록(seed records), 어휘적 BM25 검색 (lexical BM25 retrieval), 결정론적 비-LLM 에이전트 (deterministic no-LLM agents), 그리고 docker compose up을 통한 로컬 데모가 가능합니다.
이 방식은 315개의 모든 테스트가 네트워크 호출 없이 실행될 수 있었기에 큰 도움이 되었습니다. 앱은 테스트 가능하고, 재현 가능하며, 데모하기 쉽습니다. 그 후 라이브 모드에서는 동일한 인터페이스 뒤에서 결정론적 에이전트를 실제 Qwen 에이전트로 교체합니다.
아이디어는 견고했습니다: 동일한 타입 경계(typed boundaries)를 유지하고, zod 컨트랙트를 사용하며, 모든 구조화된 출력(structured output)을 검증하는 것이었습니다.
하지만 Alibaba Cloud에 배포하고 실제 qwen-plus를 사용하기 시작했을 때, 진짜 교훈이 나타났습니다:
오프라인 테스트는 필수적이지만, 에이전트 시스템(agentic system)에서 가장 중요한 실패를 잡아낼 수는 없습니다. 왜냐하면 많은 실패가 코드가 아닌 모델 출력 분포(model output distribution)에서 발생하기 때문입니다.
우리는 라이브 모델을 대상으로 동일한 흐름을 여러 번 실행했으며, 그 과정에서 여섯 가지 서로 다른 문제가 나타났습니다. 모든 테스트는 통과(green) 상태였지만, 실제 동작은 오직 실제 모델 출력만이 드러낼 수 있는 방식으로 여전히 무너졌습니다.
6단계의 수정 과정
1단계 — 상태(status) 값이 항상 계약(contract)에서 기대하는 대로 나오지 않음
우리의 스키마(schema)는 다음과 같은 값을 기대했습니다:
status: "completed" | "failed" | "needs_user_input"
하지만 라이브 qwen-plus는 다음과 같은 값들을 반환했습니다:
"success"
"done"
"in_progress"
때로는 필수 항목인 summary를 누락하기도 했습니다.
엄격한 파서(parser)는 답변 자체는 유용하더라도 태스크 전체를 거부해 버렸습니다.
수정 사항: 관대한 전처리기(preprocessors)를 추가했습니다. 이들은 상태의 동의어들을 정규화(normalize)하고, 필요한 경우 대체 요약(fallback summaries)을 도출합니다.
여기서 얻은 교훈은 간단합니다. 레이블 불일치 때문에 정답을 거부하는 것은 대개 잘못된 트레이드오프(trade-off)라는 것입니다.
2단계 — 플래너(planner)가 때때로 빈 계획을 생성함
때때로 플래너가 작업(task)도, 명확화 질문(clarification question)도 반환하지 않는 경우가 있었습니다.
기술적으로 출력값은 유용하지 않았지만, 앱은 이를 여전히 일반적인 응답으로 변환하려고 시도했습니다. 이는 오해를 불러일으키는 상투적인 답변(canned answer)을 만들어냈습니다.
수정 사항: 퇴화된 계획(degenerate plans)을 감지하고, 한 번 재시도한 뒤, 시스템이 계획을 도출할 수 없음을 알리는 정직한 로컬 메시지로 전환하는 순수 계획 해석기(plan interpreter)를 추가했습니다.
에이전트가 이해하지 못한 것을 이해한 척하는 것보다 투명하게 공개하는 것이 더 낫습니다.
3단계 — 인용(citations)에 서로 다른 필드 이름이 사용됨
인용 스키마는 다음과 같은 필드들을 기대했습니다:
documentTitle
passage
confidence
하지만 모델은 다음과 같은 변형된 형태를 반환했습니다:
title
text
high
또한, 계약상 스페인어 스타일의 값을 기대했음에도 불구하고 일부 신뢰도(confidence) 레이블이 영어로 들어오기도 했습니다.
수정 사항: 별칭 매핑(alias mapping)과 항목별 인용 복구(per-item citation salvage) 기능을 추가했습니다.
각 인용(citation)은 독립적으로 검증됩니다. 만약 하나의 인용이 잘못된 형식(malformed)이라면, 해당 인용만 제외하고 유효한 인용들은 유지합니다.
하나의 잘못된 인용이 네 개의 올바른 인용을 망쳐서는 안 됩니다.
Round 4 — 플래너(planner)가 초안 없이 저장을 예약함
한 워크플로우(flow)에서 사용자에게 보고서 저장을 승인해달라고 요청했으나, 플래너가 보고서 초안(draft)을 먼저 생성하지 않은 상태였습니다.
이에 사용자가 작업을 승인하자, 시스템은 다음과 같이 올바르게 답변했습니다:
저장할 보고서 초안이 없습니다.
로직은 안전했지만, 사용자 경험(UX)은 깨진 상태였습니다.
수정 사항: 이제 플래너 해석기(plan interpreter)가 짝이 맞지 않는 저장 작업(unpaired save task)을 감지하고, 그 이전에 누락된 초안 생성 작업을 삽입합니다.
프롬프트(prompt)에는 예상되는 세 가지 작업 레시피(three-task recipe)가 설명되어 있지만, 코드에서 더 이상 모델이 이를 따랐을 것이라고 가정하지 않습니다.
이것은 가장 중요한 교훈 중 하나입니다: 프롬프트 계약(prompt contracts)이 도움이 되기는 하지만, 코드가 여전히 워크플로우(workflow)를 보호해야 합니다.
Round 5 — 모델이 동일한 확인 질문을 다시 요청함
앱은 제재 대상 엔티티(sanctioned entities)를 클릭 가능한 카드 형태로 나열할 수 있습니다. 사용자가 한 회사를 클릭하면, 선택된 RUC(사업자 등록 번호)와 함께 실행이 재개됩니다.
영어 환경에서 모델은 선택된 엔티티를 전달받았음에도 불구하고 가끔 동일한 확인 질문을 다시 던지는 경우가 있었습니다.
수정 사항: 사용자가 확인 질문에 한 번 답변하면, 시스템은 동일한 확인 질문을 다시는 하지 않습니다.
그 시점부터 엔티티 식별(entity resolution)은 기록(records)을 바탕으로 계산됩니다. 모델은 서술형 문장(narrative)을 작성할 수 있지만, 엔티티가 식별되었는지 여부를 제어하지는 않습니다.
Round 5에서 목록 기능이 강화되었습니다: 엔티티가 클릭 가능한 후보로 표시됩니다. 식별(resolution) 과정이 결정론적(deterministic)이므로, 모델의 상태(mood)에 의존하지 않습니다.
Round 6 — 데이터가 모호하지 않음에도 모델이 모호하다고 주장함
다음 대상에 대한 보고서를 요청했을 때:
Minera Las Bambas S.A.
모델은 법적 명칭(legal name)이 고유한 하나의 기록과 일치함에도 불구하고, 해당 엔티티가 모호하다고 주장했습니다.
그로 인해 데이터가 생성되지 않았고, 승인 후 저장 단계가 실패했습니다.
해결책 (Fix): 엔티티 휴리스틱 (entity heuristic)에 전체 이름 확인 (full-name resolution) 로직을 추가했습니다.
이제 모델이 주장하는 모든 모호함은 데이터를 통해 검증됩니다. 데이터가 하나의 엔티티로 귀결되면 시스템이 답변합니다. 만약 모호함이 실제라면, 시스템이 직접 후보 목록을 생성합니다.
모델은 모호함을 제안할 수 있지만, 모호함의 존재 여부는 데이터가 결정합니다.
6라운드 이후의 설계 원칙
주요 원칙이 명확해졌습니다:
LLM이 서술하게 하되, 구조화된 결과물(structured outcomes)을 소유하게 하지 마라.
이러한 수정 사항들을 거친 후, 중요한 구조화된 부분들은 결정론적 (deterministic)입니다:
- 엔티티 확인 (entity resolution)
- 엔티티 목록 (entity listings)
- 차트 데이터 (chart data)
- 보고서 조립 (report assembly)
- 승인 쌍 맞추기 (approval pairing)
- 인용구 복구 (citation salvage)
- 모호성 검증 (ambiguity verification)
Qwen은 여전히 매우 유용합니다. 분석가의 의도를 이해하고, 작업을 계획하며, 적절한 페루 규제 용어를 사용하여 스페인어와 영어로 법적 서술을 작성합니다.
하지만 시스템은 기록(records)에서 가져와야 할 정보에 대해 모델이 진실의 근원 (source of truth)이 되도록 요구하지 않습니다.
규제 보고서에 포함되는 필수 면책 조항 (mandatory disclaimer)조차 z.literal로 처리됩니다. 모델은 해당 부분을 소유하지 않기 때문에 문구를 재구성할 수 없습니다.
이것이 이 프로젝트의 이름이 AgentOps Debugger인 이유이기도 합니다. 모든 모델 호출(model call)과 도구 호출(tool call)은 토큰 수, 지연 시간 (latency), 기여도 (attribution)와 함께 추가 전용 추적 원장 (append-only trace ledger)에 기록됩니다. 다음 라이브 이슈가 발생했을 때, 추적 기록을 통해 정확히 어떤 일이 일어났는지 확인할 수 있습니다.
라이브 배포 환경의 추적성 (Traceability) 시트: 토큰 및 지연 시간이 포함된 qwen-plus 모델 호출과 기여도가 포함된 도구 호출.
Qwen Cloud를 위한 실무 참고 사항
중요했던 몇 가지 구현 세부 사항입니다:
@ai-sdk/openai 대신 @ai-sdk/openai-compatible 사용
DashScope의 /compatible-mode/v1의 경우, OpenAI 호환 (OpenAI-compatible) 프로바이더가 더 잘 작동했습니다.
일반적인 OpenAI 프로바이더는 OpenAI 모델이 아닌 모델 ID를 추론 모델 (reasoning models)로 분류하거나, developer 역할을 전송하고, Responses API를 대상으로 삼을 수 있습니다. DashScope는 이러한 가정을 거부합니다.
프롬프트에 "json"이라는 단어 포함하기
DashScope는 다음 설정을 준수하기 전에 메시지 내에 json이라는 글자가 명시적으로 포함되어 있어야 합니다:
response_format: { type: "json_object" }
시스템 프롬프트(system prompt)에 한 줄을 추가하는 것으로 이 문제를 해결했습니다.
프롬프트에서 계약(contract)을 고정하되, 파서(parser)에서는 변동성을 허용하기
프롬프트는 오류를 줄여주지만, 완전히 제거하지는 못합니다.
파서는 여전히 방어적 (defensive)이어야 합니다:
- 별칭 (aliases) 정규화
- 알려진 유의어 강제 변환 (coerce)
- 유효한 항목 복구
- 진정으로 안전하지 않은 것만 거부
프롬프트에만 의존하면 시스템이 깨질 것이고, 파서에만 의존하면 모델이 더 자주 이탈 (drift)할 것입니다. 두 가지 모두가 필요합니다.
필요할 때 국제 DashScope 엔드포인트 사용하기
저희의 배포 환경에서는 국제 엔드포인트가 적절한 선택이었습니다:
dashscope-intl.aliyuncs.com
또한, 이러한 유형의 대량 에이전트 워크로드 (bulk agent workload)의 경우, qwen-plus가 qwen-max보다 더 나은 절충안이었습니다. 충분히 능력이 있으면서도 훨씬 저렴하기 때문입니다.
localhost뿐만 아니라 배포된 URL에서 테스트하기
모델과 관련 없는 버그 중 하나는 놓치기 매우 쉬웠습니다.
crypto.randomUUID는 로컬에서는 작동했지만, 보안 컨텍스트 (secure contexts)에서만 존재하기 때문에 일반 HTTP 데모 IP에서는 실패했습니다.
따라서 "New investigation" 기능이 localhost에서는 작동했지만, 배포된 URL에서는 작동하지 않았습니다.
실제 브라우저 테스트가 중요합니다.
결과: 차트, 증거 칩 (evidence chips), 제안된 다음 단계가 포함된 인용 답변이 실제 Qwen을 사용하여 Alibaba Cloud ECS에서 라이브로 실행됩니다.
결론
모든 결론이 그것을 생성한 기록, 문서, 그리고 결정 사항들로 거슬러 올라가 추적될 수 있을 때, 에이전트 시스템 (Agentic system)은 더욱 신뢰할 수 있게 됩니다.
Qwen Cloud는 기술적인 법률 도메인에서 두 가지 언어로 계획을 세우고 서술할 수 있을 만큼 충분히 강력한 모델을 우리에게 제공했습니다.
하지만 이번 해커톤의 엔지니어링 교훈은 단순히 모델을 사용하는 방법만이 아니었습니다. 진짜 교훈은 모델이 무엇을 소유해서는 안 되는지를 결정하는 것이었습니다.
이러한 종류의 규제 워크플로 (Regulatory workflow)에서 모델은 의도 (Intent), 계획 (Planning), 그리고 언어 (Language) 측면에서 도움을 줄 수 있습니다.
하지만 최종적인 구조화된 결과물 (Structured outcome)은 반드시 계산되고, 검증되며, 추적 가능해야 합니다.
프로젝트: AgentOps Debugger — OEFA 환경 준수 (Environmental Compliance)
해커톤: Qwen Cloud Hackathon, Track 3 — Agent Society
코드: github.com/GinoLlerena/agentops-debugger-architecture
라이선스: MIT
스택: Qwen Cloud 기반의 Qwen, DashScope, Mastra, AI SDK v5, Hono, React, Docker, Alibaba Cloud ECS.
이 포스트는 AI의 도움(Claude Code)을 받아 작성되었습니다 — 이는 우리가 해커톤 기간 동안 페어 프로그래밍 (Pair-programmed)을 했던 바로 그 어시스턴트입니다. 버그, 수정 사항, 그리고 교훈들은 우리의 실제 빌드 로그 (Build log)에서 가져온 것이며, 이 글이 설명하는 프로젝트가 검증되지 않은 AI 출력물을 절대 신뢰하지 않는 것에 관한 것이라는 점은 매우 적절합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

