03 릴리스하고 감지하기
알려진 정책 사각지대를 안은 선택된 에이전트를 릴리스하고, 운영 평가와 실제 사용자 피드백으로 이를 감지합니다.
시작 지점
모듈 02가 완료되었습니다. 실제 Arena 실행에서 선택된 승리 config_id를 기록한 다음,
랩 루트(ClickHouse_Demos/workshops/agent_arena)에서 이를 내보냅니다:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"검증된 워크숍 실행에서는 qwen3.7-flash__P2_fewshot이 선택되었습니다. 여러분의
방에서 다른 결과가 나왔다면 그 승자를 유지하세요. 측정했던 것과 동일한 모델과
프롬프트를 사용합니다 — 실패만을 위한 특별한 에이전트가 아닙니다.
승자가 Qwen이라면, OpenRouter Settings → Privacy → Data Policies → Zero Data Retention → Non-frontier를 비활성화해야 합니다. non-frontier ZDR이 강제되면 사용 가능한 Alibaba 경로가 거부됩니다. 실제 데이터에 대해 이 설정을 변경하기 전에 여러분의 프라이버시 요구사항을 검토하세요.
통과한 평가자가 사용자 가치를 놓칠 수 있는 이유
온라인 평가자는 설계된 그 차원만을 측정합니다. 여기서 sql-execution-success는
중요한 운영상의 질문에 답합니다: 에이전트가 ClickHouse가 실행할 수 있는 SQL을
생성했는가? 하지만 그 SQL이 활성 고객이라는 현재 비즈니스 정의를 따르는지는
알지 못합니다.
이것은 현실적인 모니터링 공백을 만듭니다:
| 신호 | 답하는 질문 | 이 인시던트에서 기대되는 값 |
|---|---|---|
sql-execution-success | 생성된 SQL이 성공적으로 실행되었는가? | true |
user-thumbs | 이 답변이 이 사용자의 필요를 충족했는가? | false |
운영 평가는 깨진 SQL, 타임아웃, 실행 오류를 잡아냅니다. 시맨틱 평가 또는 사용자 평가는 실행 가능한 답변이 실제로 유용하고 비즈니스 의미에 부합하는지를 묻습니다. 어느 쪽도 다른 쪽을 대체하지 않습니다. 👎는 우선순위를 정하는 신호일 뿐 근거 진실 (ground truth)이 아닙니다. 사람이 모듈 04에서 이를 조사할 것입니다.
목표
운영 평가자는 통과하지만 사용자는 답변을 낮게 평가하는 실제 chat_turn 트레이스를
하나 만듭니다. 그 트레이스와 서로 상충하는 두 개의 카운트를 사람의 조사를 위해
기록합니다.
1단계 — 시딩된 인시던트가 재현 가능한지 증명하기
데모 서버를 시작하기 전에 랩 루트에서 프리플라이트를 실행하세요:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"
.venv/bin/python -m schema.gen_schema_context
.venv/bin/python -m scripts.check_online_eval_scenario \
--config-id "$WINNER_CONFIG_ID"이 명령은 두 정의를 모두 실행하고 선택된 구성에 세 가지 다른 표현으로 같은 질문을
던집니다. 서로 다른 stale_count와 current_count 값, 세 개의
classification_N=policy-v1 줄, 그리고 다음 문구를 출력해야 합니다:
어떤 표현의 결과가 ok/unknown이면 프리플라이트는 동일한 표현과 구성만 한 번
다시 시도합니다. policy-v2 또는 공급자/모델/에이전트 실패는 재시도하지 않으며,
두 번째 결과도 ok/unknown이면 차단 상태로 남습니다.
OK: seeded online-evaluation incident is reproducible카운트가 같거나 분류 중 하나라도 policy-v1이 아니라면 멈추세요: 이 데이터/모델
실행에서는 대비가 드러나지 않습니다.
현재 비즈니스 정의는 정확히 이 SQL입니다:
SELECT uniqExact(customer_id) FROM v_orders
WHERE order_ts >= now() - INTERVAL 30 DAY
AND status NOT IN ('cancelled', 'returned')이전 policy-v1 정의는 대신 지난 90일 안에 가입한 고객을 셉니다. 따라서 이 모듈에서
모델이 생성하는 SQL은 명시적인 policy-v1 지침에 비추어 보면 유효합니다. 여기서
말하고자 하는 바는 모델이 똑똑하지 않다는 것이 아닙니다. 배포된 정책 컨텍스트가
오래된 상태이고, SQL 실행 평가자는 그것을 알아채기에는 너무 좁은 범위로 설계되어
있다는 것입니다.
2단계 — 운영 평가자 프로비저닝
프로비저닝은 멱등적(idempotent)이므로 다시 실행해도 안전합니다:
source .env
.venv/bin/python -m scripts.provision_online_evaluators --operational출력에는 평가자 이름 sql-execution-success와 활성화된 규칙
agent-arena-sql-execution-online이 표시되어야 합니다.
3단계 — 시딩된 오래된 릴리스 시작하기
첫 번째 터미널에서, 랩 루트에서 이전 정책을 명시적으로 선택한 채 서버를 시작하고 계속 실행 상태로 둡니다:
source .env
AGENT_ARENA_POLICY_VERSION=policy-v1 \
.venv/bin/uvicorn serving.api:app --port 8100AGENT_ARENA_POLICY_VERSION을 빠뜨리지 마세요. 이를 생략하면 서비스는 기본적으로
현재의 policy-v2를 사용하게 되는데, 이는 취소되거나 반품된 주문을 정확히
제외합니다.
4단계 — Chat을 통해 질문하고 평가하기
다른 터미널에서, 아직 실행 중이 아니라면 대시보드를 시작합니다:
scripts/arena.sh servehttp://localhost:5174를 열고, Chat 탭과 $WINNER_CONFIG_ID를 선택한 다음
질문합니다:
How many active customers do we have?생성된 SQL과 결과를 읽어보세요. policy-v1의 시딩된 90일 가입 정의를 따라야
합니다. 이 답변에 👎를 클릭하고 UI에서 feedback sent라고 표시될 때까지
기다립니다.
이 Chat 루트 트레이스는 여러분이 점수를 매기고 모듈 04로 넘길 단 하나의 근본(authoritative) 인시던트입니다.
5단계 — Chat 트레이스 찾고 검증하기
Langfuse에서 Tracing을 열고 user-thumbs = false로 필터링합니다. 질문이
How many active customers do we have?이고, 구성이 $WINNER_CONFIG_ID와 일치하며,
메타데이터가 policyversion=policy-v1을 보여주는 가장 최근의 루트 chat_turn을
엽니다. 트레이스 ID와 트레이스 URL을 복사한 다음, 로컬에서 ID를 설정합니다:
export CHAT_TRACE_ID="<paste the Chat trace ID>"
.venv/bin/python -m scripts.verify_online_scores "$CHAT_TRACE_ID" \
sql-execution-success=true user-thumbs=falseuser-thumbs가 숫자나 텍스트 점수가 아니라 Boolean false인지 확인하세요. 서빙
소스는 메타데이터 필드를 policy_version이라고 부르며, OpenTelemetry 어댑터는
이를 정제하여 Langfuse에 전달되는 키인 policyversion으로 만듭니다.
6단계 — 평가되지 않은 curl로 재현하기
원시 API 호출은 명령줄 수준에서 반드시 수행해야 하는 재현 및 진단입니다. 별도의 트레이스를 만들지만, 이것은 피드백 인시던트가 아니며 평가되어서는 안 됩니다:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"
ASK_BODY=$(.venv/bin/python -c \
'import json,sys; print(json.dumps({"question": sys.argv[1], "config_id": sys.argv[2]}))' \
"How many active customers do we have?" "$WINNER_CONFIG_ID")
CURL_RESPONSE=$(curl -fsS http://localhost:8100/ask \
-H 'content-type: application/json' -d "$ASK_BODY")
printf '%s\n' "$CURL_RESPONSE" | .venv/bin/python -m json.tool
CURL_TRACE_ID=$(printf '%s\n' "$CURL_RESPONSE" | .venv/bin/python -c \
'import json,sys; data=json.load(sys.stdin); assert data.get("policy_version") == "policy-v1"; assert data.get("outcome") == "ok"; trace_id=data.get("trace_id"); assert isinstance(trace_id, str) and trace_id; print(trace_id)')응답에는 outcome: "ok", 비어 있지 않은 trace_id, policy_version: "policy-v1"이
있어야 합니다.
비동기 평가자를 기다렸다가 그 운영 점수만 검증합니다:
.venv/bin/python -m scripts.verify_online_scores "$CURL_TRACE_ID" \
sql-execution-success=trueCURL_TRACE_ID에 대해 /feedback을 호출하지 말고 워크시트에도 넣지 마세요. 이는
재현 가능한 API 진단일 뿐이며, CHAT_TRACE_ID가 여전히 인계할 트레이스로
남습니다.
7단계 — 현재 정책과 비교하기
에이전트와 동일한 읽기 전용 ClickHouse 클라이언트를 통해 현재 정책 SQL을 실행한
다음, 그 단일 결과를 CURL_RESPONSE의 오래된 결과와 비교합니다:
.venv/bin/python - <<'PY'
from arena.config import load_config
from agents.chclient import ROClickHouseClient
sql = """SELECT uniqExact(customer_id) FROM v_orders
WHERE order_ts >= now() - INTERVAL 30 DAY
AND status NOT IN ('cancelled', 'returned')"""
result = ROClickHouseClient(load_config().clickhouse).query(sql)
print(result.rows[0][0])
PY방금 실행한 쿼리는 정확히 이것입니다:
SELECT uniqExact(customer_id) FROM v_orders
WHERE order_ts >= now() - INTERVAL 30 DAY
AND status NOT IN ('cancelled', 'returned')다른 카운트가 사용자가 겪는 실패입니다. SQL은 실행되었습니다. 다만 잘못된 비즈니스 정의에 답한 것입니다. 이 카운트를 근본 Chat 트레이스 증거와 함께 기록하세요.
조사 워크시트
모듈 04를 위해 이 인계 정보를 보관하세요:
| 증거 | 여러분의 값 |
|---|---|
승자 config_id | |
| 근본 Chat 트레이스 ID | |
| 근본 Chat 트레이스 URL | |
오래된 policy-v1 카운트 | |
현재 policy-v2 카운트 | |
sql-execution-success | true |
user-thumbs | false |
완료 여부 확인하기
- 프리플라이트에서 오래된/현재 카운트가 서로 달랐고, 시딩된 세 질문 모두
policy-v1로 분류되었습니다. - 서비스가
AGENT_ARENA_POLICY_VERSION=policy-v1로 실행되었고/ask가outcome: "ok"를 반환했습니다. - 근본 Chat 트레이스에 Langfuse에서
sql-execution-success=true와user-thumbs=false가 있습니다. - 필수 curl 진단이
outcome: "ok"를 반환하고, 별도의CURL_TRACE_ID를 생성했으며, 평가되거나 인계되지 않았습니다. - 자격 증명을 공유하지 않고 근본 Chat 트레이스 ID/URL과 두 카운트를 기록했습니다.
- SQL 실행 성공이 왜 시맨틱 정확성을 증명하지 못하는지 설명할 수 있습니다.
이 신호를 사람이 검토한 진단으로 바꾸려면 모듈 04 — 사람과 함께 조사하기로 계속하세요.