← 런처 · 🎬 코믹스로 먼저 보기 (5분, 그림으로) · 📑 코드 총정리 (레퍼런스 — 정확한 컬럼·플래그·SQL)

eval_ph1 — 코드 동작 총정리

멀티에이전트 팀을 벤치마크로 평가하고, 모든 실행 과정을 PostgreSQL trace DB에 기록해 팀 구성 변경의 효과를 데이터로 검증하는 시스템 — 동작·설정·워크플로우·DB 메트릭 수집·Grafana 연동을 한 페이지로.

처음이라면 → 먼저 코믹스(5분)로 큰 그림을 잡고, 이 페이지는 정확한 이름·규칙을 찾을 때 사전처럼 쓰세요.
~44 teams14 migrations8+ benchmarks9 Grafana dashboards3 materialized viewsPython 3.11 · async
↑ 목차
🔑 30초 용어집 — 막히면 여기로 돌아오세요
멀티에이전트 팀 SelectorGroupChat — 역할 다른 AI들의 협업
벤치마크 benchmark — 정답 있는 문제집
trace events — 실행 중 일어난 모든 일의 기록
run runs.run_id — 문제 1개를 1번 푼 시도
pass@1 mv_batch_summary.pass_at_1 — 한 번에 맞힌 비율
p95 p95_latency — 느린 쪽 5% 응답시간(꼬리 지연)
F1 research_grounding — 인용 정확도(precision·recall 조화평균)
grounding v_analysis_grounded — 답이 실제 근거에 뿌리내렸는지
MAST failure_stage·root_cause — 실패 원인 2축 분류
materialized view mv_* — 미리 계산해 둔 집계 표
🧭 읽는 순서
  1. 맨 위 30초 요약 카드부터 — 이 시스템이 누구를 위해 무슨 문제를 푸는지 일상어로 잡습니다.
  2. 30초 용어집을 한 번 훑어 두세요 — pass@1·p95·F1·grounding·trace·run·MAST 같은 단어에서 막히면 여기로 돌아옵니다.
  3. 큰 그림(🟢): §0 한눈에(데이터 흐름) → §1 run 1회의 생애 → §4 팀·재현성 가드. 여기까지면 전체 동작이 잡힙니다.
  4. 색 약속(범례)을 §0 도식 아래에서 확인 — 파랑=명령, 보라=AI팀, 초록=데이터·성공, 노랑=기록, 빨강=실패. 이후 모든 도식·표에 똑같이 적용됩니다.
  5. 각 섹션은 '그림 먼저, 표는 사전' 구조입니다 — 도식으로 흐름을 잡고, 정확한 컬럼·플래그가 필요할 때만 표를 lookup하세요.
  6. 깊은 레퍼런스(🔵): §2 CLI · §3 env · §5~11(이벤트 로깅·채점·DB 스키마·집계 뷰·Grafana·정합성·절대규칙)은 필요할 때 골라 봅니다.
  7. '그림으로 다시 보고 싶다'면 헤더/하단의 코믹스 링크로 — 같은 내용을 이야기로 풉니다.

0한눈에 — 데이터가 흐르는 길

⏱ 30초 요약 — 이게 뭐예요?
  • AI 여러 명이 한 팀(P-E-C: 계획·실행·검증)이 되어 문제를 푸는데, '진짜 더 똑똑해졌는지'를 사람 눈대중 대신 점수로 확인하는 채점·기록 시스템입니다.
  • 모든 실행의 대화·도구호출·결과를 PostgreSQL 데이터베이스(trace DB = 모든 실행을 그대로 적는 기록용 DB)에 한 줄씩 그대로 적어둡니다 — runs(실행 1회의 결과/조건) + events(그 안의 모든 메시지).
  • 그 숫자를 그래프(Grafana)로 비교해 '어떤 팀 구성이 더 나은지'를 증거로 증명합니다 (pass@1, 지연 p95, 인용 F1 등).
  • 공정 비교가 깨지지 않도록 프롬프트가 한 글자라도 바뀌면 새 team_id를 강제합니다 (재현성 가드 — 해시 불일치 시 즉시 중단).
  • 명령 한 번(cli.py run) → 태스크 선택 → 팀 실행 → 전부 기록 → 채점 → 집계 뷰 → Grafana. 이 한 줄이 페이지 전체의 뼈대입니다.
  • 이 페이지는 '사전'입니다 — 큰 그림은 코믹스(5분)에서 잡고, 여기서는 정확한 컬럼명·플래그·SQL을 찾아 쓰세요.

한 줄 요약(식별자 버전): CLI가 러너를 부르고 → 러너가 팀을 돌리고 → 로거가 모든 메시지를 events에, 채점기가 결과를 runs에 적고 → 집계 뷰를 거쳐 Grafana 패널이 읽는다.

cli.py
typer · run/report/compare
runner.py
run_batch() · async
teams/registry
SelectorGroupChat
team.run_stream()
P–E–C 멀티에이전트
tracing/logger
AutoGen 이벤트 매핑
PG · events
메시지 1줄=1행 (seq)
+
gaia/scorer 등
success·format_valid
PG · runs
실행 1회의 결과/조건
runs + events
원천 테이블
materialized views
mv_batch_summary · mv_passk · mv_tool_summary
Grafana
postgres datasource → SQL 패널
🗺 전체 데이터 흐름 (상세) — 왼쪽 cli.py에서 시작해 화살표를 따라가면 마지막에 Grafana 그래프가 됩니다
flowchart TB
classDef cli fill:#eaf0fd,stroke:#2f5bd0,color:#16202c;
classDef orch fill:#efeaff,stroke:#6b4fc4,color:#16202c;
classDef cap fill:#fdf4e3,stroke:#b07400,color:#16202c;
classDef db fill:#e9f6ee,stroke:#1f8a4c,color:#16202c;
classDef viz fill:#eaf0fd,stroke:#2f5bd0,color:#16202c;
CLI["🧑‍🔬 cli.py · run / report / compare"]:::cli
subgraph ORCH["② 오케스트레이션 · runner.py"]
  RB["runner.run_batch<br/>async · Semaphore · asyncpg pool"]:::orch
  REG["teams.registry<br/>build + 재현성 가드"]:::orch
  TEAM["SelectorGroupChat · run_stream<br/>Planner → Executor → Critic · Selector"]:::orch
end
subgraph CAP["③ 기록 · 채점"]
  LOG["TraceLogger<br/>event→message_type · 마스킹"]:::cap
  SC["scorer<br/>success · format_valid"]:::cap
end
subgraph DBP["④ PostgreSQL · schema eval_ph1"]
  T1[(tasks)]:::db
  T2[(teams)]:::db
  RUNS[(runs ★)]:::db
  EV[(events ★)]:::db
  MV["mv_batch_summary · mv_passk · mv_tool_summary"]:::db
  VW["v_research / v_analysis / v_cppx grounding"]:::db
end
GRAF["📊 Grafana ①–⑥ · MAST"]:::viz
CLI -->|"run_batch(TaskFilter)"| RB
RB --> REG --> TEAM
RB -.->|select| T1
REG -.->|upsert| T2
TEAM -->|AutoGen events| LOG
TEAM -->|final_answer| SC
LOG -->|INSERT seq| EV
SC -->|UPDATE success| RUNS
RB -.->|refresh_views| MV
RUNS --> MV
EV --> MV
MV -->|postgres SQL| GRAF
VW -->|live view| GRAF
    
CLI가 러너를 부르고 → 러너가 팀을 돌리고 → 로거/채점기가 events·runs에 적고 → 집계뷰(mv_*)·라이브뷰(v_*)를 거쳐 Grafana가 읽는다. ★=핵심 테이블
🎨 도식 색상 범례 파랑 (cli) — 명령/실행 보라 (orchestration) — AI팀·시각화 노랑 (capture) — 기록/채점·주의 초록 (db / success) — 데이터·성공 빨강 (fail / stop) — 실패·중단·금지 📍 도식 읽는 법: 화살표를 따라가세요 (위 색은 이후 8개 도식·표에 그대로 적용됩니다)
핵심 분리: runs = 실행 1회의 결과/조건(성공·토큰·지연·temperature/seed), events = 그 run 안에서 일어난 모든 메시지(seq 순서). 메트릭은 거의 전부 이 둘에서 파생된다.

1run 1회의 생애 — run_batch()

runner.py run_batch() 가 (task × attempts)개의 run을 세마포어로 동시 실행한다. 각 run의 단계:

검증 & 풀 생성attempts≥1, concurrency≥1 확인. db.create_pool(min=1, max=max(concurrency+1,2))
팀 보장registry로 팀 빌드 → teams 행 upsert(ensure_team_record). 재현성 가드 작동(§4).
태스크 선택/필터SQL WHERE: benchmark=$1 · (level) · (requires_web=false if --exclude-web) · (task_id=ANY) · LIMIT · ORDER BY task_id
run 행 INSERT상태 running. 이 시점에 조건이 박제된다: model_name=EVAL_LLM_MODEL, temperature=EVAL_DEFAULT_TEMPERATURE(기본 0.2), seed=EVAL_DEFAULT_SEED, max_messages. 이미 completed면 skip / crashed--retry-crashed 여부로 분기.
팀 실행 + 스트림 로깅TraceLogger(conn).log_stream(run_id, team.run_stream(task=msg)). 매 이벤트를 events에 적재(§5). 반환: final_answer(한 줄)·final_answer_full·stop_reason·turns·tokens(in/out)·latency_ms.
채점benchmark별 채점기 선택(gaia/analysis/extract/sql/cppx/meeting/ocrproxy/coding…). open-ended는 final_answer_full로 채점(§6). → success·format_valid.
run 행 UPDATE상태 completed, terminated_by(approve/max_messages), final_answer, success, format_valid, turns/tokens/latency, ended_at=now(). 예외 시 crashed(terminated_by='error').
뷰 갱신배치 종료 시 db.refresh_views() → mv_batch_summary · mv_passk · mv_tool_summary REFRESH (안 하면 report/Grafana가 stale).
⏱ run 1회의 워크플로우 — 시퀀스 & 상태 전이 — 위→아래로 메시지가 오가고, 오른쪽 상태기계는 running에서 시작합니다
sequenceDiagram
autonumber
actor OP as 평가자
participant R as runner
participant DR as runs(DB)
participant TM as Team P–E–C
participant LG as TraceLogger
participant DE as events(DB)
OP->>R: cli.py run --batch --team --attempts
R->>DR: INSERT status=running (temp·seed·model 박제)
R->>TM: build + run_stream(task)
loop 매 이벤트
  TM->>LG: AutoGen 이벤트
  LG->>DE: INSERT (seq · 마스킹 · 절단+해시)
end
TM-->>R: trace_result(final_answer, tokens, latency)
R->>R: score_fn(answer, gold)
R->>DR: UPDATE completed (success, format_valid)
Note over R,DR: 예외→crashed · 배치끝→refresh_views()
R-->>OP: BatchSummary
        
시퀀스 — 메시지 순서와 누가 무엇을 쓰는지
stateDiagram-v2
[*] --> running: INSERT
running --> completed: 채점·UPDATE
running --> crashed: 예외
running --> timeout: max_messages
crashed --> running: --retry-crashed
completed --> [*]
timeout --> [*]
        
runs.status 상태기계
동시성 모델: asyncio.Semaphore(concurrency) + asyncpg 풀(max=concurrency+1). 크래시는 run 단위로 흡수돼 배치 요약에 집계될 뿐 전체를 멈추지 않는다(--retry-crashed로 재실행, --task-ids로 1개씩 독립 실행 가능).

2CLI 레퍼런스 — python cli.py <cmd>

Typer 앱. 기본 팀 v1-baseline. 평가/적재/리포트/실험 분석 + 개인화(fleet/propose/learn) 명령.

명령역할주요 플래그 (기본값)
init-dbmigrations 001+ 멱등 적용(000_provision은 DBA 수동)
load-gaiaGAIA metadata.jsonl → tasks 적재
load-research리서치 벤치 tasks.jsonl 적재--dir, --benchmark research|research_hard
load-cppx전문가 코딩 문제셋 적재(benchmark='cppx')--dir, --level-dir L5_full
run평가 배치 실행(태스크 필터 → run → 채점)아래 표 참조
report배치 요약(level별 pass@1·fmt_err·loop·turns·p95)--batch(필수)
compare배치 ≥2개 A/B 비교(level별 pass@1·p95)--batches a,b(필수)
failures실패/크래시 run 트랜스크립트 요약--batch, --limit 10
grounding-score리서치 인용 F1 채점 → research_grounding--batch(필수)
cppx-grounding코딩 보고서 사실 인용 채점 → cppx_grounding--batch(필수)
export-team팀을 AutoGen Studio JSON 컴포넌트로 직렬화--team, --out, --baseline-tool
fleet-run야간 플릿 사이클(상시의도→실패triage→모닝브리프)--date, --domains, --dry-run/--commit
propose이상/반복 신호 → 개선 제안(personal_proposals)--domains, --dry-run/--commit, --git-lookback 30
load-learn / learn-next학습 동반자 태스크 적재 / 다음 커리큘럼 선택

run 플래그 (핵심)

플래그타입기본의미
--batchstr필수배치 ID(실험 단위 식별자)
--teamstrv1-baseline인스턴스화할 팀 ID(registry 키)
--benchmarkstrgaia태스크 벤치(gaia|research|cppx…)
--levelint?NoneGAIA level / research hops 필터
--exclude-webboolFalserequires_web=false 태스크만
--task-idsstr?None콤마구분 특정 task만(크래시 격리에 유용)
--limitint?None선택 태스크 수 상한
--attemptsint1태스크당 반복(pass@k 측정용)
--concurrencyint?EVAL_CONCURRENCY동시 실행 워커 수
--retry-crashedboolFalsecrashed run 삭제 후 재실행

3설정 — config.py (env, 하드코딩 금지)

pydantic-settings BaseSettings. 모든 키 EVAL_* 접두사, .env에서 로드. 비밀정보는 로그/예외/커밋에 노출 금지.

env필드기본의미
EVAL_PG_DSNpg_dsn필수PostgreSQL 접속 DSN
EVAL_LLM_BASE_URLllm_base_urlNone내부 vLLM(OpenAI 호환) 엔드포인트
EVAL_LLM_MODELllm_modelNone모델명(예: glm-4.7) → runs.model_name
EVAL_LLM_API_KEYllm_api_keyNoneLLM API 키
EVAL_DEFAULT_TEMPERATUREdefault_temperature0.2샘플링 temperature → runs.temperature
EVAL_DEFAULT_SEEDdefault_seedNoneseed → runs.seed (비교 가능성용)
EVAL_MAX_MESSAGESmax_messages20run당 최대 메시지(루프 종료 한계)
EVAL_CONCURRENCYconcurrency1기본 동시 워커 수
EVAL_GAIA_DIRgaia_dirdata/gaia/validationGAIA 태스크 디렉토리
EVAL_RESEARCH_DIR / EVAL_CPP_DIRresearch_dir / cpp_dir경로리서치 / 코딩 문제 디렉토리
EVAL_FIRECRAWL_BASE_URLfirecrawl_base_urlapi.firecrawl.dev웹검색 백엔드
EVAL_FIRECRAWL_API_KEYfirecrawl_api_keyNone있으면 Firecrawl, 없으면 DuckDuckGo 폴백
EVAL_CLOSED_NETclosed_netTrue폐쇄망 안전 토글
temperature·seed·model은 NOT NULL 취급: 기록 안 된 실행은 비교 불능. run INSERT 시점에 박제되며 LLM 클라이언트에도 동일 값이 주입된다.

4팀 & 재현성 가드

팀은 SelectorGroupChat(셀렉터가 다음 발화자를 라우팅). 베이스라인은 P–E–C 3-에이전트.

v1-baseline 구조 (Planner – Executor – Critic)

에이전트역할도구
planner 🤖계획 수립 + FINAL ANSWER 초안없음
executor 🛠도구 실행(파일/산술)read_attached_file, python_calc(AST allowlist, eval 금지)
critic 🧐형식·정답 검증 → APPROVE 또는 수정요구없음

셀렉터는 JSON 출력 {"next","reason_code","summary"} — reason_code ∈ {NEEDS_PLANNING, NEEDS_EXECUTION, NEEDS_VERIFICATION, NEEDS_REVISION, READY_TO_FINAL}. 종료조건: TextMentionTermination("APPROVE", sources=["critic"]) | MaxMessageTermination(max_messages).

등록 팀 ~44개, 네이밍 패턴: v1~v6-baselineanalyst-solo/deep/webhq-research/content/coding/investresearch-pipeline/grounded/xverifysql-solo/executed/groundedcppx-solo/panelmetric-format/loop/groundingextract-solo/validated

🚦 셀렉터 루프 (Planner–Executor–Critic) — 가운데 셀렉터가 reason_code로 다음 발화자를 고릅니다 (점선=발화 후 복귀)
flowchart LR
classDef sel fill:#efeaff,stroke:#6b4fc4;
classDef p fill:#eaf0fd,stroke:#2f5bd0;
classDef e fill:#e9f6ee,stroke:#1f8a4c;
classDef c fill:#fdf4e3,stroke:#b07400;
classDef fin fill:#e9f6ee,stroke:#1f8a4c;
SEL{{"🚦 Selector<br/>next + reason_code (JSON)"}}:::sel
P["🤖 Planner<br/>계획 · FINAL ANSWER 초안"]:::p
E["🛠 Executor<br/>read_attached_file · python_calc"]:::e
C["🧐 Critic<br/>형식 · 정답 검증"]:::c
F["🏁 종료<br/>APPROVE | max_messages"]:::fin
SEL -->|NEEDS_PLANNING| P
P -.->|발화| SEL
SEL -->|NEEDS_EXECUTION| E
E -.->|결과| SEL
SEL -->|NEEDS_VERIFICATION| C
C -.->|NEEDS_REVISION| SEL
C -->|APPROVE| F
    
셀렉터가 reason_code로 다음 발화자를 라우팅 · allow_repeated_speaker=false · max_selector_attempts=3 · 종료는 critic의 APPROVE 또는 max_messages

재현성 가드 (CLAUDE.md 절대규칙 #3 / SPEC AC9)

# 팀 빌드 시 프롬프트 번들 해시 계산 (v1_baseline.py)
joined = "\n---\n".join([PLANNER_PROMPT, EXECUTOR_PROMPT, CRITIC_PROMPT, SELECTOR_PROMPT])
prompt_bundle_hash = sha256(joined.encode()).hexdigest()

# registry.ensure_team_record(): 같은 team_id인데 해시가 다르면 즉시 중단
if existing_hash is not None and existing_hash != new_hash:
    raise TeamConfigMismatchError("구성이 바뀌었으면 새 team_id를 쓰세요")
🔒 재현성 가드 흐름 (해시 불일치 → 즉시 중단) — 빌드→해시 비교 분기, YES(빨강)면 즉시 중단·NO(초록)면 진행
flowchart LR
classDef ok fill:#e9f6ee,stroke:#1f8a4c;
classDef bad fill:#fbecea,stroke:#c0392b;
B["팀 빌드"] --> H["prompt_bundle_hash<br/>sha256(planner+executor+critic+selector)"]
H --> Q{"기존 team_id 행과<br/>해시 불일치?"}
Q -->|YES| X["raise TeamConfigMismatchError<br/>즉시 중단"]:::bad
Q -->|NO| K["ensure_team_record<br/>runs 진행"]:::ok
    
프롬프트가 한 글자라도 바뀌면 해시가 달라진다 — 같은 team_id로 재사용하면 비교가 오염되므로 DB 레벨에서 막는다(AC9)
규칙: 프롬프트가 한 글자라도 바뀌면 새 team_id. 기존 team_id + 다른 prompt_bundle_hash 조합은 즉시 에러로 중단 — "좋아졌다"는 비교가 오염되는 걸 DB 레벨에서 막는다.

config_capture.py는 빌드 시점의 전체 구성을 teams.config_full(JSONB)에 스냅샷한다 — 에이전트별 system_prompt+sha, 도구별 impl_sha, selector_prompt, termination, allow_repeated_speaker, max_selector_attempts. git 없이도 Grafana ⑥ 대시보드에서 팀 구성을 추적할 수 있다.

5이벤트 로깅 파이프라인 — tracing/logger.py

AutoGen 이벤트 스트림을 받아 message_type으로 매핑하고 events 테이블에 seq 순서로 적재.

📒 이벤트 → events 적재 파이프라인 — 왼쪽 이벤트들이 가운데 매핑을 거쳐 오른쪽 events 행(초록)으로 떨어집니다
flowchart LR
classDef ev fill:#eef2f8,stroke:#9aa7b6;
classDef map fill:#efeaff,stroke:#6b4fc4;
classDef db fill:#e9f6ee,stroke:#1f8a4c;
subgraph S["AutoGen 이벤트 스트림"]
  A1["TextMessage / ToolCallSummary"]:::ev
  A2["ToolCallRequestEvent"]:::ev
  A3["ToolCallExecutionEvent"]:::ev
  A4["SelectorEvent"]:::ev
  A5["TaskResult"]:::ev
end
M["mapping → message_type<br/>seq+1 · 비밀 *** 마스킹<br/>content/result 절단+해시<br/>INSERT 실패→경고 후 계속"]:::map
subgraph EVS["events 행 (run_id, seq 순)"]
  R1["agent_message"]:::db
  R2["tool_call (id·name)"]:::db
  R3["tool_result / tool_error"]:::db
  R4["selector_decision"]:::db
  R5["final_answer"]:::db
end
A1 --> M
A2 --> M
A3 --> M
A4 --> M
A5 --> M
M --> R1
M --> R2
M --> R3
M --> R4
M --> R5
R2 -.->|"응답 짝 필수 (CHECK)"| R3
    
AutoGen 이벤트를 message_type으로 매핑해 seq 순으로 한 줄씩 적재 — 도구 호출엔 반드시 응답(result/error) 짝이 따라붙는다(DB CHECK). 로깅 실패는 평가를 멈추지 않는다.

AutoGen 이벤트 → message_type 매핑 (mapping.py)

AutoGen 이벤트message_type비고
TextMessage / ToolCallSummaryMessageagent_message일반 에이전트 발화
ToolCallRequestEventtool_callFunctionCall당 1행
ToolCallExecutionEventtool_result / tool_error결과 항목별 분기
SelectorEvent / SelectSpeakerEventselector_decision라우팅 결정
HandoffMessagehandoff에이전트 핸드오프
TaskResult (종료)final_answerrun 종료
EvaluatorScoreEventevaluator_score교차검증 판정(FabricMAS)
그 외(streaming chunk, thought…)None → skip조용히 무시

events 적재 규칙

per-event INSERT (배치 아님) seq: 0부터 +1 (멀티항목 이벤트도 항목별 +1) content 8192자 / tool_result 2048자 절단 + 해시 final_answer는 planner에서, full은 critic 제외 마지막 에이전트
로깅은 평가를 죽이지 않는다(절대규칙 #2): events INSERT 실패 → _safe_insert()가 경고 로그 + missed_inserts 카운트만 올리고 평가는 계속. 배치를 멈추는 건 DB 연결 자체의 소실뿐.
비밀정보 마스킹(절대규칙 #5, 단일 지점): tool_args 저장 전 키가 (password|token|secret|api_key|authorization)(대소문자 무시)에 매치되면 값을 "***"로 치환. 구현은 logger.py 한 곳.

events 주요 컬럼: run_id, seq, agent_name, message_type, content(+truncated), tool_call_id, tool_name, tool_args(JSONB·마스킹), tool_result(JSONB·해시), tool_error(JSONB), selected_next_agent, selector_reason(JSONB), tokens/input/output, latency_ms, ts, eval_meta(JSONB·009)

6채점 & 메트릭 — 무엇이 어디에 적히나

GAIA 채점 (gaia/scorer.py) — 정답 1:1 비교

⚖️ 채점기 라우팅 (benchmark별 → 저장 위치) — benchmark 분기를 따라 채점기를 고르고, 오른쪽 저장 위치로 이어집니다
flowchart LR
classDef io fill:#eef2f8,stroke:#9aa7b6;
classDef sc fill:#fdf4e3,stroke:#b07400;
classDef out fill:#e9f6ee,stroke:#1f8a4c;
IN["final_answer<br/>(open-ended: _full)"]:::io
RT{"benchmark?"}
IN --> RT
RT -->|gaia| G["gaia.score<br/>정규화 · 리스트 순서보존 · 1e-6"]:::sc
RT -->|sql| SQ["sql.score<br/>실행결과 멀티셋"]:::sc
RT -->|research| RE["research.grounding<br/>인용 F1"]:::sc
RT -->|analysis| AN["analysis.score<br/>형식만 (정답 비교 없음)"]:::sc
RT -->|extract| EX["extract.score<br/>field_accuracy"]:::sc
RT -->|cppx · roles| CP["fact-ratio · coverage · CER"]:::sc
G --> O1["runs.success / format_valid"]:::out
SQ --> O1
AN --> O1
EX --> O1
RE --> O2["도메인 테이블<br/>research_ / cppx_grounding"]:::out
CP --> O2
    
벤치마크에 따라 채점기를 고른다 — 공통 결과(success/format_valid)는 runs에, 인용·사실 grounding은 도메인 테이블에 적힌다.

도메인별 채점기 → 컬럼/테이블

도메인측정판정 규칙저장
gaia정답 정확일치정규화 후 일치runs.success/format_valid
extract필드 추출 정확도모든 gold 필드 일치 / field_accuracy=일치/전체runs + 산출
sql실행 결과 일치pred·gold SQL 실행 후 멀티셋 비교(순서무관, 부동소수 4자리)runs
analysis보고서 형식 유효성FINAL ANSWER 추출 성공(정답 비교 없음)runs + grounding 뷰
research인용 정확도precision/recall/F1(cited∩gold)research_grounding
roles(meeting)액션아이템 커버리지키워드 60%↑ 매치, coverage≥0.5 통과runs
roles(ocr)문자오류율 CERLevenshtein/gold_len ≤0.15 통과runs
cppx사실 인용 비율gold 사실 등장 비율 + objective 방향 일치cppx_grounding
grounding의 두 정의(뉘앙스 주의): research grounding = 인용 doc-id의 F1(정답 근거를 얼마나 정확히 댔나). analysis grounding = 실제 검색 결과가 있었나(v_analysis_grounded: n_search_ok > 0) — "호출 횟수" 기반의 옛 정의는 검색 100% 실패인데 grounded 100%로 보이던 환각을 잡지 못해, 성공한 검색 기준으로 교정됨.

report / compare / experiments

reportmv_batch_summary(pass@1·format_invalid_rate·loop_rate·tokens_per_success·p95)·mv_passk(pass_any/all·k)·mv_tool_summary를 읽어 표로 출력. experiments 테이블은 단일변수 개선을 기록: title·hypothesis·intervention·team_before/after·batch_before/after·verdict(promoted/rejected/inconclusive)·key_evidence(JSONB)·lesson — Grafana 시계열에 실험 마커(annotation)로 겹쳐 보인다.

7DB 스키마 & 불변식 (migrations/*.sql, ORM 금지)

스키마가 SSOT. 변경은 번호 증가 SQL 파일로만(기존 수정 금지). DB invariant는 애플리케이션 검증이 아니라 CHECK 제약으로 강제.

🗄 스키마 ER (핵심 관계) — 선의 갈래(||--o{)가 1:N 관계, tasks·teams에서 runs로, runs에서 events로 이어집니다
erDiagram
  tasks ||--o{ runs : "task_id"
  teams ||--o{ runs : "team_id"
  runs ||--o{ events : "run_id · CASCADE"
  runs ||--o| research_grounding : "run_id"
  runs ||--o| cppx_grounding : "run_id"
  teams ||--o{ experiments : "before/after"
  tasks {
    text task_id PK
    text benchmark
    text gold_answer
    bool requires_web
  }
  runs {
    uuid run_id PK
    text batch_id
    real temperature
    bool success
    text status
  }
  events {
    uuid run_id FK
    int seq PK
    text message_type
    jsonb tool_args
  }
    
tasks·teams → runs (1:N) → events (1:N, run 삭제 시 CASCADE). 도메인 grounding은 run당 0/1행. experiments는 team_before/after로 팀 진화를 추적.

핵심 테이블

테이블PK핵심 컬럼
taskstask_idbenchmark, level, question, gold_answer, file_name, requires_web, annotator_meta(JSONB)
teamsteam_idagents(JSONB), topology, prompt_bundle_hash, config_full(JSONB·004)
runsrun_id(UUID)team_id, task_id, attempt, batch_id, model_name, temperature, seed, status, terminated_by, success, format_valid, total/input/output_tokens, latency_ms, judge_meta, failure_stage, root_cause
events(run_id, seq)message_type, content, tool_*, selector_reason, tokens, latency_ms, ts, eval_meta(009)

DB가 강제하는 불변식 (CHECK)

completed → success NOT NULL failure_stage → root_cause NOT NULL tool_call → tool_call_id & tool_name tool_result → tool_call_id tool_error → tool_error payload NOT(tool_result & tool_error 동시) temperature/latency/tokens/seq ≥ 0 UNIQUE(batch_id, task_id, attempt) status ∈ running/completed/crashed/timeout
가드 철학: 제약을 통과 못 하는 데이터가 보이면 제약을 풀지 말고 데이터 생성부를 고친다.

마이그레이션 타임라인 (003–014)

#추가 객체용도
003experiments + v_experiment_annotations단일변수 실험 원장 / Grafana 마커
004teams.config_full + v_team_config/v_team_orchestrationgit 없이 팀 구성 관측
005v_team_graph_nodes/edgesGrafana Node Graph(셀렉터 허브)
006research_grounding + 요약뷰인용 F1/precision/recall
007research_corpus코퍼스 문서(real vs decoy)
008cppx_grounding + 요약뷰코딩 보고서 사실 인용
009events.eval_meta교차검증 판정 메타
010v_analysis_grounded + 요약뷰분석 grounding(성공검색 기준 재정의)
011failure_stages·root_causes seedMAST 통제어휘(2축 실패분류)
012personal_proposals개선 제안(promoted엔 exp_id 필수)
013runs.escalated·learner_skill·topology 확장학습 슬라이스(SM-2 간격반복)
014standing_intentions·fleet_briefs야간 플릿 상시의도/모닝브리프
2차용 placeholder: claims·evidence 테이블, failure_stage/root_cause 자동 라벨링, LLM-judge는 스키마만 존재, 코드 구현 금지(Tier 1~2 범위).

8집계 뷰 — 002_views.sql (배치 종료 시 REFRESH)

원천 runs/events를 배치별로 미리 계산. report와 Grafana 대부분이 이 3개를 읽는다.

CREATE MATERIALIZED VIEW mv_batch_summary AS
SELECT batch_id, team_id, level,
  COUNT(*)                              AS n_runs,
  AVG(success::int)                     AS pass_at_1,         -- 1회 성공률
  AVG((NOT format_valid)::int) FILTER(...) AS format_invalid_rate,
  AVG((terminated_by='max_messages')::int) AS loop_rate,    -- 루프 소진
  SUM(total_tokens)::float/NULLIF(SUM(success::int),0) AS tokens_per_success,
  PERCENTILE_CONT(0.95) WITHIN GROUP(ORDER BY latency_ms) AS p95_latency
FROM runs r JOIN tasks t USING(task_id)
WHERE status='completed' GROUP BY 1,2,3;
그룹핵심 산출
mv_batch_summarybatch×team×levelpass@1, format_invalid_rate, loop_rate, avg_turns/tokens, tokens_per_success, p95_latency
mv_passkbatch×team×taskk(시도수), pass_any(BOOL_OR), pass_all(BOOL_AND)
mv_tool_summarybatch×toolcalls, errors, avg_latency
흔한 실수: 배치 종료 시 REFRESH를 잊으면 report/Grafana가 stale. run_batch() 끝에서 db.refresh_views()가 자동 호출되지만, 수동 적재 후엔 직접 갱신 필요. (도메인 뷰 v_research_grounding 등은 live view라 갱신 불필요)

9Grafana 연동 — DB에서 패널까지

Grafana 컨테이너가 PostgreSQL에 직접 붙어(같은 docker 네트워크) SQL 패널로 뷰/테이블을 읽는다. 코드 배포 없이 데이터가 즉시 그래프가 된다.

📊 메트릭 → 뷰 → 대시보드 매핑 — 왼쪽 집계뷰(초록)에서 화살표를 따라가면 오른쪽 Grafana 대시보드(파랑)에 닿습니다
flowchart LR
classDef src fill:#e9f6ee,stroke:#1f8a4c;
classDef dash fill:#eaf0fd,stroke:#2f5bd0;
MBS["mv_batch_summary"]:::src
MPK["mv_passk"]:::src
MTS["mv_tool_summary"]:::src
VAG["v_analysis_grounded"]:::src
VRG["v_research_grounding"]:::src
VTC["v_team_config / graph"]:::src
RUN["runs (failure_stage)"]:::src
EXP["experiments"]:::src
D1["① 총괄 개요"]:::dash
D2["② 실험 메트릭"]:::dash
D3["③ 분석 grounding"]:::dash
D4["④ 리서치 grounding"]:::dash
D6["⑥ 팀 구성"]:::dash
DM["MAST 실패분류"]:::dash
MBS --> D2
MPK --> D2
MTS --> D2
EXP --> D2
EXP --> D1
VAG --> D3
VRG --> D4
VTC --> D6
RUN --> DM
RUN --> D1
    
어떤 집계뷰(mv_*)·라이브뷰(v_*)가 어느 Grafana 대시보드를 먹이는가 — 모두 같은 postgres datasource(eval_ph1_pg→evaldb)에서 SQL로 읽는다.

datasource (provisioning/datasources/eval_pg.yml)

항목
name / typeeval_ph1 / postgres (isDefault)
uideval_ph1_pg
urleval_ph1_pg:5432 (컨테이너명 직접 접근)
database / userevaldb / eval_ph1
searchPatheval_ph1 (모든 테이블이 사는 스키마)
networkeval-system-stack_evals-internal (PG와 공유 내부망)
postgresVersion / ssl1600 (PG 16) / disable

대시보드 자동 로딩 (provisioning/dashboards/dashboards.yml)

json/*.json을 file provider로 등록, updateIntervalSeconds: 30JSON 변경은 30초마다 자동 반영(재시작 불필요). MAST 대시보드는 json-mast/ 별도 폴더("MAST 논문 따라가기" 메뉴). 단, provider yaml 자체를 처음 추가/수정하면 Grafana 1회 재시작 필요.

대시보드 인벤토리

대시보드uid읽는 소스
① 총괄 개요eval_ph1_overviewexperiments, runs(completed), events
② 실험 메트릭(8407…)mv_batch_summary, mv_tool_summary, mv_passk + 실험 annotation
③ 분석 Groundingeval_ph1_analysis_groundingv_analysis_grounded(_summary)
④ 리서치 Groundingeval_ph1_research_groundingv_research_grounding(_summary)
⑤ 리서치 Corpuseval_ph1_research_corpusv_research_corpus(🔴decoy/🟢real)
⑥ 팀 구성eval_ph1_team_configv_team_config, v_team_orchestration, v_team_graph_*
HQ Teamshq_teamsruns(HQ), mv_batch_summary
발표용 메트릭 변화eval_pres_trendsruns, n_search vs n_search_ok
MAST 실패 분류eval_ph1_mastruns(failure_stage, root_cause), events 프록시
메트릭 흐름(종합): runs+eventsrefresh_views()mv_* → Grafana postgres datasource → SQL 패널. 도메인 grounding은 라이브 뷰(v_*)로 즉시 반영. 패널 정의 원본은 dashboards/grafana_panels.sql(10패널)·team_config_panels.sql(4패널).
라이브 배포 주의: 라이브 Grafana 스택은 레포 밖 /home/hm/vps-srv/grafana-stack. 대시보드 추가 시 레포와 라이브 양쪽 동기화 필요.

10정합성 점검 SQL (DoD — 둘 다 0행이어야)

-- 완료 run에 success 누락
SELECT run_id FROM runs WHERE status='completed' AND success IS NULL;

-- tool_call인데 응답(result/error) 짝이 없는 이벤트
SELECT e.run_id, e.seq FROM events e
WHERE e.message_type='tool_call' AND NOT EXISTS (
  SELECT 1 FROM events r WHERE r.run_id=e.run_id
    AND r.tool_call_id=e.tool_call_id
    AND r.message_type IN ('tool_result','tool_error'));

11절대 규칙 (위반 금지)

  1. 범위: Tier 1~2만 구현. claims/evidence·LLM-judge·자동 라벨링은 스키마만, 코드 금지.
  2. 로깅은 평가를 죽이지 않는다: trace INSERT 실패 → 경고 후 계속.
  3. 재현성 가드: 팀 구성 변경 → 새 team_id. 해시 불일치 즉시 중단.
  4. DB invariant는 CHECK로: 통과 못 하면 제약이 아니라 데이터 생성부를 고친다.
  5. 비밀정보: DSN/토큰을 로그·예외·커밋에 노출 금지. tool_args 마스킹 단일 지점.
  6. 외부 네트워크 가정 금지: 운영은 폐쇄망. LLM은 내부 vLLM(EVAL_LLM_BASE_URL)만.