v2-analyst-deep, 웹 없음)과 개선(v6-analyst-web, 웹 검색+날짜+출처 인용)에 각각 돌린 결과.
둘 다 planner→analyst→synthesizer→critic 4-에이전트(z.ai GLM). 차이는 웹 근거 하나.
N=3의 뜻).
각 주제 run은 자기 주제만 검색한다(반도체 run은 반도체만). 그래서 §7 로그의 '반도체' 검색어는 전부 반도체/HBM/파운드리다.
| 품질 차원 | base (v2-analyst-deep) | 개선 (v6-analyst-web) | 왜 중요한가 |
|---|---|---|---|
| 근거성 (grounding) | 0% — 학습지식으로만 | 100% — 모든 핵심 사실 실검색 기반 | "지어낸 게 아니라 실제 자료에서 왔다" |
| 최신성 (recency) | 2025 컷오프 시점 일반론 | 2026-06-14 기준 (AI 슈퍼사이클 반영) | 오래된 정보가 아니라 지금 상황 |
| 검증가능성 | 출처 0 → 한 줄도 확인 불가 | 출처 6개 클릭 → 원문 대조 | 믿으라가 아니라 직접 확인 가능 |
| 구체성 | 둥근 추정 (HBM "약 300억$") | 출처 단 정밀 수치 (HBM 546억$ +58%, C사 72.3%) | 의사결정에 쓸 수 있는 정밀도 |
| 전망의 성격 | 임의 확률 시나리오(60/25/15%) | 실제 진행 사안 기반(A사 2nm 수율·HBM4 검증) | 가상이 아니라 추적 가능한 관전 포인트 |
핵심 metric: grounded%(검색 근거 보유율) · report_sources(인용 출처 수) · web_search/run(검색 횟수) · tokens(비용) · latency. 같은 3개 task(N=3)·temp=0 통제 비교. (N=3 = 반도체·EV배터리·AI가속기 서로 다른 주제 3개의 평균 — 한 주제는 우연일 수 있어서. 위 §2 리포트는 그중 반도체 1개만 표시. 용어가 낯설면 페이지 상단 📖 용어 풀이 참고.)
| 단계 | grounded% | 출처수 | 검색/run | 토큰 | 지연s | 판정 |
|---|---|---|---|---|---|---|
| v2-deep (base) | 0 | 0 | 0 | 29.4k | 280 | — |
| v3-web EXP-65 | 100 | 4.7 | 17.3 | 97k | 602 | promoted |
| v4-lite EXP-66 | 100 | 2.3 | 5.7 | 17.8k | 209 | promoted |
| v5 EXP-67 | 67 | 15.7 | 5.0 | 28.6k | 306 | rejected |
| v6-dedup EXP-68 (현재) | 100 | 4.7 | 4.0 | 21.3k | 177 | promoted |
| 메트릭 | 측정 방식 (어디서 나오나) | base v2-deep | 현재 v6-dedup | Δ 절대 | Δ % | 해석 |
|---|---|---|---|---|---|---|
| grounded_pct (%) | events — web_search 호출수 ≥1 (자기보고 ❌) | 0 | 100 | +100pp | — | 도구 유무에서 갈리는 능력 자체 |
| web_search / run | events — 도구 호출 카운트 | 0 | 4.0 | +4.0 | — | 1라운드 dedupe 검색 (과검색 제거 후) |
| report_sources * | 리포트 텍스트 파싱 (## 출처) | 0 | 4.7* | +4.7 | — | *N=3 평균·flaky (아래 EXP-69 주의) |
| total_tokens | 누산기 — input+output 합산 | 29,409 | 21,285 | −8,124 | −28% | 개선이 base보다 오히려 쌈 |
| latency_s (e2e) | first_ts ↔ last_ts (로거 수신 wall-clock) | 280 | 177 | −103 | −37% | 개선이 base보다 빠름 (전 버전 통틀어 최저) |
| crashed | 런타임 예외 카운트 | 0 | 0 | 0 | — | 단 ⓪하니스 수정 전 web팀은 빈 검색에 크래시 |
| stop_reason | 종료 사유 문자열 | APPROVE / MaxMsg | APPROVE | — | — | critic 완화 게이트로 조기 APPROVE 안정 |
| tokens_per_success † | 파생 — SUM(tokens)/SUM(success) | 29,409 | 21,285 | −8,124 | −28% | †success=1.0 가정(무크래시) → 정직한 비용축 |
| grounded% / 1k token † | 파생 — 근거효율 | 0.0 | 4.70 | +4.70 | — | †base는 토큰을 써도 근거 0 → 효율 0 |
| tokens / source † | 파생 — 출처 1개당 비용 | n/a (출처 0) | ≈4,529 | — | — | †v5(전부인용)은 ≈1,800이나 노이즈로 기각 |
grounded_pct는 모델이 "검색했다"고 말한 걸 믿는 게 아니라 events의 web_search 실제 호출수로 독립 재계산한다 —
crosscheck에서 stale 팀 0/3 run이 검색 사용, grounded 팀 3/3 run(총 104 calls)로 검증됐다. (텍스트에 URL이 박혔는지가 아니라 도구를 실제로 때렸는지가 견고한 신호다.)
metric 특이점을 잡아도, 고치는 건 별개다. "어디를 어떻게 바꿨나"의 실제 diff — 1변수 원칙대로 한 번에 한 곳만. 특히 측정 자체를 가능하게 한 비자명한 하니스 버그 2건(아래 ⓪)이 이 라인의 가장 예리한 판단이었다.
teams/analyst_web.py — 빈 검색이 run 전체를 죽이던 문제analyst = AssistantAgent("analyst", model_client, tools=[web_search],
reflect_on_tool_use=False, # ← 핵심 수정
system_message=ANALYST_PROMPT + date_ctx)
# WHY: 검색 다수가 '결과 없음'이면 reflect_on_tool_use=True는
# 빈 응답 → RuntimeError → 배치 전체 크래시. False로 두면 analyst가
# 검색 결과(출처 URL 포함)를 그대로 넘기고 synthesizer가 해석·작성.
tracing/logger.py — critic의 CoT를 '답'으로 오인하던 채점 오염# synthesizer 리포트 전문을 final_answer로 캡처하되, critic은 제외:
if (agent_name != "critic"
and "FINAL ANSWER" in str(record["content"]).upper()):
final_answer_full_str = record["content"] # 마지막 non-critic이 이김
# WHY: critic의 검수 CoT가 "FINAL ANSWER: <answer> 형식인지 확인…"을
# 언급 → critic 메시지를 답으로 오인해 채점 오염. synthesizer(planner 뒤)가
# 올바르게 덮어쓰도록 critic 제외. (grounding을 텍스트 URL이 아니라
# events의 web_search 호출수로 측정해야 견고하다는 교훈도 여기서.)
teams/analyst_web.py vs base(analyst_deep)- analyst = AssistantAgent("analyst", model_client, # 도구 없음
- system_message=ANALYST_PROMPT) # 학습지식으로만
+ analyst = AssistantAgent("analyst", model_client, tools=[web_search], # ← 검색 도구
+ reflect_on_tool_use=False, system_message=ANALYST_PROMPT + date_ctx) # ← 날짜 주입
# 런타임 날짜 주입 (모델엔 시계가 없음 → prompt_bundle_hash엔 미포함: 날짜=실행맥락)
+ date_ctx = f"[현재 시점] 오늘은 {today}이다. '최근/올해'는 이 날짜 기준,
+ 당신의 학습 데이터 시점이 아니다."
# 프롬프트 강화:
ANALYST_PROMPT += "핵심 수치·사건·날짜는 반드시 web_search로 확인. 검색어에 현재 연도."
CRITIC_PROMPT += "출처 URL 5개 이상 + 각 섹션 1개. 미달이면 어느 소주제 재검색할지 지목 반려."
teams/analyst_web_lite.py vs v3ANALYST_PROMPT:
- "한 턴에 최소 5회 web_search. critic이 반려하면 그 소주제를 다시 web_search 보강." # 재검색 루프
+ "소주제당 1회씩 약 5회만 검색하고 끝낸다. 재검색 루프 없이 한 번에 마친다(효율 우선)."
CRITIC_PROMPT:
- "출처 5개 이상 + 각 섹션 1개. 어느 소주제 재검색할지 지목해 반려."
+ "FINAL ANSWER + 출처 3개 이상이면 APPROVE. 그 외엔 한 번만 간단히 반려(루프 안 끔)."
- term = ... | MaxMessageTermination(18) # 재검색 루프 여유
+ term = ... | MaxMessageTermination(10) # 루프 없으니 상한 낮춤
teams/analyst_web_cited.py · SYNTH_PROMPT만 변경SYNTH_PROMPT:
+ "검색 결과 출처 URL은 하나도 빠뜨리지 말고 인라인으로 달고,
+ '## 출처'에 모든 URL을 빠짐없이 나열한다(최소 4개 이상)." # ← 양으로 밀어붙임
research/web_tool.py — 신규 web_search_dedupeddef web_search_deduped(query):
raw = web_search(query)
for block in raw.blocks:
domain = urlsplit(url).netloc.removeprefix("www.")
if domain in seen: continue # ← 도메인당 1개만 (trendforce 반복 방지)
kept.append(block)
if len(kept) >= 4: break # 상위 4개 고유 출처
teams/analyst_web_dedup.py — 도구 교체 + SYNTH 상한analyst: tools=[web_search → web_search_deduped]
SYNTH_PROMPT:
+ "같은 URL·매체는 한 번만 — 중복 제거 후 핵심 5~8개만 '## 출처'에 인용
+ (전부 나열 금지, 대표성 있는 것만)." # ← 질·상한
True면 '검색 결과 없음'에 빈 답을 만들다 에러로 죽음.
CoT = 모델의 '생각 과정' 텍스트 — critic의 검수 생각을 최종답으로 오인한 게 채점 오염의 원인.
완화 게이트 = critic 승인 조건을 느슨하게(출처 5개→3개) 해서 재검색 루프를 줄인 것.
MaxMsg = 대화 최대 메시지 수 상한(무한루프 방지).
양치기 = 프롬프트로 "더 많이 인용해"라고 다그쳐 양으로 미는 방식.
검색 ≠ 인용 = 검색을 해도 리포트에 출처가 꼭 박히는 건 아니다(EXP-69).
| 실험 | 특이점 / 근인 (root cause) | 1변수 레버 (정확히 한 곳) | 측정 변화 (DB 확인) | 부작용 | 판정 | 원리 |
|---|---|---|---|---|---|---|
| ⓪ 하니스 측정 가능화 |
reflect_on_tool_use=True가 빈 검색에서 RuntimeError → 배치 전체 크래시. + critic의 검수 CoT("FINAL ANSWER 형식인지…")를 답으로 오인해 채점 오염. |
reflect_on_tool_use=False · final_answer를 non-critic 마지막 메시지로 한정 · grounding은 텍스트 아닌 events로 |
크래시 0 · 채점 정상화 → 이후 숫자가 신뢰됨 | — | 🔧 필수 | 측정 전에 하니스 신뢰부터 |
| EXP-65 v2→v3 |
grounded 0%·출처 0 — analyst에 도구가 없어 학습 컷오프(2024) 지식만 출력. | analyst +web_search · 런타임 오늘 날짜 주입 | grounded 0→100% · 검색 0→17.3 · 토큰 29,409→97,089(3.3×) · 지연 280→602 | 과검색 + critic 재검색 루프 = 비용 폭증 | promoted | 능력은 도구 한 번에 얻는다 |
| EXP-66 v3→v4 |
토큰 97k·지연 602s — 주범은 과검색 강제 + critic 재검색 루프. | 검색 1라운드 · critic 완화 게이트 · MaxMsg 18→10 | 토큰 97,089→17,805(−82%) · 턴 10.3→5.0 · grounded 100% 유지 | 없음 — stale base(29k)보다도 쌈 | promoted | grounding은 검색 횟수가 아니라 도구 사용에서 |
| EXP-67 v4→v5 |
출처가 줄어든 게(4.7→2.3) 아쉬움 → 양으로 밀어붙이려 함. | synthesizer "검색 URL 전부 나열" 강제 (프롬프트만) | 출처 2.3→15.7(37개 덤프) · 토큰 +61% · grounded 100→67% | 중복·노이즈, grounded 역행 | rejected | 인용은 양이 아니라 질 |
| EXP-68 v4→v6 (현재) |
v5 부작용 — 프롬프트로 양치기는 실패. 도메인 중복이 진짜 노이즈원. | 도구 web_search_deduped(도메인당 1·상위 4 고유) + synthesizer 5~8개 상한 | 출처 2.3→4.7 회복 · 토큰 21,285 · 지연 177(최저) · grounded 100% | 없음 | promoted | 질은 프롬프트가 아니라 도구·상한으로 |
| EXP-69 재측정 |
"출처 4.7"이 진짜 안정적인가 의심 → 개입 없이 N 늘려 재측정. | (개입 없음) 9 run 재측정 | web_search 8/9 사용하나 report_sources ≥1은 3/9뿐 — "4.7은 N=3 행운" | — | inconclusive | 검색 ≠ 인용 — 다음 과제로 분리 |
pass@1 0.967→0.067 붕괴) · EXP-63 disambiguation 프롬프트(0.8→0.27 붕괴).
교훈: 프롬프트 개입은 비단조적이다 — "더 신중히" 지시가 working flow를 깬다. 신뢰할 개선은 surgical한 도구/검색 레벨 수정이다.
team_id + 새 prompt_bundle_hash.
바꾸지 않는 부분(executor·selector·다른 프롬프트)은 베이스라인에서 그대로 import한다. 재현성 가드(AC9): 프롬프트가 한 글자라도 바뀌면 해시가 달라져 새 team_id가 강제되고,
"같은 team_id + 다른 해시" 조합은 즉시 에러로 중단된다 → 변경 없는 재측정과 변경 있는 개입이 DB 레벨에서 구분된다.
(단 오늘 날짜는 실행 맥락이라 prompt_bundle_hash에서 제외 — 날짜가 팀 정체성을 바꾸지는 않으므로.)
src/agent_eval/teams/analyst_*.py · research/web_tool.py.
analyst_web_dedup.py가 v4의 critic·planner를 그대로 import).
v2-deep → v3-web — 웹 검색을 처음 붙이다 promotedtools=[web_search] 추가 + 런타임 날짜 주입 + (검색 강제) 프롬프트. + 하니스: reflect_on_tool_use=False(빈 검색 크래시 방지), critic에 '출처 5개 미달 시 재검색 지목 반려', MaxMessage 12→18(재검색 루프 여유).# ── analyst system_prompt ──
- 당신은 분석가다. planner가 지정한 각 소주제를 구체적 사실·수치·기업/제품명·사례와
함께 깊이 있게 분석해 보고한다. 일반론을 피하고 구체적으로 쓴다.
+ 당신은 분석가다. planner가 지정한 각 소주제를 분석하되, 모든 핵심 수치·사건·날짜는
반드시 web_search 도구로 확인한 뒤 인용한다. 다음을 반드시 지킨다:
+ - 검색을 먼저, 작성은 나중. 분석 문장을 한 줄이라도 쓰기 전에 web_search를 호출한다.
+ - 소주제 5~6개 각각에 최소 1회씩, 즉 한 턴에 최소 5회 web_search 호출(병렬). 1~2번만 하지 마라.
+ - 검색어에 반드시 현재 연도를 넣는다(예: 'HBM4 2026 양산'). 기억 속 연도가 아니라 '오늘' 기준.
+ - 각 사실 뒤에 출처 URL, 수치에 기준 시점. 미확인은 '미확인'.
+ - critic이 반려하면, 지적된 소주제를 다시 web_search 한 뒤 보강한다. ← 재검색 루프(비용원)
# ── critic system_prompt ──
- (1)구체적인가 (2)균형 잡혔나 (3)구조가 명확한가 점검 → 부족하면 한 번 반려, 충분하면 APPROVE.
+ 다음을 모두 만족할 때만 APPROVE: (1)출처 URL 5개 이상·각 섹션 1개 (2)모든 수치에 기준 시점
+ (3)올해 이전 수치를 '현재'처럼 안 씀. 하나라도 불만족이면 어느 소주제를 다시 web_search 할지
+ 구체적으로 지목해 반려한다. ← '5개 미달=재검색' 게이트가 루프를 키움
# ── 런타임 날짜 주입 (프롬프트 정체성 해시에는 미포함 — 실행맥락) ──
+ date_ctx = "[현재 시점] 오늘은 {today}이다. '최근/올해'는 이 날짜 기준, 학습 시점이 아니다."v3-web → v4-lite — 검색 다이어트: 능력은 두고 비용만 깎다 promotedMaxMessage 18→10.# ── analyst ──
- 소주제 각각 최소 1회씩, 한 턴에 최소 5회 web_search 호출. 1~2번만 하지 마라.
- critic이 반려하면 지적된 소주제를 다시 web_search 한 뒤 보강한다. (재검색 루프)
+ 소주제당 1회씩, 한 턴에 약 5회만 검색하고 끝낸다. 재검색 루프 없이 한 번에 마친다(효율 우선).
# ── critic ──
- 출처 5개 이상·각 섹션 1개·기준 시점·stale 금지 모두 만족해야 APPROVE. 아니면 재검색 지목 반려.
+ FINAL ANSWER 형식이고 출처 URL이 3개 이상이면 APPROVE. 그 외엔 한 번만 간단히 반려
+ (재검색 루프를 길게 끌지 않는다).
# ── termination ──
- term = TextMention("APPROVE") | MaxMessageTermination(18) # 재검색 루프 여유
+ term = TextMention("APPROVE") | MaxMessageTermination(10) # 루프 없으니 상한 낮춤v4-lite → v5-cited — '출처 전부 나열' 강제: 역행 rejected# ── synthesizer (이 줄 하나만 추가됨) ──
- (v4) 각 수치에 기준 시점. 맨 끝 '## 출처'에 인용 URL을 나열. 첫 줄 '기준 시점:' 명시.
+ (v5) 각 수치에 기준 시점. 검색 결과에 등장한 출처 URL은 하나도 빠뜨리지 말고 해당 사실
+ 옆에 인라인으로 달고, 맨 끝 '## 출처'에 검색 결과의 모든 URL을 빠짐없이 나열한다
+ (최소 4개 이상). ← 양으로 밀어붙임 = 노이즈·비용v4-lite → v6-dedup — dedup 도구 + 출처 상한: 현재 챔피언 🏆 promotedweb_search_deduped(도메인당 1개·상위 4) ② analyst 도구 교체 ③ synthesizer "중복 제거 후 핵심 5~8개만". critic·planner·날짜함수는 v4에서 그대로 import — 1변수 import 규율의 실증.# ── research/web_tool.py · 신규 도구 ──
def web_search_deduped(query):
"""web_search와 동일하나 도메인 중복을 제거하고 상위 4개 고유 출처만 반환."""
raw = web_search(query)
if raw.startswith("("): return raw # 실패/결과없음 사유는 그대로
for block in raw:
domain = urlsplit(url).netloc.removeprefix("www.")
if domain in seen: continue # ← 도메인당 1개만 (trendforce 반복 방지)
kept.append(block)
if len(kept) >= 4: break # 상위 4개 고유 출처
# ── teams/analyst_web_dedup.py — v4를 그대로 물려받고 딱 2곳만 바꿈 ──
from agent_eval.teams.analyst_web_lite import CRITIC_PROMPT, PLANNER_PROMPT, _today_str # ← 불변부 재사용
analyst: tools=[web_search → web_search_deduped], reflect_on_tool_use=False
# ── synthesizer ──
- (v4) 맨 끝 '## 출처'에 인용 URL을 나열한다.
+ (v6) 같은 URL·매체는 한 번만 — 중복을 제거한 핵심 출처 5~8개만 '## 출처'에 인용한다
+ (전부 나열 금지, 대표성 있는 것만).v6 재측정 (개입 0) — "출처 4.7은 진짜였나?" inconclusiveteam.dump_component() = ComponentModel — provider/config 중첩, model_client(LLM 설정)가 인라인, load_component로 재로드 가능 — 회사에서 보시던 그 포맷)와
우리 관측 스냅샷(config_full — 프롬프트/도구/토폴로지만, 사람이 읽는 용).
LLM 설정(model·temperature·seed)은 우리 시스템에선 team이 아니라 runs 테이블에 run마다 기록 → 그래서 우리 스냅샷엔 없고, 네이티브엔 있다(아래 펼치면 seed:42, temperature:0.2, model:glm-4.7, api_key:***). api_key는 마스킹.
v2-analyst-deep → v6-analyst-web · 기본 ↔ 개선 config 완전 diff (라인 단위)glm-4.7 · seed 42 · temperature 0.2) · planner·synthesizer·critic의 도구 없음.[] = 없음(빈손). 두 팀 통틀어 analyst만 검색 도구를 갖는다(나머지 3명은 양쪽 다 빈손).seed·temperature)으로 쓰는지. 두 팀 완전 동일 = LLM은 안 건드렸다는 뜻(1변수 원칙의 핵심).# ══ 토폴로지 · LLM · 에이전트 구성 — 전부 동일 (1변수 원칙) ══
team_type: RoundRobinGroupChat (동일)
participants: planner → analyst → synthesizer → critic (동일 · 순서까지)
model_client: model=glm-4.7 · seed=42 · temperature=0.2 (동일 · LLM은 손 안 댐)
# ══ planner ════════════════ tools: [] (동일, 도구 없음)
- sha e4b9d0478516
+ sha b835617358a0
+ …각 소주제마다 web_search로 확인할 최신 수치/사건을 구체 지정한다… # ← 검색 지시 추가
+ [현재 시점] 오늘은 2026-06-13이다. '최근/올해'는 이 날짜 기준, 학습 시점 아님. # ← 날짜 주입
# ══ analyst ════════════════ ★★ 유일한 도구 변경 ★★
- tools: [] # base: 도구 없음 → 학습지식으로만 (검색 불가)
+ tools: [web_search_deduped] # 개선: 도메인당 1개·상위 4 고유출처 (도구 레벨 dedupe)
- sha b34ccf53e029
+ sha dad3f8e68e23
- …구체적 사실·수치·기업/제품명과 함께 깊이 분석한다. 일반론을 피한다. (학습지식)
+ 핵심 수치·사건·날짜는 검색 도구로 확인. 소주제당 1회씩 약 5회만(재검색 루프 없이). # ← 1라운드
+ 각 사실 뒤 출처 URL, 수치에 기준 시점. 미확인은 '미확인'. # ← 환각 대신 정직한 공백
reflect_on_tool_use: False # 도구가 생겨 이제 load-bearing — 빈 검색 크래시 방지(⓪)
# ══ synthesizer ════════════ tools: [] (동일)
- sha a8bf71e53245
+ sha df3e90baccd0
+ 같은 URL·매체는 한 번만 — 중복 제거 후 핵심 5~8개만 '## 출처'에 인용(전부 나열 금지). # ← 출처 상한(질)
+ 첫 줄에 '기준 시점:' 명시. # ← 최신성 표기
# ══ critic ═════════════════ tools: [] (동일)
- sha 2e9df711ce26
+ sha 14ec736d8c69
- 구체성·균형·구조를 점검 → 부족하면 한 번 반려, 충분하면 APPROVE.
+ FINAL ANSWER 형식 + 출처 URL ≥3개면 APPROVE. 그 외 한 번만 간단히 반려(재검색 루프 안 끔). # ← 완화 게이트
config_full + AutoGen 네이티브 dump_component())은 아래 버전별 카드에서 펼쳐 볼 수 있다.dump_component() 직렬화 후 api_key 스크럽 · 박힌 날짜 동적화:# 등록된 팀을 AutoGen Studio 임포트용 JSON 파일로 추출
$ python cli.py export-team --team v6-analyst-web --out v6-analyst-web.json
export 완료: v6-analyst-web.json
비밀값 스크럽: 4곳 (REPLACE_WITH_YOUR_KEY) # api_key 제거
날짜 동적화: 4개 프롬프트 (박힌 날짜 → web_search 기준 시점) # 날짜는 실행맥락
도구 주입: 1개 에이전트 # 클로저 도구를 self-contained로
.json 파일로 바로 저장:team.dump_component()(provider/config 중첩, model_client 인라인 — 회사에서 보던 포맷, 그대로 재로드 가능). 스냅샷 = 사람이 읽는 config_full(프롬프트/도구/토폴로지만). api_key는 마스킹됨.{ "provider": "autogen_agentchat.teams.RoundRobinGroupChat", "component_type": "team", "version": 1, "component_version": 1, "description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.", "label": "RoundRobinGroupChat", "config": { "participants": [ { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "planner", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Decomposes the analysis and specifies what to search.", "system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } }, { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "analyst", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [ # ← 도구 목록 { "provider": "autogen_core.tools.FunctionTool", "component_type": "tool", "version": 1, "component_version": 1, "description": "Create custom tools by wrapping standard Python functions.", "label": "FunctionTool", "config": { "source_code": "def web_search_deduped(query: str) -> str:\n \"\"\"web_search와 동일하나 **도메인 중복을 제거**하고 상위 4개 고유 출처만 반환한다.\n\n 여러 소주제 검색이 같은 매체(trendforce 등)를 반복 인용해 출처가 부풀고 노이즈가\n 되는 문제를 줄인다(도구 레벨 dedupe). 도메인당 1개만 남긴다.\n \"\"\"\n import re\n import urllib.parse\n\n raw = web_search(query)\n if raw.startswith(\"(\"): # 실패/결과없음 사유 문자열은 그대로\n return raw\n\n # web_search 출력은 \"[n] 제목\\n 요약\\n URL\" 블록의 연속.\n blocks = re.split(r\"\\n(?=\\[\\d+\\])\", raw)\n seen: set[str] = set()\n kept: list[str] = []\n for b in blocks:\n urls = re.findall(r\"https?://\\S+\", b)\n if not urls:\n continue\n domain = urllib.parse.urlsplit(urls[-1]).netloc.lower().removeprefix(\"www.\")\n if domain in seen:\n continue\n seen.add(domain)\n kept.append(b.strip())\n if len(kept) >= 4:\n break\n return \"\\n\".join(f\"[{i + 1}] \" + re.sub(r\"^\\[\\d+\\]\\s*\", \"\", b)\n for i, b in enumerate(kept)) or \"(검색 결과 없음)\"\n", # ← 도구 실제 코드(도메인 dedupe) "name": "web_search_deduped", # ← ★ 핵심: 검색 도구 추가 "description": "web_search와 동일하나 **도메인 중복을 제거**하고 상위 4개 고유 출처만 반환한다.\n\n 여러 소주제 검색이 같은 매체(trendforce 등)를 반복 인용해 출처가 부풀고 노이즈가\n 되는 문제를 줄인다(도구 레벨 dedupe). 도메인당 1개만 남긴다.\n ", "global_imports": [], "has_cancellation_support": false } } ], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Retrieves current data via domain-deduped search (single round).", "system_message": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 검색 도구로 확인한다. 소주제당 1회씩 한 턴에 약 5회만 검색하고 끝낸다(재검색 루프 없이). 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준). 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } }, { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "synthesizer", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Writes a report citing 5-8 deduped representative sources.", "system_message": "당신은 리포트 작성자다. analyst가 검색으로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적는다. **같은 URL·매체는 한 번만 — 중복을 제거한 핵심 출처 5~8개만** '## 출처'에 인용한다(전부 나열 금지, 대표성 있는 것만). 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } }, { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "critic", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Lenient gate: APPROVE if FINAL ANSWER + >=3 source URLs.", "system_message": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } } ], "termination_condition": { "provider": "autogen_agentchat.base.OrTerminationCondition", "component_type": "termination", "version": 1, "component_version": 1, "label": "OrTerminationCondition", "config": { "conditions": [ { "provider": "autogen_agentchat.conditions.TextMentionTermination", "component_type": "termination", "version": 1, "component_version": 1, "description": "Terminate the conversation if a specific text is mentioned.", "label": "TextMentionTermination", "config": { "text": "APPROVE" } }, { "provider": "autogen_agentchat.conditions.MaxMessageTermination", "component_type": "termination", "version": 1, "component_version": 1, "description": "Terminate the conversation after a maximum number of messages have been exchanged.", "label": "MaxMessageTermination", "config": { "max_messages": 10, "include_agent_event": false } } ] } }, "emit_team_events": true }}{ "provider": "autogen_agentchat.teams.RoundRobinGroupChat", "component_type": "team", "version": 1, "component_version": 1, "description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.", "label": "RoundRobinGroupChat", "config": { "participants": [ { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "planner", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Decomposes the analysis into subtopics.", "system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 직접 분석하지 않는다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } }, { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "analyst", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], # ← 도구 목록 "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Analyzes each subtopic with concrete facts.", "system_message": "당신은 분석가다. planner가 지정한 각 소주제를 구체적 사실·수치·기업/제품명·사례와 함께 깊이 있게 분석해 보고한다. 일반론을 피하고 구체적으로 쓴다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } }, { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "synthesizer", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Writes a structured FINAL ANSWER report.", "system_message": "당신은 리포트 작성자다. analyst의 분석을 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성하며 각 섹션에 구체 근거를 담는다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } }, { "provider": "autogen_agentchat.agents.AssistantAgent", "component_type": "agent", "version": 2, "component_version": 2, "description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.", "label": "AssistantAgent", "config": { "name": "critic", "model_client": { "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", "component_type": "model", "version": 1, "component_version": 1, "description": "Chat completion client for OpenAI hosted models.", "label": "OpenAIChatCompletionClient", "config": { "seed": 42, "temperature": 0.2, "model": "glm-4.7", "api_key": "***", "model_info": { "vision": false, "function_calling": true, "json_output": true, "family": "unknown", "structured_output": true, "multiple_system_messages": true }, "base_url": "https://api.z.ai/api/coding/paas/v4" } }, "workbench": [ { "provider": "autogen_core.tools.StaticStreamWorkbench", "component_type": "workbench", "version": 1, "component_version": 1, "description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.", "label": "StaticStreamWorkbench", "config": { "tools": [], "tool_overrides": {} } } ], "model_context": { "provider": "autogen_core.model_context.UnboundedChatCompletionContext", "component_type": "chat_completion_context", "version": 1, "component_version": 1, "description": "An unbounded chat completion context that keeps a view of the all the messages.", "label": "UnboundedChatCompletionContext", "config": {} }, "description": "Checks specificity/balance/structure.", "system_message": "당신은 검수자다. 리포트가 (1)구체적인가(수치·사례) (2)균형 잡혔나(기회+리스크) (3)구조가 명확한가를 점검한다. 부족하면 무엇을 보강할지 한 번만 반려하고, 충분하면 APPROVE 만 출력한다.", # ← 프롬프트 개선(검색지시·날짜·출처상한·완화게이트) "model_client_stream": false, "reflect_on_tool_use": false, "tool_call_summary_format": "{result}", "max_tool_iterations": 1, "metadata": {} } } ], "termination_condition": { "provider": "autogen_agentchat.base.OrTerminationCondition", "component_type": "termination", "version": 1, "component_version": 1, "label": "OrTerminationCondition", "config": { "conditions": [ { "provider": "autogen_agentchat.conditions.TextMentionTermination", "component_type": "termination", "version": 1, "component_version": 1, "description": "Terminate the conversation if a specific text is mentioned.", "label": "TextMentionTermination", "config": { "text": "APPROVE" } }, { "provider": "autogen_agentchat.conditions.MaxMessageTermination", "component_type": "termination", "version": 1, "component_version": 1, "description": "Terminate the conversation after a maximum number of messages have been exchanged.", "label": "MaxMessageTermination", "config": { "max_messages": 12, "include_agent_event": false } } ] } }, "emit_team_events": true }}dump_component() 결과, api_key 마스킹). 강조 줄 외 나머지가 전부 동일하다는 게 "1변수 원칙"의 시각적 증거.| 버전 | 도구(tools) | 이전 버전 대비 변경점 |
|---|---|---|
| v2-analyst-deep base · 웹 없음 | — | base |
| v3-analyst-web EXP-65 · 웹검색+날짜 | web_search | 📝 planner 프롬프트 변경 · 📅 planner 날짜맥락 주입 · 🔧 analyst 도구 [없음] → [web_search] · 📝 analyst 프롬프트 변경 · 📅 analyst 날짜맥락 주입 · 📝 synthesizer 프롬프트 변경 · 📅 synthesizer 날짜맥락 주입 · 📝 critic 프롬프트 변경 · 📅 critic 날짜맥락 주입 |
| v4-analyst-web EXP-66 · 검색 다이어트 | web_search | 📝 planner 프롬프트 변경 · 📝 analyst 프롬프트 변경 · 📝 synthesizer 프롬프트 변경 · 📝 critic 프롬프트 변경 |
| v5-analyst-web EXP-67 · 전부 인용 (rejected) | web_search | 📝 synthesizer 프롬프트 변경 |
| v6-analyst-web EXP-68 · dedup+상한 (현재) | web_search_deduped | 🔧 analyst 도구 [web_search] → [web_search_deduped] · 📝 analyst 프롬프트 변경 · 📝 synthesizer 프롬프트 변경 |
v2-analyst-deep{
"agents": [
{
"name": "planner",
"tools": [
],
"system_prompt": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 직접 분석하지 않는다.",
"system_prompt_sha": "e4b9d0478516"
},
{
"name": "analyst",
"tools": [
],
"system_prompt": "당신은 분석가다. planner가 지정한 각 소주제를 구체적 사실·수치·기업/제품명·사례와 함께 깊이 있게 분석해 보고한다. 일반론을 피하고 구체적으로 쓴다.",
"system_prompt_sha": "b34ccf53e029"
},
{
"name": "synthesizer",
"tools": [
],
"system_prompt": "당신은 리포트 작성자다. analyst의 분석을 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성하며 각 섹션에 구체 근거를 담는다.",
"system_prompt_sha": "a8bf71e53245"
},
{
"name": "critic",
"tools": [
],
"system_prompt": "당신은 검수자다. 리포트가 (1)구체적인가(수치·사례) (2)균형 잡혔나(기회+리스크) (3)구조가 명확한가를 점검한다. 부족하면 무엇을 보강할지 한 번만 반려하고, 충분하면 APPROVE 만 출력한다.",
"system_prompt_sha": "2e9df711ce26"
}
],
"team_type": "RoundRobinGroupChat",
"termination": "TextMentionTermination OR MaxMessageTermination",
"selector_prompt": null,
"selector_prompt_sha": null,
"max_selector_attempts": null,
"allow_repeated_speaker": null
}{
"provider": "autogen_agentchat.teams.RoundRobinGroupChat",
"component_type": "team",
"version": 1,
"component_version": 1,
"description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.",
"label": "RoundRobinGroupChat",
"config": {
"participants": [
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "planner",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Decomposes the analysis into subtopics.",
"system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 직접 분석하지 않는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "analyst",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Analyzes each subtopic with concrete facts.",
"system_message": "당신은 분석가다. planner가 지정한 각 소주제를 구체적 사실·수치·기업/제품명·사례와 함께 깊이 있게 분석해 보고한다. 일반론을 피하고 구체적으로 쓴다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "synthesizer",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Writes a structured FINAL ANSWER report.",
"system_message": "당신은 리포트 작성자다. analyst의 분석을 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성하며 각 섹션에 구체 근거를 담는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "critic",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Checks specificity/balance/structure.",
"system_message": "당신은 검수자다. 리포트가 (1)구체적인가(수치·사례) (2)균형 잡혔나(기회+리스크) (3)구조가 명확한가를 점검한다. 부족하면 무엇을 보강할지 한 번만 반려하고, 충분하면 APPROVE 만 출력한다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
}
],
"termination_condition": {
"provider": "autogen_agentchat.base.OrTerminationCondition",
"component_type": "termination",
"version": 1,
"component_version": 1,
"label": "OrTerminationCondition",
"config": {
"conditions": [
{
"provider": "autogen_agentchat.conditions.TextMentionTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation if a specific text is mentioned.",
"label": "TextMentionTermination",
"config": {
"text": "APPROVE"
}
},
{
"provider": "autogen_agentchat.conditions.MaxMessageTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation after a maximum number of messages have been exchanged.",
"label": "MaxMessageTermination",
"config": {
"max_messages": 12,
"include_agent_event": false
}
}
]
}
},
"emit_team_events": true
}
}v3-analyst-web{
"agents": [
{
"name": "planner",
"tools": [
],
"system_prompt": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인해야 할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "ab82c1c7bf2f"
},
{
"name": "analyst",
"tools": [
{
"name": "web_search",
"impl_sha": "4b14fa025c6a",
"description": "웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\n 요약\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n "
}
],
"system_prompt": "당신은 분석가다. planner가 지정한 각 소주제를 분석하되, 모든 핵심 수치·사건·날짜는 반드시 web_search 도구로 확인한 뒤 인용한다. 다음을 **반드시** 지킨다:\n- **검색을 먼저, 작성은 나중.** 분석 문장을 한 줄이라도 쓰기 전에 web_search를 호출한다. 검색 없이 작성하면 critic이 반려한다.\n- **소주제 5~6개 각각에 대해 최소 1회씩, 즉 한 턴에 최소 5회 web_search를 호출**한다 (여러 검색을 한 번에 병렬 호출). 1~2번만 검색하고 끝내지 마라.\n- **검색어에 반드시 현재 연도를 넣는다**(예: '반도체 시장 규모 2026', 'HBM4 2026 양산'). 당신의 기억 속 연도가 아니라 시스템이 알려준 '오늘' 기준이 진짜 현재다.\n- 검색 결과가 과거 연도 자료뿐이면 그 수치는 '과거 시점'이라고 분명히 표시하고, 가장 최근 자료를 우선한다. 기억에 의존해 옛 수치를 현재처럼 쓰지 마라.\n- 각 사실 뒤에 출처 URL을 붙이고, 수치에는 기준 시점을 명시한다. 미확인은 '미확인'.\n- critic이 부족하다고 반려하면, 지적된 소주제에 대해 **다시 web_search**한 뒤 보강한다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "0516af223b3d"
},
{
"name": "synthesizer",
"tools": [
],
"system_prompt": "당신은 리포트 작성자다. analyst의 (검색으로 확인된) 분석을 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적고, 맨 끝에 '## 출처' 섹션으로 인용한 URL을 나열한다. 리포트 첫 줄에 '기준 시점:'을 명시한다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "8e83d93d649a"
},
{
"name": "critic",
"tools": [
],
"system_prompt": "당신은 검수자다. 다음을 **모두** 만족할 때만 APPROVE 를 단독 출력한다:\n (1) 출처 URL이 **5개 이상**이고, 각 섹션마다 최소 1개 출처가 있다.\n (2) 모든 핵심 수치에 기준 시점이 명시돼 있다.\n (3) 올해(현재 연도) 이전 수치를 '현재'처럼 쓰지 않았다.\n하나라도 불만족이면 절대 통과시키지 말고, **어느 소주제를 다시 web_search 해야 하는지 구체적으로 지목해** 반려한다. analyst가 검색을 건너뛰었으면 반드시 반려한다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "aa6777b78299"
}
],
"team_type": "RoundRobinGroupChat",
"termination": "TextMentionTermination OR MaxMessageTermination",
"selector_prompt": null,
"selector_prompt_sha": null,
"max_selector_attempts": null,
"allow_repeated_speaker": null
}{
"provider": "autogen_agentchat.teams.RoundRobinGroupChat",
"component_type": "team",
"version": 1,
"component_version": 1,
"description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.",
"label": "RoundRobinGroupChat",
"config": {
"participants": [
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "planner",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Decomposes the analysis and specifies what to web-search.",
"system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인해야 할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "analyst",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [
{
"provider": "autogen_core.tools.FunctionTool",
"component_type": "tool",
"version": 1,
"component_version": 1,
"description": "Create custom tools by wrapping standard Python functions.",
"label": "FunctionTool",
"config": {
"source_code": "def web_search(query: str) -> str:\n \"\"\"웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\\n 요약\\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n \"\"\"\n import gzip\n import html\n import re\n import urllib.parse\n import urllib.request\n\n top_k = 6\n url = \"https://html.duckduckgo.com/html/?q=\" + urllib.parse.quote(query)\n req = urllib.request.Request(\n url,\n headers={\n \"User-Agent\": (\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \"\n \"(KHTML, like Gecko) Chrome/124.0 Safari/537.36\"\n ),\n \"Accept-Encoding\": \"gzip\",\n },\n )\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n raw = resp.read()\n if resp.headers.get(\"Content-Encoding\") == \"gzip\":\n raw = gzip.decompress(raw)\n body = raw.decode(\"utf-8\", \"replace\")\n except Exception as exc: # noqa: BLE001 - 네트워크 실패는 사유를 그대로 알린다\n return f\"(검색 실패: {type(exc).__name__}: {exc})\"\n\n # DDG HTML: 결과 블록마다 result__a(제목+링크) + result__snippet(요약)\n link_re = re.compile(\n r'result__a\"[^>]*href=\"(?P<href>[^\"]+)\"[^>]*>(?P<title>.*?)</a>', re.S\n )\n snip_re = re.compile(r'result__snippet\"[^>]*>(?P<snip>.*?)</a>', re.S)\n\n def _clean(s: str) -> str:\n return html.unescape(re.sub(r\"<[^>]+>\", \"\", s)).strip()\n\n def _real_url(href: str) -> str:\n # //duckduckgo.com/l/?uddg=<encoded>&rut=... 형태를 실제 URL로 복원\n m = re.search(r\"[?&]uddg=([^&]+)\", href)\n if m:\n return urllib.parse.unquote(m.group(1))\n return href if href.startswith(\"http\") else \"https:\" + href\n\n titles = link_re.findall(body)\n snips = snip_re.findall(body)\n if not titles:\n return \"(검색 결과 없음)\"\n\n lines: list[str] = []\n for i, (href, title) in enumerate(titles[:top_k]):\n snippet = _clean(snips[i]) if i < len(snips) else \"\"\n lines.append(f\"[{i + 1}] {_clean(title)}\\n {snippet}\\n {_real_url(href)}\")\n return \"\\n\".join(lines)\n",
"name": "web_search",
"description": "웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\n 요약\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n ",
"global_imports": [],
"has_cancellation_support": false
}
}
],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Retrieves current data via web_search; passes results (with source URLs) on.",
"system_message": "당신은 분석가다. planner가 지정한 각 소주제를 분석하되, 모든 핵심 수치·사건·날짜는 반드시 web_search 도구로 확인한 뒤 인용한다. 다음을 **반드시** 지킨다:\n- **검색을 먼저, 작성은 나중.** 분석 문장을 한 줄이라도 쓰기 전에 web_search를 호출한다. 검색 없이 작성하면 critic이 반려한다.\n- **소주제 5~6개 각각에 대해 최소 1회씩, 즉 한 턴에 최소 5회 web_search를 호출**한다 (여러 검색을 한 번에 병렬 호출). 1~2번만 검색하고 끝내지 마라.\n- **검색어에 반드시 현재 연도를 넣는다**(예: '반도체 시장 규모 2026', 'HBM4 2026 양산'). 당신의 기억 속 연도가 아니라 시스템이 알려준 '오늘' 기준이 진짜 현재다.\n- 검색 결과가 과거 연도 자료뿐이면 그 수치는 '과거 시점'이라고 분명히 표시하고, 가장 최근 자료를 우선한다. 기억에 의존해 옛 수치를 현재처럼 쓰지 마라.\n- 각 사실 뒤에 출처 URL을 붙이고, 수치에는 기준 시점을 명시한다. 미확인은 '미확인'.\n- critic이 부족하다고 반려하면, 지적된 소주제에 대해 **다시 web_search**한 뒤 보강한다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "synthesizer",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Writes a structured FINAL ANSWER report with as-of dates and sources.",
"system_message": "당신은 리포트 작성자다. analyst의 (검색으로 확인된) 분석을 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적고, 맨 끝에 '## 출처' 섹션으로 인용한 URL을 나열한다. 리포트 첫 줄에 '기준 시점:'을 명시한다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "critic",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Checks freshness, source URLs, and structure.",
"system_message": "당신은 검수자다. 다음을 **모두** 만족할 때만 APPROVE 를 단독 출력한다:\n (1) 출처 URL이 **5개 이상**이고, 각 섹션마다 최소 1개 출처가 있다.\n (2) 모든 핵심 수치에 기준 시점이 명시돼 있다.\n (3) 올해(현재 연도) 이전 수치를 '현재'처럼 쓰지 않았다.\n하나라도 불만족이면 절대 통과시키지 말고, **어느 소주제를 다시 web_search 해야 하는지 구체적으로 지목해** 반려한다. analyst가 검색을 건너뛰었으면 반드시 반려한다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
}
],
"termination_condition": {
"provider": "autogen_agentchat.base.OrTerminationCondition",
"component_type": "termination",
"version": 1,
"component_version": 1,
"label": "OrTerminationCondition",
"config": {
"conditions": [
{
"provider": "autogen_agentchat.conditions.TextMentionTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation if a specific text is mentioned.",
"label": "TextMentionTermination",
"config": {
"text": "APPROVE"
}
},
{
"provider": "autogen_agentchat.conditions.MaxMessageTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation after a maximum number of messages have been exchanged.",
"label": "MaxMessageTermination",
"config": {
"max_messages": 18,
"include_agent_event": false
}
}
]
}
},
"emit_team_events": true
}
}v4-analyst-web{
"agents": [
{
"name": "planner",
"tools": [
],
"system_prompt": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "b835617358a0"
},
{
"name": "analyst",
"tools": [
{
"name": "web_search",
"impl_sha": "4b14fa025c6a",
"description": "웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\n 요약\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n "
}
],
"system_prompt": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 web_search로 확인한다. 다음을 지킨다:\n- **소주제당 1회씩, 한 턴에 약 5회만 검색하고 끝낸다.** 재검색 루프 없이 한 번에 마친다(효율 우선).\n- 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준).\n- 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "c0ad2787fcc2"
},
{
"name": "synthesizer",
"tools": [
],
"system_prompt": "당신은 리포트 작성자다. analyst가 web_search로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적고, 맨 끝 '## 출처' 섹션에 인용 URL을 나열한다. 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "15dd48a55101"
},
{
"name": "critic",
"tools": [
],
"system_prompt": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "14ec736d8c69"
}
],
"team_type": "RoundRobinGroupChat",
"termination": "TextMentionTermination OR MaxMessageTermination",
"selector_prompt": null,
"selector_prompt_sha": null,
"max_selector_attempts": null,
"allow_repeated_speaker": null
}{
"provider": "autogen_agentchat.teams.RoundRobinGroupChat",
"component_type": "team",
"version": 1,
"component_version": 1,
"description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.",
"label": "RoundRobinGroupChat",
"config": {
"participants": [
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "planner",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Decomposes the analysis and specifies what to web-search.",
"system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "analyst",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [
{
"provider": "autogen_core.tools.FunctionTool",
"component_type": "tool",
"version": 1,
"component_version": 1,
"description": "Create custom tools by wrapping standard Python functions.",
"label": "FunctionTool",
"config": {
"source_code": "def web_search(query: str) -> str:\n \"\"\"웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\\n 요약\\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n \"\"\"\n import gzip\n import html\n import re\n import urllib.parse\n import urllib.request\n\n top_k = 6\n url = \"https://html.duckduckgo.com/html/?q=\" + urllib.parse.quote(query)\n req = urllib.request.Request(\n url,\n headers={\n \"User-Agent\": (\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \"\n \"(KHTML, like Gecko) Chrome/124.0 Safari/537.36\"\n ),\n \"Accept-Encoding\": \"gzip\",\n },\n )\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n raw = resp.read()\n if resp.headers.get(\"Content-Encoding\") == \"gzip\":\n raw = gzip.decompress(raw)\n body = raw.decode(\"utf-8\", \"replace\")\n except Exception as exc: # noqa: BLE001 - 네트워크 실패는 사유를 그대로 알린다\n return f\"(검색 실패: {type(exc).__name__}: {exc})\"\n\n # DDG HTML: 결과 블록마다 result__a(제목+링크) + result__snippet(요약)\n link_re = re.compile(\n r'result__a\"[^>]*href=\"(?P<href>[^\"]+)\"[^>]*>(?P<title>.*?)</a>', re.S\n )\n snip_re = re.compile(r'result__snippet\"[^>]*>(?P<snip>.*?)</a>', re.S)\n\n def _clean(s: str) -> str:\n return html.unescape(re.sub(r\"<[^>]+>\", \"\", s)).strip()\n\n def _real_url(href: str) -> str:\n # //duckduckgo.com/l/?uddg=<encoded>&rut=... 형태를 실제 URL로 복원\n m = re.search(r\"[?&]uddg=([^&]+)\", href)\n if m:\n return urllib.parse.unquote(m.group(1))\n return href if href.startswith(\"http\") else \"https:\" + href\n\n titles = link_re.findall(body)\n snips = snip_re.findall(body)\n if not titles:\n return \"(검색 결과 없음)\"\n\n lines: list[str] = []\n for i, (href, title) in enumerate(titles[:top_k]):\n snippet = _clean(snips[i]) if i < len(snips) else \"\"\n lines.append(f\"[{i + 1}] {_clean(title)}\\n {snippet}\\n {_real_url(href)}\")\n return \"\\n\".join(lines)\n",
"name": "web_search",
"description": "웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\n 요약\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n ",
"global_imports": [],
"has_cancellation_support": false
}
}
],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Retrieves current data via web_search (single round); passes results on.",
"system_message": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 web_search로 확인한다. 다음을 지킨다:\n- **소주제당 1회씩, 한 턴에 약 5회만 검색하고 끝낸다.** 재검색 루프 없이 한 번에 마친다(효율 우선).\n- 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준).\n- 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "synthesizer",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Writes a structured FINAL ANSWER report with as-of dates and sources.",
"system_message": "당신은 리포트 작성자다. analyst가 web_search로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적고, 맨 끝 '## 출처' 섹션에 인용 URL을 나열한다. 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "critic",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Lenient gate: APPROVE if FINAL ANSWER + >=3 source URLs.",
"system_message": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
}
],
"termination_condition": {
"provider": "autogen_agentchat.base.OrTerminationCondition",
"component_type": "termination",
"version": 1,
"component_version": 1,
"label": "OrTerminationCondition",
"config": {
"conditions": [
{
"provider": "autogen_agentchat.conditions.TextMentionTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation if a specific text is mentioned.",
"label": "TextMentionTermination",
"config": {
"text": "APPROVE"
}
},
{
"provider": "autogen_agentchat.conditions.MaxMessageTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation after a maximum number of messages have been exchanged.",
"label": "MaxMessageTermination",
"config": {
"max_messages": 10,
"include_agent_event": false
}
}
]
}
},
"emit_team_events": true
}
}v5-analyst-web{
"agents": [
{
"name": "planner",
"tools": [
],
"system_prompt": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "b835617358a0"
},
{
"name": "analyst",
"tools": [
{
"name": "web_search",
"impl_sha": "4b14fa025c6a",
"description": "웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\n 요약\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n "
}
],
"system_prompt": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 web_search로 확인한다. 다음을 지킨다:\n- **소주제당 1회씩, 한 턴에 약 5회만 검색하고 끝낸다.** 재검색 루프 없이 한 번에 마친다(효율 우선).\n- 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준).\n- 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "c0ad2787fcc2"
},
{
"name": "synthesizer",
"tools": [
],
"system_prompt": "당신은 리포트 작성자다. analyst가 web_search로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적는다. **검색 결과에 등장한 출처 URL은 하나도 빠뜨리지 말고** 해당 사실 옆에 인라인으로 달고, 맨 끝 '## 출처' 섹션에 검색 결과의 모든 URL을 빠짐없이 나열한다(최소 4개 이상). 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "781a3b9f0432"
},
{
"name": "critic",
"tools": [
],
"system_prompt": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "14ec736d8c69"
}
],
"team_type": "RoundRobinGroupChat",
"termination": "TextMentionTermination OR MaxMessageTermination",
"selector_prompt": null,
"selector_prompt_sha": null,
"max_selector_attempts": null,
"allow_repeated_speaker": null
}{
"provider": "autogen_agentchat.teams.RoundRobinGroupChat",
"component_type": "team",
"version": 1,
"component_version": 1,
"description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.",
"label": "RoundRobinGroupChat",
"config": {
"participants": [
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "planner",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Decomposes the analysis and specifies what to web-search.",
"system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "analyst",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [
{
"provider": "autogen_core.tools.FunctionTool",
"component_type": "tool",
"version": 1,
"component_version": 1,
"description": "Create custom tools by wrapping standard Python functions.",
"label": "FunctionTool",
"config": {
"source_code": "def web_search(query: str) -> str:\n \"\"\"웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\\n 요약\\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n \"\"\"\n import gzip\n import html\n import re\n import urllib.parse\n import urllib.request\n\n top_k = 6\n url = \"https://html.duckduckgo.com/html/?q=\" + urllib.parse.quote(query)\n req = urllib.request.Request(\n url,\n headers={\n \"User-Agent\": (\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \"\n \"(KHTML, like Gecko) Chrome/124.0 Safari/537.36\"\n ),\n \"Accept-Encoding\": \"gzip\",\n },\n )\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n raw = resp.read()\n if resp.headers.get(\"Content-Encoding\") == \"gzip\":\n raw = gzip.decompress(raw)\n body = raw.decode(\"utf-8\", \"replace\")\n except Exception as exc: # noqa: BLE001 - 네트워크 실패는 사유를 그대로 알린다\n return f\"(검색 실패: {type(exc).__name__}: {exc})\"\n\n # DDG HTML: 결과 블록마다 result__a(제목+링크) + result__snippet(요약)\n link_re = re.compile(\n r'result__a\"[^>]*href=\"(?P<href>[^\"]+)\"[^>]*>(?P<title>.*?)</a>', re.S\n )\n snip_re = re.compile(r'result__snippet\"[^>]*>(?P<snip>.*?)</a>', re.S)\n\n def _clean(s: str) -> str:\n return html.unescape(re.sub(r\"<[^>]+>\", \"\", s)).strip()\n\n def _real_url(href: str) -> str:\n # //duckduckgo.com/l/?uddg=<encoded>&rut=... 형태를 실제 URL로 복원\n m = re.search(r\"[?&]uddg=([^&]+)\", href)\n if m:\n return urllib.parse.unquote(m.group(1))\n return href if href.startswith(\"http\") else \"https:\" + href\n\n titles = link_re.findall(body)\n snips = snip_re.findall(body)\n if not titles:\n return \"(검색 결과 없음)\"\n\n lines: list[str] = []\n for i, (href, title) in enumerate(titles[:top_k]):\n snippet = _clean(snips[i]) if i < len(snips) else \"\"\n lines.append(f\"[{i + 1}] {_clean(title)}\\n {snippet}\\n {_real_url(href)}\")\n return \"\\n\".join(lines)\n",
"name": "web_search",
"description": "웹을 검색해 현재 시점의 상위 결과(제목·요약·URL)를 텍스트로 반환한다.\n\n 분석에 필요한 최신 수치·사건·날짜를 확인할 때 사용한다. 반환된 각 결과의\n URL은 출처로 인용한다.\n\n Args:\n query: 검색어(한국어/영어 모두 가능). 구체적 키워드가 좋다.\n\n Returns:\n \"[n] 제목\n 요약\n URL\" 형식의 상위 결과 묶음. 실패 시 사유 문자열.\n ",
"global_imports": [],
"has_cancellation_support": false
}
}
],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Retrieves current data via web_search (single round); passes results on.",
"system_message": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 web_search로 확인한다. 다음을 지킨다:\n- **소주제당 1회씩, 한 턴에 약 5회만 검색하고 끝낸다.** 재검색 루프 없이 한 번에 마친다(효율 우선).\n- 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준).\n- 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "synthesizer",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Writes a structured report citing EVERY source URL from search results.",
"system_message": "당신은 리포트 작성자다. analyst가 web_search로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적는다. **검색 결과에 등장한 출처 URL은 하나도 빠뜨리지 말고** 해당 사실 옆에 인라인으로 달고, 맨 끝 '## 출처' 섹션에 검색 결과의 모든 URL을 빠짐없이 나열한다(최소 4개 이상). 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "critic",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Lenient gate: APPROVE if FINAL ANSWER + >=3 source URLs.",
"system_message": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
}
],
"termination_condition": {
"provider": "autogen_agentchat.base.OrTerminationCondition",
"component_type": "termination",
"version": 1,
"component_version": 1,
"label": "OrTerminationCondition",
"config": {
"conditions": [
{
"provider": "autogen_agentchat.conditions.TextMentionTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation if a specific text is mentioned.",
"label": "TextMentionTermination",
"config": {
"text": "APPROVE"
}
},
{
"provider": "autogen_agentchat.conditions.MaxMessageTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation after a maximum number of messages have been exchanged.",
"label": "MaxMessageTermination",
"config": {
"max_messages": 10,
"include_agent_event": false
}
}
]
}
},
"emit_team_events": true
}
}v6-analyst-web{
"agents": [
{
"name": "planner",
"tools": [
],
"system_prompt": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "b835617358a0"
},
{
"name": "analyst",
"tools": [
{
"name": "web_search_deduped",
"impl_sha": "5703a1f111c9",
"description": "web_search와 동일하나 **도메인 중복을 제거**하고 상위 4개 고유 출처만 반환한다.\n\n 여러 소주제 검색이 같은 매체(trendforce 등)를 반복 인용해 출처가 부풀고 노이즈가\n 되는 문제를 줄인다(도구 레벨 dedupe). 도메인당 1개만 남긴다.\n "
}
],
"system_prompt": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 검색 도구로 확인한다. 소주제당 1회씩 한 턴에 약 5회만 검색하고 끝낸다(재검색 루프 없이). 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준). 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "dad3f8e68e23"
},
{
"name": "synthesizer",
"tools": [
],
"system_prompt": "당신은 리포트 작성자다. analyst가 검색으로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적는다. **같은 URL·매체는 한 번만 — 중복을 제거한 핵심 출처 5~8개만** '## 출처'에 인용한다(전부 나열 금지, 대표성 있는 것만). 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "df3e90baccd0"
},
{
"name": "critic",
"tools": [
],
"system_prompt": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-13이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"system_prompt_sha": "14ec736d8c69"
}
],
"team_type": "RoundRobinGroupChat",
"termination": "TextMentionTermination OR MaxMessageTermination",
"selector_prompt": null,
"selector_prompt_sha": null,
"max_selector_attempts": null,
"allow_repeated_speaker": null
}{
"provider": "autogen_agentchat.teams.RoundRobinGroupChat",
"component_type": "team",
"version": 1,
"component_version": 1,
"description": "A team that runs a group chat with participants taking turns in a round-robin fashion\n to publish a message to all.",
"label": "RoundRobinGroupChat",
"config": {
"participants": [
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "planner",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Decomposes the analysis and specifies what to search.",
"system_message": "당신은 리서치 리드다. 분석 요청을 5~6개 핵심 소주제(예: 시장규모·수요동인, 기술/공정 트렌드, 경쟁구도, 공급망·지정학, 리스크, 전망)로 분해해 analyst에게 무엇을 다룰지 지시한다. 각 소주제마다 web_search로 확인할 최신 수치/사건을 구체적으로 지정한다. 직접 분석하지 않는다.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "analyst",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [
{
"provider": "autogen_core.tools.FunctionTool",
"component_type": "tool",
"version": 1,
"component_version": 1,
"description": "Create custom tools by wrapping standard Python functions.",
"label": "FunctionTool",
"config": {
"source_code": "def web_search_deduped(query: str) -> str:\n \"\"\"web_search와 동일하나 **도메인 중복을 제거**하고 상위 4개 고유 출처만 반환한다.\n\n 여러 소주제 검색이 같은 매체(trendforce 등)를 반복 인용해 출처가 부풀고 노이즈가\n 되는 문제를 줄인다(도구 레벨 dedupe). 도메인당 1개만 남긴다.\n \"\"\"\n import re\n import urllib.parse\n\n raw = web_search(query)\n if raw.startswith(\"(\"): # 실패/결과없음 사유 문자열은 그대로\n return raw\n\n # web_search 출력은 \"[n] 제목\\n 요약\\n URL\" 블록의 연속.\n blocks = re.split(r\"\\n(?=\\[\\d+\\])\", raw)\n seen: set[str] = set()\n kept: list[str] = []\n for b in blocks:\n urls = re.findall(r\"https?://\\S+\", b)\n if not urls:\n continue\n domain = urllib.parse.urlsplit(urls[-1]).netloc.lower().removeprefix(\"www.\")\n if domain in seen:\n continue\n seen.add(domain)\n kept.append(b.strip())\n if len(kept) >= 4:\n break\n return \"\\n\".join(f\"[{i + 1}] \" + re.sub(r\"^\\[\\d+\\]\\s*\", \"\", b)\n for i, b in enumerate(kept)) or \"(검색 결과 없음)\"\n",
"name": "web_search_deduped",
"description": "web_search와 동일하나 **도메인 중복을 제거**하고 상위 4개 고유 출처만 반환한다.\n\n 여러 소주제 검색이 같은 매체(trendforce 등)를 반복 인용해 출처가 부풀고 노이즈가\n 되는 문제를 줄인다(도구 레벨 dedupe). 도메인당 1개만 남긴다.\n ",
"global_imports": [],
"has_cancellation_support": false
}
}
],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Retrieves current data via domain-deduped search (single round).",
"system_message": "당신은 분석가다. planner가 지정한 소주제를 분석하되, 핵심 수치·사건·날짜는 검색 도구로 확인한다. 소주제당 1회씩 한 턴에 약 5회만 검색하고 끝낸다(재검색 루프 없이). 검색어에 현재 연도를 넣는다(시스템이 알려준 '오늘' 기준). 각 사실 뒤에 출처 URL을 붙이고 수치에 기준 시점을 명시한다. 미확인은 '미확인'.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "synthesizer",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Writes a report citing 5-8 deduped representative sources.",
"system_message": "당신은 리포트 작성자다. analyst가 검색으로 수집한 결과를 바탕으로 구조화한 최종 리포트를 작성한다. 'FINAL ANSWER:' 로 시작하고, 마크다운 섹션(## 시장/수요, ## 기술·공정, ## 경쟁·공급망, ## 리스크, ## 전망)으로 구성한다. 각 수치에 기준 시점을 적는다. **같은 URL·매체는 한 번만 — 중복을 제거한 핵심 출처 5~8개만** '## 출처'에 인용한다(전부 나열 금지, 대표성 있는 것만). 첫 줄에 '기준 시점:' 명시.\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
},
{
"provider": "autogen_agentchat.agents.AssistantAgent",
"component_type": "agent",
"version": 2,
"component_version": 2,
"description": "An agent that provides assistance with tool use.\n The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`\n in which :attr:`~autogen_agentchat.base.Response.chat_message` is the final\n response message.",
"label": "AssistantAgent",
"config": {
"name": "critic",
"model_client": {
"provider": "autogen_ext.models.openai.OpenAIChatCompletionClient",
"component_type": "model",
"version": 1,
"component_version": 1,
"description": "Chat completion client for OpenAI hosted models.",
"label": "OpenAIChatCompletionClient",
"config": {
"seed": 42,
"temperature": 0.2,
"model": "glm-4.7",
"api_key": "***",
"model_info": {
"vision": false,
"function_calling": true,
"json_output": true,
"family": "unknown",
"structured_output": true,
"multiple_system_messages": true
},
"base_url": "https://api.z.ai/api/coding/paas/v4"
}
},
"workbench": [
{
"provider": "autogen_core.tools.StaticStreamWorkbench",
"component_type": "workbench",
"version": 1,
"component_version": 1,
"description": "A workbench that provides a static set of tools that do not change after\n each tool execution, and supports streaming results.",
"label": "StaticStreamWorkbench",
"config": {
"tools": [],
"tool_overrides": {}
}
}
],
"model_context": {
"provider": "autogen_core.model_context.UnboundedChatCompletionContext",
"component_type": "chat_completion_context",
"version": 1,
"component_version": 1,
"description": "An unbounded chat completion context that keeps a view of the all the messages.",
"label": "UnboundedChatCompletionContext",
"config": {}
},
"description": "Lenient gate: APPROVE if FINAL ANSWER + >=3 source URLs.",
"system_message": "당신은 검수자다. FINAL ANSWER 형식이고 출처 URL이 **3개 이상**이면 APPROVE 만 출력한다. 그 외엔 무엇이 부족한지 한 번만 간단히 반려한다(재검색 루프를 길게 끌지 않는다).\n\n[현재 시점] 오늘은 2026-06-14이다. '최근/현재/올해'는 모두 이 날짜 기준이며, 당신의 학습 데이터 시점이 아니다. 검색·분석은 이 시점을 현재로 삼는다.",
"model_client_stream": false,
"reflect_on_tool_use": false,
"tool_call_summary_format": "{result}",
"max_tool_iterations": 1,
"metadata": {}
}
}
],
"termination_condition": {
"provider": "autogen_agentchat.base.OrTerminationCondition",
"component_type": "termination",
"version": 1,
"component_version": 1,
"label": "OrTerminationCondition",
"config": {
"conditions": [
{
"provider": "autogen_agentchat.conditions.TextMentionTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation if a specific text is mentioned.",
"label": "TextMentionTermination",
"config": {
"text": "APPROVE"
}
},
{
"provider": "autogen_agentchat.conditions.MaxMessageTermination",
"component_type": "termination",
"version": 1,
"component_version": 1,
"description": "Terminate the conversation after a maximum number of messages have been exchanged.",
"label": "MaxMessageTermination",
"config": {
"max_messages": 10,
"include_agent_event": false
}
}
]
}
},
"emit_team_events": true
}
}반도체 한 주제만으론 "운"일 수 있다. 같은 base↔개선 팀을 성격이 다른 5개 주제에 돌려 개선의 경계를 본 결과(총 10 run). 웹 근거의 가치는 만능이 아니라 '최신 사실' 주제에 한정됨이 드러난다.
| 주제 (자극하는 차원) | base 검색/출처 | 개선 검색/출처 | 경계 판정 |
|---|---|---|---|
| 주요 LLM 경쟁 (2026) post-cutoff 신제품 | 0 / 0 | 17 / 6 | ✅ 명확한 우위 — 본문+출처 모두 성공 |
| 엔비디아 실적 (2026) 시점 민감 실적 | 0 / 0 | 5 / 0 | 🟡 본문 grounded·인용 누락(flaky) |
| 미 대중국 규제 (2026) 정책·규제 시점성 | 0 / 0 | 6 / 0 | 🟡 본문 grounded·인용 누락 |
| 한국 반도체 업황 (2026) 한국어·로컬 출처 | 0 / 0 | 6 / 0 | 🛡️ 안전성 우위 — 정직한 거부 |
| 동적 계획법 개념 timeless 개념 (대조군) | 0 / 0 | 5 / 0 | ⚖️ 무승부 — 검색이 무용 |
eval_ph1 코드(어제 버전)는 이미 사내에 전송됨. 아래는 그 코드로 같은 루프를 도는 절차다.
핵심은 도구가 아니라 규율 — "한 번에 1변수 → 측정 → 판정 → 원장 기록 → promoted면 새 베이스라인" 루프를 반복하면 base가 챔피언이 된다.
LLM 키: .env에 EVAL_OPENAI_API_KEY·EVAL_OPENAI_BASE_URL(사내/z.ai). 검색 백엔드: EVAL_FIRECRAWL_API_KEY가 있으면 실검색(/v1/search), 없으면 DuckDuckGo 폴백 — 폐쇄망은 Firecrawl 엔드포인트만 사내값으로(EVAL_FIRECRAWL_BASE_URL). DB: Postgres(원장·점수·materialized view).
키는 코드에 두지 않고 환경에서만 읽는다(runner가 settings→os.environ 브릿지). web_tool은 표준 라이브러리(urllib)만 써서 AutoGen Studio 직렬화에도 안전.
$ python cli.py init-db # 스키마(runs·experiments·mv_batch_summary)
$ python cli.py load-research # 또는 사내 도메인 평가셋 YAML 작성
평가셋은 대표 작업 + 기대값(expect_contains·must_not_contain·expected_tools·grounding_ref)을 사람이 정의. 도메인을 바꾸려면 이 YAML만 새로 쓴다.
$ python cli.py run --batch base_0616 --team v2-analyst-deep --usecase research --k 3
$ python cli.py report --batch base_0616
--k 3 = 작업당 3회 반복(pass^k 신뢰성). seed·temperature 고정으로 통제. 단발(N=1) dramatic 수치는 믿지 않는다.
report에서 metric 이상치를 본다. 우리 경우엔 grounded%=0 · 출처=0(events에 web_search 0건) → "리포트에 출처가 한 개도 없다"가 특이점이었다. "무엇이 비정상인가"가 곧 다음에 바꿀 한 곳이다.
새 팀 모듈 1개를 만들되 바꿀 곳만 손대고 나머지는 베이스라인에서 import한다(우리 analyst_web_dedup.py가 v4의 critic·planner를 그대로 import한 것처럼). 프롬프트가 한 글자라도 바뀌면 prompt_bundle_hash가 달라져 새 team_id가 강제된다(AC9). 그리고 원장에 INSERT:
INSERT INTO experiments (title, hypothesis, intervention, team_before, batch_before)
VALUES ('EXP: 웹 grounding 추가', '도구 없으면 stale → web_search로 grounded 0→100%',
'analyst에 web_search 도구 + 날짜 주입 (딱 하나)', 'v2-analyst-deep', 'base_0616');
$ python cli.py run --batch exp65_0616 --team v3-analyst-web --usecase research --k 3
$ python cli.py compare --batches base_0616,exp65_0616 # improved/regressed (작업별 짝지어)
$ python cli.py grounding-score --batch exp65_0616 # research면 인용 F1까지
raw 팀 평균은 비교에 쓰지 않는다 — 두 팀이 다른 난이도 작업을 풀면 차이가 개입 때문인지 난이도 때문인지 못 가린다. 같은 작업끼리 짝지은 paired만 신뢰.
UPDATE experiments SET team_after='v3-analyst-web', batch_after='exp65_0616',
verdict='promoted', # promoted | rejected | inconclusive
key_evidence='{"grounded":[0,1.0],"tokens":[29409,97089],"improved":3}'::jsonb,
lesson='능력은 도구 한 번에. 단 과검색이 비용원 → 다음 실험' WHERE exp_id=N;
verdict=promoted 행의 team_after가 새 베이스라인이 된다. Grafana는 concluded_at이 채워지는 순간 annotation 마커를 자동으로 찍는다.
EXP-65의 특이점(토큰 97k) → EXP-66(검색 다이어트) → 출처 감소 → EXP-67(실패) → EXP-68(dedup) … 루프. "제약 추가는 비용·부작용, 제약 제거/도구 레벨 수정이 dominant" 패턴을 활용하면 수렴이 빠르다.
$ python cli.py export-team --team v6-analyst-web --out v6.json # Studio 임포트용(키 스크럽)
Grafana 패널(experiment-metrics·analysis-grounding·team-config)로 원장·메트릭·config를 한 화면에. 이 비교 페이지처럼 정적 대시보드로도 공유 가능.
config_full(우리 스냅샷)은 사람이 읽는 관측용이라 LLM 설정이 없고 로드 불가다. 실제로 돌리는 파일은 AutoGen 네이티브 dump_component() JSON(model_client·도구 source_code·reflect 플래그까지 포함, load_component()/Studio 임포트 가능). → §8 다운로드의 v6-analyst-web.json이 그것.api_key(마스킹됨)를 본인 키로 채우고 · 검색 백엔드 도달 가능하게(Firecrawl 키 or DDG; 폐쇄망이면 web_search 본문을 사내 검색/RAG로 교체) · AutoGen Studio 버전이 컴포넌트 스키마와 호환.reflect_on_tool_use 등) + 모델설정. "바꿀 거리"의 대부분이 여기 있는 건 맞지만, 그 안에서 프롬프트는 한 조각일 뿐이다.pass@1 0.8→0.27 붕괴. 지시 강화가 working flow를 깬다.| 병목 (무엇이 문제인가) | 가장 높은 레버 | 이 프로젝트 증거 |
|---|---|---|
| 필요한 정보/능력이 없다 | 도구 · 검색 · grounding | web_search → grounded 0→100% (최대 효과) |
| 정보는 있는데 잘못 쓴다 | 프롬프트 · 역할 설계 | 여기선 대개 기각(비단조적) |
| 협업이 깨진다 | 토폴로지 · 종료조건 · 라우팅 | selector→round_robin 한 줄로 0.967→0.067 붕괴(load-bearing) |
| 비용이 문제다 | 제약 제거 · dedup · 상한 | 과검색 해제 −82% · 도구 dedupe |
무엇을 보나: 각 실험(batch)이 돌 때 DB가 자동으로 채운 모든 메트릭을 실험 순서대로 나열했다.
메트릭은 세 곳에서 온다 — runs(실행 1건의 결과/비용), research_grounding(인용 채점, 멀티홉 QA용),
v_analysis_grounded(개방형 분석의 검색 근거 뷰). 수치는 모두 실제 DB 쿼리 결과(2026-06 기준 배치).
| batch / team | EXP·판정 | n | pass@1 (success) | format _valid |
total _turns | total _tokens | latency (s) | cite _f1 | n _cited |
cite_p | cite_r | sources _present |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| research_base v1-research-pipeline | 기준 | 30 | 0.967 | 0.967 | 6.9 | 6,528 | 233.5 | 0.967 | 1.9 | 0.967 | 0.967 | 0.97 |
| research_grounded v2-research-grounded | EXP-57 rejected | 30 | 0.967 | 1.000 | 6.9 | 5,864 | 313.6 | 0.967 | 1.9 | 0.967 | 0.967 | 0.97 |
| research_fast v2-research-fast | EXP-60 rejected | 30 | 0.067 | 0.200 | 4.6 | 7,962 | 193.7 | 0.083 | 0.2 | 0.100 | 0.078 | 0.10 |
| research_hard_base v1-research-pipeline | hard 기준 | 30 | 0.800 | 0.800 | 7.5 | 7,464 | 406.8 | 0.800 | 1.7 | 0.800 | 0.800 | 0.80 |
| research_hard_disambig v2-research-disambig | EXP-63 rejected | 30 | 0.267 | 0.667 | 6.1 | 14,567 | 502.9 | 0.300 | 0.9 | 0.300 | 0.300 | 0.43 |
| research_hard_cleanret v2-research-cleanret | EXP-64 promoted | 30 | 0.867 | 0.900 | 7.0 | 6,761 | 251.9 | 0.867 | 1.8 | 0.867 | 0.867 | 0.87 |
읽는 법: cite_f1=인용한 출처(doc_id)가 정답 출처와 맞는 정도(P·R 조화평균). EXP-64만 도구 레벨 개입(검색이 비활성 decoy 강등) — pass@1·cite_f1·지연·토큰이 동시에 개선된 유일한 dominant 케이스. EXP-63은 같은 목표를 프롬프트로 시도했다가 0.80→0.27 붕괴.
| batch / team | EXP·판정 | n | success (=format) | total _turns | total _tokens | latency (s) |
grounded% 호출기준 | grounded% 성공기준 |
search /run | search_ok /run | report _sources |
|---|---|---|---|---|---|---|---|---|---|---|---|
| analysis_stale v2-analyst-deep | base | 3 | 1.00 | 5.0 | 29,409 | 279.7 | 0 | 0 | 0 | 0 | 0 |
| analysis_grounded v3-analyst-web | EXP-65 promoted | 3 | 1.00 | 10.3 | 97,089 | 601.6 | 100 | 0 | 17.3 | 0 | 4.7 |
| analysis_lite v4-analyst-web | EXP-66 promoted | 3 | 1.00 | 5.0 | 17,805 | 209.4 | 100 | 0 | 5.7 | 0 | 2.3 |
| analysis_cited v5-analyst-web | EXP-67 rejected | 3 | 1.00 | 6.3 | 28,626 | 306.3 | 67 | 0 | 5.0 | 0 | 15.7 |
| analysis_dedup v6-analyst-web | EXP-68 promoted | 3 | 1.00 | 5.0 | 21,285 | 177.1 | 100 | 100 | 4.0 | 4.0 | 4.7 |
| analysis_v6_k3 v6-analyst-web | EXP-69 inconclusive | 9 | 0.889 | 5.0 | 19,333 | 195.8 | — | 33 | 5.0 | 1.89 | cited 3/9 |
web_search를 1회라도 호출했나 → v3·v4·v6 모두 100%.
② 성공 기준(v_analysis_grounded, 마이그레이션 010): 검색이 실제 결과를 반환했나 → v3·v4·v5는 0%, v6-dedup만 100%.
차이의 원인: 키 없는 DuckDuckGo 폴백이 rate-limit/차단으로 호출은 되지만 빈손('(검색 결과 없음)')을 반환한 run이 많았다.
즉 "검색을 했다"가 "근거를 얻었다"가 아니다 — 호출만 세면 환각 리포트가 grounded=100%로 둔갑한다.
그래서 010 뷰가 성공(실결과) 기준을 새로 정의했고, 운영 결론은 검색 백엔드를 Firecrawl(키 기반 실검색)로 교체다(§14 web_tool.py가 이미 런타임 분기 지원). 이 페이지의 §1·§5 헤드라인(grounded 0→100%)은 호출 기준임을 명시한다.
pass@1·토큰·지연 외에 형식오류율·무한루프율·성공당토큰·p95지연·pass@k/pass^k·도구에러도 모두 집계된다(materialized view, 배치 종료 시 REFRESH). 아래는 같은 12개 배치의 실측.
| batch | EXP·판정 | format _invalid | loop _rate | tokens /success | p95 지연(s) |
pass@k | pass^k | k | 도구 호출 (에러) |
|---|---|---|---|---|---|---|---|---|---|
| research_base | 기준 | 0.033 | 0.000 | 6,753 | 389.6 | 0.967 | 0.967 | 1 | search_corpus 60 (0) |
| research_grounded | 57 rej | 0.000 | 0.000 | 6,067 | 611.8 | 0.967 | 0.967 | 1 | search_corpus 60 (0) |
| research_fast | 60 rej | 0.800 | 0.100 | 119,429 | 435.0 | 0.067 | 0.067 | 1 | search_corpus 36 (0) |
| research_hard_base | hard 기준 | 0.200 | 0.100 | 9,330 | 632.1 | 0.800 | 0.800 | 1 | search_corpus 74 (0) |
| research_hard_disambig | 63 rej | 0.333 | 0.100 | 54,628 | 864.1 | 0.267 | 0.267 | 1 | search_corpus 61 (0) |
| research_hard_cleanret | 64 prom | 0.100 | 0.000 | 7,802 | 381.1 | 0.867 | 0.867 | 1 | search_corpus 62 (0) |
| analysis_stale | base | 0.000 | 0.000 | 29,409 | 296.4 | 1.000 | 1.000 | 1 | (도구 없음) |
| analysis_grounded | 65 prom | 0.000 | 0.000 | 97,089 | 861.0 | 1.000 | 1.000 | 1 | web_search 52 (0) |
| analysis_lite | 66 prom | 0.000 | 0.000 | 17,805 | 246.9 | 1.000 | 1.000 | 1 | web_search 17 (0) |
| analysis_cited | 67 rej | 0.000 | 0.000 | 28,626 | 392.9 | 1.000 | 1.000 | 1 | web_search 15 (0) |
| analysis_dedup | 68 prom | 0.000 | 0.000 | 21,285 | 237.3 | 1.000 | 1.000 | 1 | web_search_deduped 12 (0) |
| analysis_v6_k3 | 69 inc | 0.111 | 0.000 | 21,749 | 249.6 | 1.000 | 0.667 | 3 | web_search_deduped 45 (0) |
읽는 법: pass@k=task당 k회 중 1회 이상 성공 비율, pass^k=k회 전부 성공 비율. k=1 배치는 둘이 같다. analysis_v6_k3만 k=3 — pass@1 0.889인데 pass^k 0.667(3 task 중 1개가 3회 중 일부만 성공)=비결정성이 드러난 자리(§12 EXP-69). 도구 호출수는 배치 전체 합(table B의 /run × n과 일치: 52/3≈17.3 등).
tool_error가 아니라 tool_result에 '(검색 결과 없음)' 문자열로 저장된다. 그래서 err_rate 0%인데 grounding 성공기준은 0%일 수 있다(§11 위 caveat·010 뷰의 존재 이유).
| 메트릭 | 방향 | 주의 임계 | 이 라더에서 걸린 곳 |
|---|---|---|---|
| pass@1 정답률 | ↑ | 급락·슬라이스 격차 | EXP-60 0.97→0.07 · EXP-63 0.80→0.27 |
| format_invalid_rate 형식오류 | ↓ | ≥0.2 | EXP-60 0.80 · EXP-63 0.33 · hard_base 0.20 |
| loop_rate 무한루프 | ↓ | ≥0.3(레벨별) | EXP-60·63·hard_base의 hop2 = 0.30 |
| tokens_per_success | ↓ | ≥40k(루프 동반) | EXP-60 119k · EXP-63 55k · EXP-65 97k |
| mv_tool_summary.errors | ↓ | 에러율 100% 도구 | 이 라더 0 (빈손은 에러 아님 → 위 ④) |
| cite_f1 [research] 인용 | ↑ | hop격차·급락 | EXP-60 0.97→0.08 · EXP-63 0.80→0.30 |
| grounded_pct [analysis] 웹근거 | ↑ | 0%(능력 공백) | base 0% · 성공기준선 v3/v4도 0% |
| pass^k 반복 신뢰성 | ↑ | pass@1보다 크게 낮음 | EXP-69 pass@1 0.89 → pass^k 0.67 |
| 메트릭 | 저장 위치 (table.column) | 채우는 코드 | 의미 |
|---|---|---|---|
| success | runs.success | scorer.score() → runner UPDATE | 정답(GAIA/research) 또는 형식유효(analysis) |
| format_valid | runs.format_valid | scorer.score() | 'FINAL ANSWER:' 패턴 존재 |
| total_turns | runs.total_turns | logger: agent_message 카운트 | 에이전트 발화 수 |
| total/input/output_tokens | runs.total_tokens 외 | logger: models_usage 합산 | LLM 비용 ≈ 토큰 |
| latency_ms | runs.latency_ms | logger: 첫↔끝 event ts 차 | run 1회 소요시간 |
| terminated_by | runs.terminated_by | logger stop_reason → runner | approve / max_messages / error |
| (이벤트 전체) | events.* (seq별 1행) | TraceLogger.log_stream() | 메시지·도구호출·selector 결정 원장 |
| cite_f1 / precision / recall | research_grounding.cite_* | research/grounding.score_batch() | 인용 출처 vs 정답 출처 일치도 |
| n_cited / n_gold / sources_present | research_grounding.* | research/scorer.extract_sources() | 인용 개수·정답 개수·인용 유무 |
| grounded (성공기준) | v_analysis_grounded (뷰) | events 쿼리 (저장 아님) | 실결과 반환 검색 ≥1 |
| web_search/run, search_ok | events 집계 / 뷰 | events tool_name LIKE '%search%' | 검색 시도·성공 횟수 |
| pass@1, format_invalid_rate, loop_rate, avg_turns, avg_tokens, tokens_per_success, p95_latency | mv_batch_summary | 002_views.sql (배치 종료 REFRESH) | 배치×팀×레벨(hop) 집계 — 슬라이스별 격차가 여기서 보인다 |
| pass@k (pass_any) / pass^k (pass_all) | mv_passk | 002_views.sql | task당 k회 중 1회↑ / k회 전부 성공 = 반복 신뢰성 |
| calls / errors / avg_latency (도구별) | mv_tool_summary | 002_views.sql | 도구별 호출·에러·지연. 빈손 검색은 에러 아님(tool_result) |
이 절의 목적: 각 EXP에서 왜 그 개입을 골랐고(method), 무엇이 근거였으며(evidence), 사람이 이 판단을 자동화 없이 직접 하려면 어떤 절차를 밟고, 어떤 오픈소스/툴을 어떻게 쓰는지를 단계마다 적었다. (도구의 근거등급: ★공식 = 해당 프레임워크 1급 기능 / ◆de-facto = 업계 널리 쓰는 OSS / ▷내추론 = 이 프로젝트의 적용 제안.)
| 선정한 방법 | planner엔 'SOURCES 줄 항상 출력' 강제, critic엔 'SOURCES 없으면 반려' 게이트 추가. |
| 근거 | 대시보드가 인용 F1 ~58%·출처누락 13건을 가리켰다 → "근거가 부실하다"는 가설. |
| 실제 결과 | 메트릭이 버그였다. 채점기가 critic의 추론 텍스트 속 'SOURCES'까지 긁어 planner의 진짜 인용을 가렸다. planner FINAL ANSWER로 한정하니 베이스라인이 이미 F1 97%. 개입 효과 0 + 정답 1건 회귀. |
| 사람이 직접 하려면 | ① 메트릭 정의를 코드로 다시 읽어 "무엇을 세고 있나" 확인 ② 의심 run 5건을 손으로 라벨링(정답 인용 vs 채점값 대조) ③ 메트릭을 좇기 전 메트릭을 검증. |
| OSS·툴 | RAGAS(faithfulness·context-recall ◆de-facto) / TruLens(groundedness ◆) — 인용 채점을 표준화. 단 채점기 자체의 단위 테스트(▷내추론: golden set에 known F1을 박아 회귀 검출)가 먼저다. |
| 선정한 방법 | SelectorGroupChat → RoundRobinGroupChat. selector의 매 턴 LLM 라우팅 호출을 제거해 지연·토큰 절감 가설. |
| 근거 | selector는 발화자 선택에 LLM을 한 번 더 부른다 = 오버헤드처럼 보임. |
| 실제 결과 | pass@1 0.967→0.067 붕괴(회귀 27). 고정 순환이 멀티홉 흐름을 깨 critic이 답 없이 조기 APPROVE·환각. selector 라우팅은 낭비가 아니라 load-bearing이었다. |
| 사람이 직접 하려면 | 효율 최적화엔 반드시 정확도 가드를 동반. "20% 빨라짐"만 보지 말고 paired 정확도부터 확인. 토폴로지 변경은 기능 변경으로 취급. |
| OSS·툴 | AutoGen GroupChat 토폴로지(Selector/RoundRobin/Swarm ★공식) / LangGraph(명시적 상태그래프로 흐름을 못 깨게 ◆) — 라우팅을 데이터로 관측하려면 selector_decision 이벤트(이 프로젝트 events 테이블)나 트레이싱. |
같은 약점(이름충돌 decoy로 hard셋 80%), 두 가지 처방. hard corpus는 60문서 중 30개가 decoy(비활성 동명 엔티티).
| 63 — 방법 | planner+researcher 프롬프트에 "정확한 전체 이름·활성 엔티티만" disambiguation 지시 추가 → 0.80→0.27 붕괴(researcher는 정답 찾는데 planner가 빈답/템플릿텍스트). |
| 64 — 방법 | 프롬프트 불변, 검색 도구만 demote_inactive=True로 비활성 decoy를 순위 강등 → 0.80→0.87, hop2 0.60→0.90, 게다가 지연 −38%·토큰 −9%. |
| 근거/교훈 | 같은 의도라도 프롬프트 강화는 비단조적(working flow를 깸), 도구/검색 레벨 surgical 수정은 신뢰롭다. "에이전트에게 더 시키기" vs "에이전트가 나쁜 입력을 안 보게 하기". |
| 사람이 직접 하려면 | 실패 run의 도구 입출력 로그를 본다 → researcher는 맞는데 planner가 망치면 병목은 '지시'가 아니라 '검색이 decoy를 보여줌'. 처방을 가장 낮은 레이어(데이터/검색)에 둔다. |
| OSS·툴 | 검색 랭킹 제어: Elasticsearch/OpenSearch function_score·필터(◆), BM25/rerank(cross-encoder, sentence-transformers ◆), 메타데이터 필터(상태=active). 평가 회귀: promptfoo(프롬프트 A/B ◆)로 63 같은 역효과를 사전 검출. |
| 선정한 방법 | analyst에 web_search 도구 + "오늘 날짜" 런타임 주입. 구조 동일, 1변수=도구/맥락. |
| 근거 | 도구 없는 팀은 학습컷오프(2024) 데이터를 현재처럼 출력(stale). grounded% 0. |
| 실제 결과 | grounded(호출기준) 0→100%(검색 17.3/run). 단 검색강제+critic 재검색 루프로 토큰 3.3×·지연 2.15× = GAIA형(능력↑ 대신 비용↑) 트레이드오프. 다음 특이점=토큰 97k. |
| 사람이 직접 하려면 | grounding은 events(도구 호출수)로 잰다 — 모델 자기보고 금지. 날짜는 프롬프트에 박지 말고 런타임 주입(직렬화돼도 안 얼어붙게). |
| OSS·툴 | 검색 API: Tavily·Firecrawl·Brave Search API·SerpAPI(◆), 자체호스팅 SearxNG(◆ 폐쇄망 친화). 프레임워크 도구화: AutoGen FunctionTool(★공식) / LangChain Tool(◆). 폐쇄망이면 사내 검색/RAG로 교체(▷). |
| 선정한 방법 | 검색 1라운드로 축소(재검색 루프 제거) + critic 완화 게이트(출처 3개면 통과). |
| 근거 | 토큰 3.3×의 주범은 '검색 강제(턴당 5+)+critic 재검색 루프'였다. |
| 실제 결과 | 토큰 −82%·지연 −65%·턴 −51%, grounding 100% 유지. stale 팀보다도 싸짐(29.4k→17.8k) = dominant 회복. 단 출처 4.7→2.3 미세 하락. |
| 사람이 직접 하려면 | "효과는 도구 사용에서 나오지 횟수에서 안 나온다" — 과검색은 순비용. 게이트 임계(출처 N개)를 낮춰 루프 길이를 통제. |
| OSS·툴 | 비용 관측: Langfuse·Helicone·Arize Phoenix(토큰/턴/지연 트레이싱 ◆). 루프 상한은 프레임워크 termination(MaxMessage ★공식)으로 강제. |
| 선정한 방법 | synthesizer 프롬프트만 '검색 결과 URL 전부 나열' 강화(인용 회복 목표). |
| 근거 | v4의 출처수 2.3 하락이 synthesizer 누락 탓이라는 가설. |
| 실제 결과 | 출처 2.3→15.7로 과다(한 task 37개 덤프=중복·노이즈). 토큰 +61%·지연 +46% 역행(v4가 줄인 바로 그 메트릭). 순개선 아님. |
| 사람이 직접 하려면 | 인용은 '양(전부)'이 아니라 '질(dedupe+상한)'. 프롬프트 '추가'(전부 인용)는 비단조적 부작용 — 양치기 금지. |
| OSS·툴 | 출처 dedupe·정규화: URL canonicalization, 도메인 클러스터링(▷). 인용 품질 평가는 RAGAS context-precision(◆)로 "관련 없는 인용" 패널티. |
| 선정한 방법 | analyst가 web_search_deduped(도메인 중복 제거) 사용 + synthesizer '핵심 5~8개만' 인용. v5의 교훈 적용. |
| 근거 | "양이 아니라 질" — 도구 레벨 dedupe + 출력 상한. |
| 실제 결과 | 출처 2.3→4.7 회복(v3 수준), 과다 회피, grounding 100%, 지연 −15%(177s 최저). v5와 같은 목표를 더 싸고 안정적으로. |
| 사람이 직접 하려면 | 도구/질 레버가 프롬프트 양치기보다 신뢰롭다(EXP-64 패턴 재확인). 후처리 dedupe를 도구 안에 넣어 결정성 확보. |
| OSS·툴 | 도메인 dedupe(urllib.parse.netloc ★표준), reranking으로 대표성 선별(◆), 출처 상한은 synthesizer 프롬프트+후처리 이중 가드(▷). |
| 선정한 방법 | 개입 0. v6를 3 task × 3회 반복(N=9)으로 분산 측정. |
| 근거 | v6의 '출처 4.7'이 N=3 행운인지 체계적인지 확인. |
| 실제 결과 | grounding(검색)은 안정(8/9 검색 사용)이나 리포트 인용은 매우 불안정(synthesizer가 URL을 리포트에 옮긴 건 3/9뿐, attempt별 0,0,6). '출처 4.7'은 행운, 실측은 '대개 0, 가끔 분출'. |
| 사람이 직접 하려면 | 단발 dramatic 금지 — N≥3 반복 + 분산(SD). 비결정적 메트릭은 평균만 보지 말고 run별 분포를 본다. |
| OSS·툴 | paired 유의성: scipy.stats(McNemar/Wilcoxon ◆), 부트스트랩 CI(◆). 반복 실험 관리: MLflow·W&B·DVC experiments(◆). 비결정성 추적: 같은 seed·temp 고정 후 SD 리포트(▷). |
설계 철학(SPEC): 스키마가 SSOT(ORM 금지, migrations/*.sql로만 변경). runs=실행 1회의 조건·결과, events=run 내 모든 이벤트(seq 순), experiments=개선 원장. 불변식은 애플리케이션이 아니라 CHECK 제약으로 강제한다.
runs.team_id→teams · runs.task_id→tasks · events.run_id→runs(ON DELETE CASCADE) · research_grounding.run_id→runs · experiments.team_before/after→teams · runs.failure_stage→failure_stages · events.message_type→message_types.
| 테이블/뷰 | 역할 | 핵심 컬럼 | 연계·불변식 |
|---|---|---|---|
| teams | 팀 정체(재현성 1급) | team_id(PK), agents(JSONB), topology, prompt_bundle_hash, config_full(JSONB) | topology∈{selector,round_robin,swarm}. 프롬프트 1글자 변경→해시 변경→새 team_id (AC9) |
| tasks | 평가 문제·정답 | task_id(PK), benchmark, level, question, gold_answer, annotator_meta(JSONB: gold_sources) | benchmark로 scorer 분기. annotator_meta가 grounding 정답 출처 보유 |
| runs | 실행 1회 결과/조건 | run_id(PK), team_id, task_id, batch_id, model_name, temperature, seed, success, format_valid, total_tokens, latency_ms, terminated_by | UNIQUE(batch,task,attempt). completed→success NOT NULL. temp/seed 기록 강제(비교 가능성) |
| events | run 내 전 이벤트 원장 | (run_id,seq) PK, message_type, agent_name, content, tool_name, tool_args/result(JSONB), selector_reason, tokens, ts | tool_call→tool_call_id+tool_name 必. tool_result/tool_error 동시 금지. content 8KB·tool_result 2KB 절단+hash |
| research_grounding | 인용 채점(answer와 독립 축) | run_id(PK), cited/gold_sources(배열), cite_precision/recall/f1, sources_present | grounding.score_batch()가 upsert. 일반 뷰라 REFRESH 불필요 |
| experiments | 개선 원장(ledger) | exp_id(PK), hypothesis, intervention, team_before/after, batch_before/after, verdict, key_evidence(JSONB), lesson | verdict∈{promoted,rejected,inconclusive}. promoted의 team_after=새 베이스라인. Grafana annotation 소스 |
| message_types / failure_stages / root_causes | lookup | code(PK) | events/runs FK 대상. failure_stage/root_cause는 스키마만(자동 라벨링 scope-out) |
| claims / evidence | 2차용 placeholder | — | 스키마만 존재, 코드 구현 금지(Tier 1~2 범위, SPEC §0.3) |
| mv_batch_summary (mview) | 배치×팀×레벨 집계 | pass_at_1, tokens_per_success, p95_latency, loop_rate | 배치 종료 시 REFRESH (안 하면 stale) |
| v_analysis_grounded (view) | 분석 grounded 성공기준 | n_search, n_search_ok, grounded | events 실시간 쿼리(저장 아님). 010 마이그레이션 |
| v_team_config / v_team_orchestration | config_full 평탄화 | system_prompt, tools, selector_prompt, termination | git 없이 팀 구성 확인(004) |
***(logger 단일 지점).| 모듈 | 책임 | 주요 심볼 → 호출 |
|---|---|---|
cli.py | 진입점(typer) | run / report / init-db / grounding-score → runner |
runner.py | 배치 오케스트레이터 | run_batch→ _execute_run → registry.build_registered_team + TraceLogger.log_stream + score_fn → runs UPDATE; settings→os.environ 브릿지(검색키) |
teams/registry.py | team_id→builder 매핑 | build_registered_team, build_run_message, ensure_team_record(+config_capture) |
teams/*.py | 팀 정의(빌더) | build_team(settings, model_client)→ (AutoGen 팀, meta). research_pipeline·analyst_web*·cleanret 등 |
teams/config_capture.py | 관측 스냅샷 | capture_team_config→ 프롬프트·도구 impl_sha·종료조건 (방어적 getattr) |
research/web_tool.py | 검색 도구 | web_search/web_search_deduped — Firecrawl(키) ▸ DDG 폴백, stdlib만(직렬화 안전) |
research/corpus.py | 사내 doc 검색 | make_search_corpus(dir, demote_inactive) → FunctionTool |
tracing/logger.py | 이벤트→DB | TraceLogger.log_stream → _build_*_record → _insert_event → RunResult(집계) |
tracing/mapping.py | 이벤트 타입→message_type | get_message_type, is_tool_execution_event, parse_selector_reason |
{gaia,analysis,research,sql,...}/scorer.py | 채점 | score(answer, gold)→ ScoreResult(success, format_valid). benchmark별 교체 |
research/grounding.py | 인용 채점 패스 | score_batch → events에서 SOURCES 추출 → research_grounding upsert |
db.py / config.py | asyncpg 풀·마이그레이션 / 설정 | create_pool, refresh_views / pydantic-settings(env) |
연계 핵심: 개선은 teams/*.py 한 파일에 격리된다(나머지는 import). runner·logger·scorer·DB는 팀과 무관하게 불변 — 그래서 "팀만 바꿔 측정"이 성립한다. web_tool은 어느 팀이 쓰든 동일 구현(impl_sha로 DB에서 구분).
핵심: 우리 build_team()은 AutoGen의 1급 객체 — OpenAIChatCompletionClient, AssistantAgent,
FunctionTool(자동), SelectorGroupChat/RoundRobinGroupChat, *Termination — 를 조립할 뿐이다.
실행은 team.run_stream()이 이벤트를 흘리고, 직렬화는 dump_component()가 한다.
model_info = {"vision":False,"function_calling":True,"json_output":True,
"family":UNKNOWN,"structured_output":True,"multiple_system_messages":True}
OpenAIChatCompletionClient(model=settings.llm_model, base_url=settings.llm_base_url,
api_key=..., temperature=settings.default_temperature, seed=settings.default_seed,
model_info=model_info) # 내부 vLLM/z.ai. function_calling=True가 도구 호출 전제
analyst = AssistantAgent("analyst", model_client,
tools=[web_search], # 평범한 파이썬 함수 → AutoGen이 FunctionTool로 자동 래핑
system_message=ANALYST_PROMPT + date_ctx,
reflect_on_tool_use=False, # 빈 검색결과로 reflection이 빈 응답→RuntimeError 크래시 방지
description="Retrieves current data via web_search; passes results on.")
도구 이름·설명·인자 스키마는 함수 시그니처+독스트링에서 자동 추출(그래서 web_search 독스트링이 곧 도구 설명). research 팀은 make_search_corpus(...)가 반환한 클로저를 도구로 쓴다.
# 분석 라더 = RoundRobin (planner→analyst→synthesizer→critic 고정 순환)
term = TextMentionTermination("APPROVE", sources=["critic"]) | MaxMessageTermination(10)
team = RoundRobinGroupChat([planner,analyst,synthesizer,critic],
termination_condition=term, emit_team_events=True)
# 리서치 라더 = Selector (매 턴 LLM이 다음 발화자 선택 — EXP-60에서 load-bearing 입증)
team = SelectorGroupChat([planner,researcher,critic], model_client=model_client,
termination_condition=term, selector_prompt=SELECTOR_PROMPT,
allow_repeated_speaker=False, max_selector_attempts=3,
emit_team_events=True, model_client_streaming=True)
A | B는 OrTerminationCondition(APPROVE 또는 최대 메시지). emit_team_events=True라야 selector_decision 같은 팀 이벤트가 스트림에 나와 로깅된다.
async for event in team.run_stream(task=run_message): # TextMessage / ToolCallRequestEvent /
... # ToolCallExecutionEvent / SelectorEvent / TaskResult
team.dump_component() # → 네이티브 JSON(model_client·도구 source_code·flags 포함, load 가능)
load_component()·AutoGen Studio 임포트가 되는 실행 가능 표현(§8 다운로드). 반면 config_full(우리 config_capture.py)은 사람이 읽는 관측용 스냅샷 — 프롬프트 전문·도구 impl_sha·종료조건만 담고 model_client가 없어 로드 불가. 둘을 혼동하지 말 것(§10 FAQ Q1).한 문장: run_stream()이 흘린 AutoGen 이벤트를 TraceLogger가 한 건씩 events로 INSERT하면서 토큰·턴·지연을 누적(RunResult)하고, 끝나면 runner가 scorer를 돌려 runs를 UPDATE한다. 인용·grounding은 별도 패스다.
| 메트릭 | 출처 이벤트/필드 | 코드 경유 | 도착 (table.column) |
|---|---|---|---|
| total/input/output_tokens | TextMessage·ToolCallRequestEvent의 models_usage | _extract_tokens → total_*_tokens 누적 → RunResult | runs.total_tokens 외 (runner UPDATE) |
| total_turns | agent_message 이벤트 | log_stream: msg_type==agent_message마다 +1 | runs.total_turns |
| latency_ms (run) | 첫·마지막 event 수신 ts | (last_ts − first_ts)·1000 | runs.latency_ms |
| latency_ms (이벤트별) | 직전 event ts | _calc_latency_ms(prev_ts, ts) | events.latency_ms |
| success / format_valid | FINAL ANSWER 텍스트 | final_answer(_full) → benchmark별 scorer.score | runs.success, runs.format_valid |
| final_answer | planner FINAL ANSWER(한 줄) / synthesizer 전문 | _extract_final_answer (critic CoT 오염 차단; full은 non-critic 마지막) | runs.final_answer |
| terminated_by | TaskResult.stop_reason | _terminated_by (approve/max/error) | runs.terminated_by |
| tool_args (마스킹) | ToolCallRequestEvent.content[i].arguments | _parse_tool_args → _mask_secrets(password/token/…→***) | events.tool_args (JSONB) |
| tool_result (절단+hash) | ToolCallExecutionEvent.content[i] | 2KB 초과시 절단 + sha256 | events.tool_result, events.tool_result_hash |
| selected_next_agent / reason | SelectorEvent / SelectSpeakerEvent | parse_selector_reason (JSON "next" 추출) | events.selected_next_agent, selector_reason |
report_sources)는 synthesizer가 URL을 옮겨 적었는지에 좌우돼 비결정적(EXP-69: 검색 8/9인데 리포트 인용 3/9). 그래서 "검색 도구를 실제 호출/성공했나"(events)를 1급 grounding 신호로 삼고, report_sources는 참고 지표로만 둔다. 인용 채점(cite_f1)도 critic CoT 오염을 피해 planner의 FINAL ANSWER 메시지로 한정해 추출한다(EXP-57이 가르친 교훈).
같은 시스템을 5개의 다른 렌즈로 그렸다. ① 코드가 어떻게 짜였나(컴포넌트) ② 누가 무슨 일을 하나(역할) ③ 숫자가 어떻게 모이나(센서) ④ 그 숫자를 보고 무엇을 의심하나(판단) ⑤ 그래서 무엇을 바꾸나(개선 루프). 마지막은 전 과정 한 장.
윗줄=제어면(무엇을 실행할지 결정·구동), 아랫줄=데이터면(무슨 일이 일어났는지 기록·채점·표시). 개선은 teams/* 한 칸에만 손대고 나머지는 불변 — 그래서 "팀만 바꿔 측정"이 성립한다(=harness가 통제변인).
| 역할 | 책임 | 도구 | 산출 | 대표 실패모드(이 라더 실측) |
|---|---|---|---|---|
| planner | 질문을 소주제/멀티홉으로 분해 | 없음 | 하위 과제 지시 | 빈답·템플릿텍스트(EXP-63), 사실 수집 전 조기 답(EXP-60) |
| researcher / analyst | 사실을 도구로 조회·수집 | search_corpus / web_search(_deduped) | 출처 단 사실 | 빈손 검색을 '결과'로 오인(grounding 착시) |
| synthesizer | 최종 리포트 작성·인용 | 없음 | FINAL ANSWER + 출처 | URL 인용 누락/과다(EXP-67·69, 비결정적) |
| critic | 검수 게이트(승인/반려) | 없음 | APPROVE 또는 반려 | 근거 없이 조기 APPROVE(EXP-60), 과한 게이트=재검색 루프 비용↑ |
| selector (selector 토폴로지만) | 매 턴 다음 발화자 LLM 라우팅 | LLM | 다음 화자 | 제거 시 흐름 붕괴(load-bearing, EXP-60) |
네 가지 타이밍: 실시간(이벤트마다)·run 종료 직후(채점)·배치 종료(mview REFRESH)·쿼리 시점(뷰는 저장 안 함). 자세한 컬럼별 경로는 §16.
| 관측 신호 | 1차 의심 | 교차 확인(어느 메트릭을 같이 보나) | 다음 액션 |
|---|---|---|---|
| pass@1 급락 | 흐름/형식 붕괴 | format_invalid_rate↑ · loop_rate↑ · 어느 hop? | 토폴로지/게이트 점검(가드 동반) |
| tokens_per_success ≥40k | 과검색·재검색 루프 | web_search/run · turns · 정답 수(분모) | 검색 1라운드·게이트 완화(제약 제거) |
| grounded(호출) 100% but report_sources 0 | synthesizer 인용 누락 | grounded(성공기준 010) · search_ok | 도구가 출처 구조화 반환→후처리 강제 |
| grounded(성공) 0% but 도구에러 0 | 빈손 검색(백엔드 한계) | tool_result '(검색 결과 없음)' 비율 | 검색 백엔드 교체(Firecrawl 등) |
| cite_f1 급락 / hop별 격차 | decoy·검색 품질 | hop별 pass · n_cited | 검색 랭킹 수정(비활성 강등 등 도구 레벨) |
| pass@1 양호 but pass^k 낮음 | 비결정성 | attempt별 분산(SD) | N≥3 재측정·결정성 확보(후처리) |
| dramatic 단발 개선 | 노이즈/행운 | N · paired improved/regressed | 반복 측정 후 판정(원장) |
| 병목 (무엇이 문제) | 가장 높은 레버 | 이 라더 증거 |
|---|---|---|
| 능력/정보가 없다 | 도구·검색·grounding | web_search → grounded 0→100%(최대 효과) |
| 정보는 있는데 잘못 쓴다 | 프롬프트·역할(취약) | 여기선 대개 기각(비단조적) |
| 협업이 깨진다 | 토폴로지·종료조건 | selector→round_robin 0.97→0.07 |
| 비용이 문제다 | 제약 제거·dedup·상한 | 과검색 해제 −82%·도구 dedupe |
프레이밍: 이 페이지는 리서치향 멀티에이전트의 한 예시일 뿐이다. 2025–2026 업계의 큰 흐름은 한 문장으로 — "성능을 좌우하는 건 모델이 아니라 그것을 감싼 harness(도구·맥락·루프·검증)". 아래는 관련 트렌드와 논문을 조사해, 각 트렌드 → 우리 시스템의 현황 → 다음 액션으로 매핑한 것. (근거등급: ★공식=벤더/1차 / ◆de-facto=널리 인용된 논문·OSS / ▷내추론=우리 적용 제안. 출처는 각 항목 끝 링크.)
무엇: harness = 모델의 추론 루프를 감싼 비(非)모델 런타임(도구 디스패치·맥락 관리·상태 유지·피드백). "모델 + 주변기능"이 아니라 "harness 안에서 도는 모델"로 본다. 같은 모델이라도 bare-model / vendor-scaffolded / full-system 세 리더보드가 GAIA에서 30~50점 차이 — 차이는 전부 harness다. 패턴: Guides(사전 제어)+Sensors(사후 자기교정), 구조화 노트/서브에이전트 맥락 격리.
우리 현황: 우리의 logger(센서)·scorer·registry·config_capture·종료조건·reflect_on_tool_use=False 가드가 바로 harness다. 측정도 "팀만 바꿔 harness 고정" = harness를 통제변인으로 둔 실험.
▷ 다음 액션: ① critic을 명시적 Sensor 카탈로그로(형식·출처·최신성 체크를 코드 게이트화) ② AGENTS.md류 크로스툴 컨벤션 ③ 자동 harness 최적화(아래 C와 결합).
출처: Anthropic — Effective context engineering ★ · Anthropic — Effective harnesses for long-running agents ★ · M. Fowler — Harness engineering ◆ · Anthropic — Building effective agents ★(2024-12)
무엇: Geoffrey Huntley가 2025년 명명. while todo: agent(prompt) — 비대화형 'yolo' 모드로 에이전트를 반복 실행. 매 반복은 fresh context(깨끗한 새 인스턴스), 메모리는 git history·progress.txt·prd.json로 외부 영속화. 핵심 전제: 테스트 가능한 수용기준(AC)이 있어야 루프가 "언제 끝인지" 안다. 테스트·보안스캔·품질체크 실패 → 피드백 → 자가수정.
우리 현황: 우리의 1변수 측정 루프(특이점→가설→개입→측정→판정→원장)가 사실상 사람이 도는 Ralph다. experiments 원장 + paired 채점 = "AC가 충족됐나"의 자동 판정 절반.
▷ 다음 액션: cli.py loop — ① report에서 특이점 자동 탐지(임계 매트릭스 §17④) → ② 후보 개입 1개를 새 team 모듈로 생성 → ③ run+grounding-score+compare → ④ verdict 자동 INSERT → ⑤ promoted면 베이스라인 교체. 페이지의 가드레일 7계명이 곧 루프의 안전판.
출처: G. Huntley — Ralph ◆(2025) · snarktank/ralph ◆ · codecentric — Ralph Wiggum loop ◆
무엇: 메타-에이전트가 에이전트를 설계/진화시킨다. ADAS(Hu et al. 2024): meta-agent가 코드로 새 에이전트 아키텍처를 탐색·평가·개선. Gödel Agent / Darwin Gödel Machine: 자기 코드를 재귀적으로 개선. GEPA(2025): 스칼라 점수만이 아니라 실행 trace의 자연어 피드백을 읽고 프롬프트를 진화(Pareto 선택) — RL(GRPO) 대비 35× 적은 롤아웃으로 우위. AlphaEvolve(DeepMind 2025)/OpenEvolve: 프롬프트가 아니라 소스코드 자체를 진화.
우리 현황: 우리의 experiments 원장 + paired 측정 + events trace는 이 진화 루프의 평가·피드백 절반을 이미 갖췄다. 특히 GEPA의 입력(=trace 기반 자연어 진단)은 우리 events·content에 그대로 있다.
▷ 다음 액션: ① GEPA로 프롬프트 자동 진화(평가셋=우리 task, 피드백=critic/grounding) ② ADAS류 토폴로지 탐색(selector/round_robin/swarm·게이트 임계를 meta-search) — 단 EXP-60 교훈대로 정확도 가드 필수.
출처: ADAS (arXiv:2408.08435) ◆ · Gödel Agent (arXiv:2410.04444) ◆ · GEPA ◆(2025, DSPy 옵티마이저) · AlphaEvolve (DeepMind, 2025) ◆
무엇: "정답률 한 줄"을 넘어 왜 실패하는지·얼마나 안정적인지를 잰다. MAST(NeurIPS 2025): 7개 MAS 프레임워크 1600+ trace에서 14개 실패모드 / 3범주(명세·에이전트간 정렬·검증) 도출, LLM-as-Judge로 자동 라벨(Cohen's κ 0.88). τ-bench: pass^k(k회 전부 성공)로 신뢰성을 폭로 — best-of-k가 아니라 일관성. inference-time verification(DeepVerifier 등): 답을 스스로 검증·재정제해 GAIA 등에서 8~11%↑.
우리 현황(이미 정렬됨!): ① MAST 분류를 이미 채택 — migrations/011_failure_taxonomy.sql + docs/MAST_DEEP_DIVE. ② pass^k를 이미 측정(§11 C표, EXP-69가 정확히 그 신뢰성 함정). ③ grounding을 텍스트가 아닌 events로 재는 것도 검증 철학의 일종.
▷ 다음 액션: ① failure_stage/root_cause 자동 라벨링(현재 scope-out; SPEC §0.3 — 트렌드상 다음 단계) ② claims/evidence 테이블 활성화 → 클레임 교차검증(agent-as-a-judge) ③ rubric-guided inference-time 재검증.
출처: Why Do Multi-Agent LLM Systems Fail? / MAST (arXiv:2503.13657) ★ · MAST GitHub · τ-bench (arXiv:2406.12045) ◆ · LLM-as-a-Judge 서베이 (arXiv:2411.16594) ◆ · Reflexion (arXiv:2303.11366) ◆
무엇: 헤드라인 점수는 부풀려진다. SWE-bench Verified는 학습데이터 누수로 5~15점 인플레, 일부 task는 테스트가 버그를 못 잡음. 엔터프라이즈 배포에서 lab↔prod 37% 격차, 유사 정확도에 50× 비용 편차. "모델 점수 ≠ 제품 점수 ≠ 통합 시스템 점수"(GAIA 세 리더보드 30~50점차).
우리 현황: 우리가 이미 쓰는 방어 — events 기반 grounding(자기보고 불신)·paired 비교(난이도 교란 제거)·N≥3·pass^k(단발 불신)·유령 메트릭 검증(EXP-57 채점 버그). 비용 축(tokens_per_success)도 1급 메트릭.
▷ 다음 액션: ① 다차원 평가(정확도+비용+안전+지연 동시 게이트) ② 라이브 검색 비결정성 대비 고정 스냅샷 코퍼스 옵션 ③ 도메인 평가셋의 정기 갱신(누수·드리프트 방지).
출처: Beyond Accuracy: Enterprise Agentic AI 평가 (arXiv:2511.14136) ◆(2025-11) · agentic-benchmarks (UIUC) ◆
| 트렌드 | 핵심 | 우리 현황 | 다음 액션 |
|---|---|---|---|
| Harness engineering | harness가 성능 좌우 | logger/scorer/registry = harness(통제변인) | Sensor 카탈로그·자동 최적화 |
| Ralph loop | 지속 루프+fresh context+외부 메모리 | 1변수 측정 루프(수동 Ralph) | cli loop로 특이점→개입→판정 자동화 |
| ADAS/GEPA/AlphaEvolve | 메타-에이전트가 설계·진화 | 원장+paired+trace=평가 절반 보유 | GEPA 프롬프트 진화·ADAS 토폴로지 탐색(가드 필수) |
| MAST·pass^k·검증 | 왜 실패·얼마나 안정 | 011 MAST 채택·pass^k 측정 완료 | 자동 라벨링·claims/evidence 교차검증 |
| 평가의 함정 | 누수·lab↔prod·세 리더보드 | events grounding·paired·N≥3 | 다차원 게이트·스냅샷 코퍼스 |
왜 이 절이 필요한가: §18의 Ralph loop·harness engineering은 '실행/자동화 기법'의 일부다. 정작 업계 전반·대형 서비스·표준화에서 지금 가장 크게 다루는 멀티에이전트 아이템은 따로 있다. 아래 8개 축으로 정리하고, 각 축에서 이 시스템이 어디에 있는지를 표기했다. (시장 수치·제품 사실은 벤더/애널리스트·언론 보도 기준, 2025–2026 시점 — 빠르게 갱신됨.)
아래 8축 지형도는 공식 단일 표준이 아니라 업계 'agent stack' 정리를 종합한 것이다(O'Reilly·LangChain·서베이). 반면 우리 시스템 자체의 분류·점수는 공인 레퍼런스를 그대로 차용했다 — 발표 시 이 셋을 근거로 대면 된다:
| 우리 역할 | Anthropic 패턴 (5종 중) |
|---|---|
| planner (분해·하위지시) | orchestrator-workers의 orchestrator |
| analyst / researcher (도구로 사실 수집) | worker + augmented LLM(tool use) |
| synthesizer (합성·작성) | worker (합성 단계) |
| critic (검수·반려/승인) | evaluator-optimizer |
| selector (리서치팀 라우팅) | routing |
→ 즉 우리 팀 = orchestrator-workers + evaluator-optimizer 결합(+ 리서치팀은 routing). 5패턴: prompt chaining·routing·parallelization·orchestrator-workers·evaluator-optimizer. 출처: Anthropic, Building Effective Agents (2024-12) ★
011_failure_taxonomy.sql)| MAST 3범주 (14모드) | 이 라더의 실측 예 |
|---|---|
| ① specification (명세·지시 위반) | planner 빈답·템플릿텍스트 출력 (EXP-63) |
| ② inter-agent misalignment (에이전트 간 정렬) | critic 근거 없이 조기 APPROVE·환각 (EXP-60) |
| ③ task verification (검증 실패) | synthesizer 인용 누락·비결정 (EXP-69) |
출처: Why Do Multi-Agent LLM Systems Fail? / MAST (arXiv:2503.13657) ★ (1600+ trace, LLM-judge, Cohen's κ 0.88)
pass@1 / pass@k = OpenAI Codex/HumanEval (Chen et al. 2021) ★ · pass^k(신뢰성) = τ-bench (2024) ★ · 인용 precision/recall/F1 = 정보검색(IR) 표준 지표 ◆.
발표용 한 줄: "에이전트 구조는 Anthropic Building Effective Agents 패턴, 실패 분류는 MAST(NeurIPS 2025), 점수는 HumanEval의 pass@k·τ-bench의 pass^k를 차용했다." (8축 지형도만 '종합 정리'로, 단일 표준 아님.)
MCP(Anthropic, 2024-11)는 도구·맥락 연결의 de-facto 표준이 됐다(2010년대 LSP가 IDE에 그랬듯 — Cursor·VS Code·GitHub·Slack·Notion 등 광범위 채택). A2A(Google, 2025-04)는 에이전트끼리 동료(peer)로 일을 위임하는 프로토콜로 Linux Foundation에 기증; IBM의 ACP(AGNTCY: Cisco·LangChain·LlamaIndex 등)는 2025-08 A2A로 통합 수순. 요지: MCP=도구를 붙이는 층, A2A=에이전트끼리 말 거는 층.
Microsoft(Copilot Studio: 16만 조직·40만+ 커스텀 에이전트, Agent 365·Azure AI Foundry)·Salesforce Agentforce(보도상 ARR ~$800M)·ServiceNow가 선두; AWS Bedrock AgentCore·Google Agentspace(+Cloud Next의 A2A 전면화)·NVIDIA(GTC 2026 엔터프라이즈 에이전트 플랫폼)도 가세. Gartner는 2026년 말 엔터프라이즈 앱의 40%가 태스크 특화 에이전트 탑재(2025년 <5%)를 전망.
프롬프트 인젝션이 에이전트 체인을 타고 전파·증폭(중간 에이전트가 악성 지시를 오히려 정제)되고, 공유 메모리/벡터스토어 오염·에이전트 신원·신뢰 문제가 부상. OWASP·NIST는 간접 프롬프트 인젝션을 "생성형 AI 최대 결함"으로 명시. 단일 에이전트 가드레일로는 부족 → 아키텍처 레벨 강제 필요(TRiSM 프레임워크 등).
| 축 | 무엇이 화제인가 | 주요 플레이어·제품 | 이 시스템의 위치 |
|---|---|---|---|
| A. 상호운용 프로토콜 | 도구·에이전트 연결 표준 | MCP(Anthropic)·A2A(Google/LF)·ACP(AGNTCY)·ANP | 도구를 MCP로 노출 가능(미적용); AutoGen Studio 반입은 됨 |
| B. 오케스트레이션 프레임워크 | 에이전트 조립·라우팅 추상화 | LangGraph·CrewAI·AutoGen→AG2·OpenAI Agents SDK·Google ADK·Claude Agent SDK·DSPy | 여기 — AutoGen(AG2 계열) GroupChat(Selector/RoundRobin) 사용 |
| C. 엔터프라이즈 플랫폼 | 거버넌스+워크플로 통합 | MS Copilot/Agent365·Salesforce Agentforce·ServiceNow·AWS Bedrock AgentCore·Google Agentspace·NVIDIA | 제품 아님 — 내부 '평가 하니스'(이들의 신뢰성 검증에 해당) |
| D. 플래그십 능력 | 딥리서치·컴퓨터유즈·코딩·범용자율 | Deep Research(OpenAI 등)·Operator/Computer Use/Mariner·Manus·Claude Code | 도메인 일치 — Deep Research형 MAS(analyst-web) |
| E. 오케스트레이션 패턴 | 역할 위상·승인 게이트 | orchestrator-worker(최다)·supervisor/라우팅·swarm/hierarchical·HITL gate | planner-분배 + critic 게이트(HITL의 자동판) + selector(supervisor 유사) |
| F. 메모리·상태 | 지속 메모리·agentic RAG | Letta(MemGPT)·Mem0·Zep(Graphiti)·LangMem·permission-aware RAG | 현재 run 무상태; 외부기억=events DB; RAG=search_corpus/web_search |
| G. 옵저버빌리티·평가·가드레일 | 트레이스·eval·eval→guardrail | LangSmith·Langfuse·Arize Phoenix·AgentOps·Braintrust·Galileo | 본체가 이것 — trace DB(events)+메트릭+원장 = 자체구축 옵저버빌리티·평가 |
| H. 보안·신뢰성·거버넌스 | 인젝션 전파·신원·메모리오염 | OWASP/NIST·TRiSM·멀티에이전트 보안 연구 | 폐쇄망·비밀 마스킹·CHECK 불변식; 인젝션 방어는 향후 과제 |
출처(대표): MCP(Anthropic) ★ · A2A(Google) ★ · 에이전트 상호운용 프로토콜 서베이(arXiv:2505.02279) ◆ · OpenAI Agents SDK ★ · Google ADK ★ · AutoGen/AG2 ◆ · Operator(MIT TR) ◆ · Anthropic Computer Use ★ · Letta/MemGPT ◆ · TRiSM for Agentic AI(arXiv:2506.04133) ◆ · OWASP LLM01 프롬프트 인젝션 ★ · Gartner 에이전트 전망 ◆(보도 기준)
왜 이 절이 필요한가: §1–19가 '무엇을 측정하고(개념·지표), 어떻게 동작하나(코드·스키마)'였다면, 이 절은 코드를 폐쇄망 머신으로 옮겨 직접 돌리는 절차다. 평가 1회는 한 명령으로 끝나지 않는다 — 환경 → 설정 → 스키마 → 문제 적재 → LLM 점검 → 스모크 → 본 평가가 사슬로 이어지고, 앞 고리가 빠지면 뒤가 즉시 무너진다. 각 단계를 명령 한 줄 + 존재 이유 + 다음 단계로의 연결로 읽는다.
| # | 명령 | 무엇을 한다 | 왜 · 다음 단계와의 연관 |
|---|---|---|---|
| 0 | python3 -m venv .venvsource .venv/bin/activatepip install -e ".[dev]" |
가상환경 생성 + 의존성 설치 | python이 시스템에 없을 수 있고, 폐쇄망이라 외부 PyPI 금지 → 사내 미러로 설치. 이게 돼야 cli.py가 import된다. 매 세션 activate 필수. |
| 1 | cp .env.example .env (편집) |
EVAL_PG_DSN·EVAL_LLM_BASE_URL 채움 |
설정 하드코딩 금지 — pydantic-settings가 env만 읽는다. DSN/LLM이 없으면 이후 모든 단계가 즉시 실패. 비밀은 .env에만(커밋 금지). |
| 2 | python cli.py init-db |
마이그레이션 001+ 멱등 적용 | 스키마가 SSOT. 2회 돌려도 성공(AC1). 단 최초 1회 000_provision.sql(계정·스키마 생성)은 DBA 수동 — init-db는 그 위에서만 동작. |
| 3 | python cli.py load-gaia |
tasks 테이블에 문제 적재 (GAIA 165) |
평가 대상이 있어야 runner가 선별. 벤치마크별 로더가 다름(아래 표). 첨부 빠진 태스크는 attachment_missing로 기본 제외. |
| 4 | python scripts/smoke_llm.py |
LLM function-calling 1회 왕복 | 실측 165개를 깨먹기 전 vLLM tool-calling 설정 확인 게이트. 실패 시 --enable-auto-tool-choice --tool-call-parser부터 의심. 통과해야 다음으로. |
| 5 | python cli.py run --batch dev_smoke --team v1-baseline --limit 2 --exclude-web |
2문제 스모크 실행 | 조건(model·temp·seed)을 running으로 INSERT 박제 → 채점 → completed. 같은 batch 재실행 시 completed run은 멱등 skip(재개). |
| 6 | python cli.py report --batch dev_smokepython cli.py failures --batch dev_smoke |
pass@1·레벨별 p50·도구 / 실패 트랜스크립트 | 눈대중이 아니라 숫자로 확인. 깨끗하면 스케일업, 실패가 있으면 failures로 트랜스크립트를 직접 본다. |
| 7 | python cli.py run --batch perf_… --team … --concurrency 1 --retry-crashed또는 python scripts/eval_all_teams.py |
본 평가 (팀 1개 전체 / 52팀 전부) | --limit 제거 전체. 드라이버는 팀↔벤치마크 매핑·재개·크래시 재시도(3회), 결과를 scorecard.json에 실시간 누적. z.ai 직렬 — 동시 두 평가 금지. |
.venv가 없다. python3 -m venv .venv로 만들고, 외부 PyPI 대신 사내 미러로 설치한다 — pip install -e ".[dev]" --index-url http://<사내-pypi>/simple --trusted-host <사내-pypi>. 런타임 코드는 huggingface/외부 API를 호출하지 않으며 LLM은 내부 vLLM(EVAL_LLM_BASE_URL)만 쓴다.| 벤치마크 | 로더 |
|---|---|
| gaia | cli.py load-gaia |
| research | cli.py load-research |
| cppx | cli.py load-cppx |
| learn_algo | cli.py load-learn |
| analysis | scripts/load_analysis.py |
| sql | scripts/load_sql.py |
| extract | scripts/load_extract.py |
| coding · codetest | scripts/load_code.py |
| meeting · ocrproxy · role | scripts/load_roles.py |
cli.py run 주요 파라미터| 플래그 | 뜻 |
|---|---|
--batch 필수 | 실행 묶음 ID = 재개 키. 같은 값으로 재실행하면 이어서. |
--team | team_id (기본 v1-baseline, 52팀 등록) |
--benchmark | gaia/research/cppx… |
--limit N | N개만 (스모크용) |
--exclude-web | 웹 필요 태스크 제외 |
--concurrency N | 동시 실행 수 (z.ai면 1 권장) |
--attempts N | 태스크당 반복 (pass@k용) |
--retry-crashed | crashed run 삭제 후 재실행 |
--task-ids a,b | 특정 태스크만 |
ps aux | grep -E "eval_all_teams|cli.py run"로 백그라운드 평가가 도는지 보고, 돌고 있으면 끝날 때까지 기다리거나 같은 batch ID로만 재개한다. 본 평가는 --concurrency 1로. 진행 현황은 data/personal/eval/scorecard.json.# 0) 폐쇄망 머신: 환경 1회
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" --index-url http://<사내-pypi>/simple --trusted-host <사내-pypi>
cp .env.example .env # EVAL_PG_DSN / EVAL_LLM_BASE_URL 채우기
# 1) 스키마 → 문제 → LLM 점검
python cli.py init-db
python cli.py load-gaia
python scripts/smoke_llm.py
# 2) 스모크(2문제) → 리포트
python cli.py run --batch dev_smoke --team v1-baseline --limit 2 --exclude-web
python cli.py report --batch dev_smoke
# 3) OK면 본 평가 (팀 1개 전체 / 또는 52팀 전부)
python cli.py run --batch perf_v1-baseline --team v1-baseline --concurrency 1 --retry-crashed
python scripts/eval_all_teams.py