주제 리서치: base vs 개선반도체 업계 동향

같은 주제를 두 에이전트 팀에 돌린 실제 리포트 + 품질 해석 + team config + 도식 + base→개선 metric 여정 · 실제 기업명은 익명 처리(A사·B사 등)
무엇을 비교하나: 동일 주제 "반도체 업계 동향(2026 메모리/HBM·공정·경쟁·공급망·전망)"base(v2-analyst-deep, 웹 없음)과 개선(v6-analyst-web, 웹 검색+날짜+출처 인용)에 각각 돌린 결과. 둘 다 planner→analyst→synthesizer→critic 4-에이전트(z.ai GLM). 차이는 웹 근거 하나.
먼저 읽어주세요 — 이 페이지의 두 가지 스코프(중요): ① 리포트(§2)는 '반도체' 1개 주제를 보여준다.   ② 하지만 메트릭 숫자(§5·§6의 토큰·검색·지연 등)는 서로 다른 주제 3개의 '평균'이다 — 한 주제만 보면 우연일 수 있어, 통계 신뢰를 위해 반도체 + 전기차 배터리 + AI 가속기(GPU) 세 주제를 각각 돌려 평균냈다(이게 N=3의 뜻). 각 주제 run은 자기 주제만 검색한다(반도체 run은 반도체만). 그래서 §7 로그의 '반도체' 검색어는 전부 반도체/HBM/파운드리다.
처음 보세요? — 용어·개념·"이거 믿어도 되나?"를 한 곳에 모은 FAQ
grounding·pass@k·토폴로지·두 개의 grounded% 같은 낯선 말과 회의적 질문 95개를, 이 분야를 처음 접하는 사람 기준으로 아주 자세히 풀었습니다(난이도·검색 필터 포함).
95문항 보기 →
📖 용어·약어 풀이 — 낯선 말이 나오면 여기서 (펼치기)
task (태스크)
평가에 쓰는 질문/작업 1건. 예: "반도체 업계 동향을 분석해줘". 이 평가셋엔 task가 3개(반도체·EV배터리·AI가속기).
run (런)
한 팀이 한 task를 1번 실행한 것. 1 run = 대화 1세션 + 결과 리포트 1개.
N=3
서로 다른 task 3개를 각각 돌렸다는 뜻. 숫자(토큰·검색 등)는 이 3개의 평균. 한 주제(N=1)는 우연일 수 있어 평균이 더 믿을 만하다.
batch (배치)
한 실험에서 돌린 run 묶음(예: v3 팀 × task 3개 = 3 run = 1 batch).
events / seq
events = 한 run 안의 모든 메시지·도구호출 기록(DB에 1줄=1이벤트). seq = 그 순번(s0, s2, s4…), 즉 일어난 순서.
턴 (turn)
에이전트가 한 번 발언하는 단위. "13턴" = 13번 주고받음.
grounded% (근거율)
리포트가 실제 웹 검색에 근거했나. 모델 말이 아니라 '검색 도구를 진짜 호출했나(events)'로 잰다. 0%=기억으로만, 100%=실검색 기반.
report_sources
최종 리포트 '## 출처'에 박힌 URL 개수.
web_search/run
run 한 번에 검색 도구를 부른 횟수.
tokens (토큰)
LLM이 읽고 쓴 양 ≈ 비용. 많을수록 비싸고 느리다.
latency (지연)
run 1번이 끝나는 데 걸린 시간(초).
빈 쿼리 연타
같은 검색 도구를 검색어 없이('') 여러 번 연속 호출한 것 — 무의미한 헛호출(모델의 도구 난사).
1변수 (원칙)
한 번에 딱 한 곳만 바꿔 측정. 두 곳 바꾸면 어느 게 효과인지 못 가린다.
promoted / rejected /
inconclusive
실험 판정 — 채택(개선됨) / 기각(역행·부작용) / 판단보류(불확실).
paired (짝지은) 비교
같은 task끼리 두 팀을 비교. 서로 다른 task로 비교하면 난이도 차이에 속는다.
planner→analyst→
synthesizer→critic
4개 역할: 분해검색·분석리포트 작성검수(승인/반려).
FINAL ANSWER /
APPROVE
synthesizer가 'FINAL ANSWER:'로 최종 리포트를 내고, critic이 'APPROVE'를 출력하면 대화 종료.
critic 게이트 / 반려
critic이 조건(출처 N개 등) 미달이면 반려 → 다시 검색·작성. 조건이 빡세면 재검색 루프가 길어져 비용↑.
dedup (중복 제거)
같은 출처(도메인)가 반복되면 하나만 남김.
EXP-65 등
실험 일련번호(원장 기록). EXP = experiment(실험).
v2-deep / v6-dedup
버전 이름. v 뒤 숫자가 클수록 나중(개선된) 버전.
prompt_bundle_hash /
team_id
프롬프트가 한 글자라도 바뀌면 자동으로 다른 team_id가 부여돼, 바뀐 팀과 안 바뀐 팀을 시스템이 구분(실수 방지).
temp=0 / seed
모델을 같은 입력엔 같은 출력이 나오게 고정(통제 비교용).
0회5회
웹 검색
0개6개
인용 출처 URL
없음2026-06-14
기준 시점(최신성)
검증불가검증가능
수치의 출처
29.4k21.3k
토큰/run −28% (개선이 더 쌈)
280s177s
지연/run −37% (개선이 더 빠름)
1 이 데이터 퀄리티가 어떤 면에서 좋아졌나 (해석)
품질 차원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 검증)가상이 아니라 추적 가능한 관전 포인트
핵심 해석 — "검증가능성"이 진짜 가치다: base 리포트도 문체·구조는 그럴듯하다. 차이는 믿을 근거가 있느냐다. 실제로 이 페이지 초안에서 내(AI)가 개선 리포트의 "A사 1분기 영익 57조"를 내 학습지식(2025 ~17조)으로 "틀렸다"고 적었는데 — 틀린 건 나였다(2026 슈퍼사이클로 57조 실제 맞음, 도메인 출처 재확인). 즉 출처 있는 웹 리포트가 맞았고, 내 기억 기반 '검증'이 환각이었다. 이게 base 팀의 실패 모드 그 자체이고, "모델 기억"이 아니라 "실제 출처"로 검증해야 하는 이유다.
2 실제 리포트 (같은 주제, 나란히)
🔴 base · v2-analyst-deep웹 없음 · 출처 0
🟢 개선 · v6-analyst-web웹검색 5회 · 출처 6
3 각 팀의 현재 config 구조
v2-analyst-deep · RoundRobin · 도구 없음
planner도구없음분석 요청을 5~6개 소주제(시장·기술·경쟁·공급망·리스크·전망)로 분해
analyst도구없음각 소주제를 구체 사실·수치로 분석 — 학습지식에서 (검색 불가)
synthesizer도구없음FINAL ANSWER 마크다운 섹션 리포트 작성 (출처 개념 없음)
critic도구없음구체성·균형·구조 점검 → APPROVE 또는 1회 반려
v6-analyst-web · RoundRobin · web_search_deduped
planner+날짜맥락동일 분해 + "오늘은 2026-06-14" 주입(학습시점 아닌 현재 기준)
analyst🔍 web_search_deduped소주제당 검색(약 5회, 1라운드)으로 수치·날짜 실확인, 각 사실에 출처 URL
synthesizer+출처상한동일 리포트 + 중복 제거 후 핵심 5~8개만 '## 출처' 인용 + '기준 시점' 명시
critic완화게이트FINAL ANSWER + 출처 URL ≥3개면 APPROVE (재검색 루프 안 강제)
1변수 원칙: 두 팀은 같은 4-에이전트·같은 토폴로지·거의 같은 프롬프트다. 바뀐 건 ① analyst에 web_search_deduped 도구오늘 날짜 주입 ③ synthesizer 출처 상한 — 이 묶음 하나. 그래서 차이가 "웹 근거" 효과로 격리된다.
4 파이프라인 도식 (개념도)
base — 학습지식 폐루프
질문
planner
분해
analyst
기억에서
synth
리포트
critic
📄 출처 0 · 검증 불가
개선 — 웹 근거 주입
질문
+오늘날짜
planner
분해
analyst
🔍 실검색
synth
출처 5~8
critic
📄 출처 6 · 검증 가능
완성형(OpenAI 딥리서치급) 개념도:조사(analyst-web, ✅ 이 페이지) → ② 팩트체크(교차검증 — 각 주장을 인용 출처에 대조, ✅ 별도 구축) → ③ 반복 검색(갭 분석→추적, 기획 단계). 지금 ①②를 각각 가졌고, 합치면 "출처 단 + 검증된 장문 리포트".
5 ★ base → 개선: metric 특징과 개선 과정 (심층)

핵심 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)00029.4k280
v3-web EXP-651004.717.397k602promoted
v4-lite EXP-661002.35.717.8k209promoted
v5 EXP-676715.75.028.6k306rejected
v6-dedup EXP-68 (현재)1004.74.021.3k177promoted
▸ 수행 시 수집된 전체 메트릭 — base(v2-deep) ↔ 현재(v6-dedup) 완전 비교
메트릭측정 방식 (어디서 나오나)base v2-deep현재 v6-dedupΔ 절대Δ %해석
grounded_pct (%)events — web_search 호출수 ≥1 (자기보고 ❌)0100+100pp도구 유무에서 갈리는 능력 자체
web_search / runevents — 도구 호출 카운트04.0+4.01라운드 dedupe 검색 (과검색 제거 후)
report_sources *리포트 텍스트 파싱 (## 출처)04.7*+4.7*N=3 평균·flaky (아래 EXP-69 주의)
total_tokens누산기 — input+output 합산29,40921,285−8,124−28%개선이 base보다 오히려 쌈
latency_s (e2e)first_ts ↔ last_ts (로거 수신 wall-clock)280177−103−37%개선이 base보다 빠름 (전 버전 통틀어 최저)
crashed런타임 예외 카운트000단 ⓪하니스 수정 전 web팀은 빈 검색에 크래시
stop_reason종료 사유 문자열APPROVE / MaxMsgAPPROVEcritic 완화 게이트로 조기 APPROVE 안정
tokens_per_success 파생 — SUM(tokens)/SUM(success)29,40921,285−8,124−28%†success=1.0 가정(무크래시) → 정직한 비용축
grounded% / 1k token 파생 — 근거효율0.04.70+4.70†base는 토큰을 써도 근거 0 → 효율 0
tokens / source 파생 — 출처 1개당 비용n/a (출처 0)≈4,529†v5(전부인용)은 ≈1,800이나 노이즈로 기각
측정 방법론 — grounding은 'LLM 자기보고'로 절대 안 잰다: 모델은 시계가 없어 도구가 없으면 학습 컷오프(2024) 지식을 현재처럼 출력한다. 그래서 grounded_pct는 모델이 "검색했다"고 말한 걸 믿는 게 아니라 events의 web_search 실제 호출수로 독립 재계산한다 — crosscheck에서 stale 팀 0/3 run이 검색 사용, grounded 팀 3/3 run(총 104 calls)로 검증됐다. (텍스트에 URL이 박혔는지가 아니라 도구를 실제로 때렸는지가 견고한 신호다.)
① 특이점
(base)
grounded% = 0, 출처 = 0. base는 검색 도구가 없어 모든 사실이 학습지식. → 특이점: "리포트에 출처가 한 개도 없다"(events에 web_search 호출 0건). 최신 사건을 물어도 학습 컷오프 시점 일반론만 나온다.
② 개입 EXP-65
web_search+날짜
능력 획득 analyst에 web_search 도구 + "오늘 날짜" 주입(1변수). → grounded 0→100%, 출처 0→4.7, 검색 0→17.3회. 단 비용 폭증: 토큰 29k→97k(3.3×), 지연 280→602s. 교훈: 능력은 얻었지만 과검색(턴당 5+)+critic 재검색 루프가 순비용. → 이 과검색·재검색이 실제로 어떻게 찍혔는지는 §7 EXP-65의 '🔬 실제 로그 증거'에서 DB 궤적으로 확인.
③ 개입 EXP-66
검색 1라운드
비용 회복 과검색·재검색 루프 제거(검색 1라운드, 완화 게이트). → 토큰 97k→17.8k(−82%), 지연 −65%, 턴 10.3→5.0. grounded 100% 그대로 유지(검색 5.7회). 핵심 교훈: grounding은 "도구를 쓰느냐"에서 나오지 "몇 번 검색하냐"에서 안 나온다 — 과검색은 순낭비. stale base(29k)보다도 싸짐.
④ 개입 EXP-67
전부 인용 강제
기각 출처가 줄어든 게(4.7→2.3) 아쉬워 synthesizer에 "검색 URL 전부 나열" 강제. → 출처 2.3→15.7로 늘었으나 37개 덤프(중복·노이즈), 토큰 +61%, grounded 100→67%. 순개선 아님 → 기각. 교훈: 인용은 양(전부)이 아니라 질(핵심)이다.
⑤ 개입 EXP-68
dedup+상한 (현재)
질로 회복 도구를 도메인 중복제거(web_search_deduped) + synthesizer "핵심 5~8개만" 상한. → 출처 2.3→4.7 회복(과다 없음), grounded 100%, 지연 177s(최저). v5(전부인용)의 목표를 더 싸고 안정적으로 달성 → 현재 챔피언 v6.
⚠️ 정직한 한계 — '출처 수(report_sources)'는 매우 불안정하다 (EXP-69, inconclusive): grounding(검색 행위)은 events로 견고히 잴 수 있지만, 그게 리포트 인용으로 이어지는지는 비결정적이다. 별도 재측정(9 run)에서 web_search는 8/9가 썼지만 synthesizer가 URL을 '## 출처'로 옮긴 건 3/9뿐 — 즉 EXP-68의 "출처 4.7"은 N=3 행운이고 실측 분포는 "대개 0, 가끔 분출"이다. 이 페이지의 반도체 리포트는 6개를 실제로 달았으나(성공 케이스), 통계적으로는 robust하지 않다. → 다음 개선 과제는 클레임 단위 교차검증 + 인용 강제(synthesizer가 검색한 URL을 빠뜨리지 않게)다. cf. 5주제 갤러리도 LLM·반도체만 출처 성공.
전체 패턴 (이 라인이 가르쳐준 것): 제약을 '추가'하면(검색 강제 EXP-65, 전부 인용 EXP-67) 비용 폭증·부작용 → 제약을 '제거/정제'하면(과검색 해제 EXP-66, dedup+상한 EXP-68) 같은 능력을 더 싸게. 그리고 능력(grounding)은 도구 도입 한 번에 얻고, 이후 개선은 전부 "어떻게 더 싸고 깔끔하게"의 튜닝이었다. base→현재: 출처 0→4.7, grounded 0→100%, 게다가 지연은 280→177s로 더 빠름.
6 ★ 각 개선의 실제 코드 변경 (디테일)

metric 특이점을 잡아도, 고치는 건 별개다. "어디를 어떻게 바꿨나"의 실제 diff — 1변수 원칙대로 한 번에 한 곳만. 특히 측정 자체를 가능하게 한 비자명한 하니스 버그 2건(아래 ⓪)이 이 라인의 가장 예리한 판단이었다.

⓪ 측정을 가능하게 한 하니스 수정 2건 (코드를 짜다 발견한 함정)

개선 코드가 아니라, 그게 측정 가능하도록 먼저 고쳐야 했던 비자명한 버그들. 안 고쳤으면 run이 크래시하거나 엉뚱한 답을 채점했다.
수정 A · 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가 해석·작성.
수정 B · 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 호출수로 측정해야 견고하다는 교훈도 여기서.)

① EXP-65 (base → v3): 웹 grounding 추가 promoted

특이점 "출처 0·grounded 0%" → analyst에 검색 도구 + 오늘 날짜를 주입.
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개. 미달이면 어느 소주제 재검색할지 지목 반려."
grounded 0→100% · 출처 0→4.7 · 검색 0→17.3회. 단 토큰 29k→97k(3.3×) — 과검색이 순비용.

② EXP-66 (v3 → v4): 검색 다이어트 — 비용 −82% promoted

특이점 "토큰 97k·지연 602s" → 주범인 과검색 강제 + critic 재검색 루프를 제거.
teams/analyst_web_lite.py vs v3
ANALYST_PROMPT:
- "한 턴에 최소 5회 web_search. critic이 반려하면 그 소주제를 다시 web_search 보강."  # 재검색 루프
+ "소주제당 1회씩 약 5회만 검색하고 끝낸다. 재검색 루프 없이 한 번에 마친다(효율 우선)."

CRITIC_PROMPT:
- "출처 5개 이상 + 각 섹션 1개. 어느 소주제 재검색할지 지목해 반려."
+ "FINAL ANSWER + 출처 3개 이상이면 APPROVE. 그 외엔 한 번만 간단히 반려(루프 안 끔)."

- term = ... | MaxMessageTermination(18)   # 재검색 루프 여유
+ term = ... | MaxMessageTermination(10)   # 루프 없으니 상한 낮춤
토큰 97k→17.8k(−82%) · 지연 −65% · 턴 10.3→5.0. grounded 100% 유지. 교훈: grounding은 '도구 사용'에서 나오지 '검색 횟수'에서 안 나온다.

③ EXP-67 (v4 → v5): 전부 인용 강제 — 역행 rejected

출처가 줄어든 게(4.7→2.3) 아쉬워 synthesizer에 "검색 URL 전부 나열"을 강제 → 부작용.
teams/analyst_web_cited.py · SYNTH_PROMPT만 변경
SYNTH_PROMPT:
+ "검색 결과 출처 URL은 하나도 빠뜨리지 말고 인라인으로 달고,
+  '## 출처'에 모든 URL을 빠짐없이 나열한다(최소 4개 이상)."  # ← 양으로 밀어붙임
출처 2.3→15.7 (한 task는 37개 덤프·중복·노이즈) · 토큰 +61% · grounded 100→67%. 순개선 아님 → 기각. 교훈: 인용은 양(전부)이 아니라 질(핵심).

④ EXP-68 (v4 → v6, 현재): dedup 도구 + 출처 상한 promoted

v5의 목표(출처 회복)를 프롬프트 양치기 대신 도구·상한으로. 도메인 중복을 도구 레벨에서 제거.
research/web_tool.py — 신규 web_search_deduped
def 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개만 '## 출처'에 인용
+  (전부 나열 금지, 대표성 있는 것만)."  # ← 질·상한
출처 2.3→4.7 회복(과다 없음) · grounded 100% · 지연 209→177s(최저). v5의 목표를 더 싸고 안정적으로 달성 → 현재 챔피언.
이 라인이 가르쳐준 구현 원칙: ① 측정 전에 하니스 버그(크래시·채점 오염)부터 잡아야 숫자가 신뢰된다. ② 능력(grounding)은 도구 한 번에 얻고, 이후는 전부 "어떻게 더 싸고 깔끔하게"의 튜닝. ③ 제약 추가(검색 강제·전부 인용)는 비용·부작용, 제약 제거/정제(루프 해제·dedup)가 dominant. ④ 한 번에 한 파일·한 변수만 — 그래야 어느 변경이 어느 metric을 움직였는지 귀속된다.
▸ 개선 메커니즘 총정리 — 근인 → 1변수 레버 → 측정 변화(DB) → 판정 → 원리
이 표 읽는 법 — 한 행(가로줄) = 실험 1건의 '4컷 만화'다: 왼쪽부터 ① 특이점/근인(무엇이 이상했고 왜 그랬나) → ② 1변수 레버(딱 한 곳만 바꿈) → ③ 측정 변화(숫자가 어떻게 움직였나, DB로 재계산) → ④ 부작용 → ⑤ 판정(채택=promoted / 기각=rejected) → ⑥ 원리(한 줄 교훈). 즉 "문제 발견 → 한 곳만 수정 → 측정 → 판정" 루프를 6칸으로 편 것.
표 안의 낯선 말 풀이:   reflect_on_tool_use = AutoGen 옵션(도구 결과를 모델이 한 번 더 곱씹게 할지) — 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 검색 ≠ 인용 — 다음 과제로 분리
🔧 도구·입력 레벨 수정 · 📝 프롬프트/구조 수정. 토큰·턴 수치는 crosscheck DB로 독립 재계산된 값.
★ 메타 패턴 (1변수 격리가 드러낸 귀속 가능한 사실): 이 라인의 promoted는 전부 도구/입력 레벨 수정(EXP-65/66/68)이고, rejected는 전부 프롬프트/구조 추가(EXP-67 URL 전부 나열)였다. 같은 패턴이 다른 라인에서도 반복됐다 — rejected: EXP-57 SOURCES 강제 · EXP-60 토폴로지 교체(pass@1 0.967→0.067 붕괴) · EXP-63 disambiguation 프롬프트(0.8→0.27 붕괴). 교훈: 프롬프트 개입은 비단조적이다 — "더 신중히" 지시가 working flow를 깬다. 신뢰할 개선은 surgical한 도구/검색 레벨 수정이다.
1변수 원칙은 '의지'가 아니라 '코드 구조'로 강제된다: 개입 1건 = 새 팀 모듈 + 새 team_id + 새 prompt_bundle_hash. 바꾸지 않는 부분(executor·selector·다른 프롬프트)은 베이스라인에서 그대로 import한다. 재현성 가드(AC9): 프롬프트가 한 글자라도 바뀌면 해시가 달라져 새 team_id가 강제되고, "같은 team_id + 다른 해시" 조합은 즉시 에러로 중단된다 → 변경 없는 재측정과 변경 있는 개입이 DB 레벨에서 구분된다. (단 오늘 날짜는 실행 맥락이라 prompt_bundle_hash에서 제외 — 날짜가 팀 정체성을 바꾸지는 않으므로.)
7 ★ 각 실험(EXP)이 사실 무엇인가 — 완전 해설 + 부연
EXP = '1변수 실험' 한 건. base(v2)에서 현재 챔피언(v6)까지, 한 번에 딱 한 곳만 바꿔가며 측정한 실제 기록이다. 각 실험은 ① 가설 → ② 무엇을 딱 하나 바꿨나(실제 코드/프롬프트 전문) → ③ 무슨 일이 일어났나(DB 측정) → ④ 왜 그렇게 됐나(근인) → ⑤ 판정·교훈 + 부연 해석으로 읽으면 된다. 실제 코드: src/agent_eval/teams/analyst_*.py · research/web_tool.py.
팀 계보 (한 화살표 = 1변수 실험 1건)
v2-deepbase · 웹 없음
EXP-65
+검색·날짜
v3-webgrounded 100%
단 토큰 97k
EXP-66
검색 다이어트
v4-lite−82% 비용
grounded 유지
EXP-67
전부 인용
v5-cited노이즈·역행
✗ rejected
v4-lite(비용 챔피언)
EXP-68
dedup 도구+상한
v6-dedup출처 회복·지연 최저
🏆 현재 챔피언
EXP-69
재측정(개입 0)
v6 재검증출처 3/9 flaky
= inconclusive
핵심: 능력(grounding)은 EXP-65 한 번에 얻고, 그 뒤 EXP-66·67·68은 전부 "같은 능력을 더 싸고 깔끔하게"의 튜닝이다. v5는 버려진 가지(rejected) — 그래서 현재 챔피언 v6는 v5가 아니라 v4에서 갈라져 나왔다(analyst_web_dedup.py가 v4의 critic·planner를 그대로 import).

EXP-65 · v2-deep → v3-web — 웹 검색을 처음 붙이다 promoted

한 줄: analyst가 기억(학습 컷오프 2024) 대신 실제 웹을 보게 만든 실험. 이 라인에서 유일하게 '능력' 자체를 획득한 개입.
가설
도구 없는 analyst는 학습 시점 일반론만 출력 → 2026 최신 사실을 못 댄다. analyst에 web_search 도구 + '오늘 날짜'를 주면 grounded(실검색 근거율)가 0→100%로 오른다.
1변수
analyst에 tools=[web_search] 추가 + 런타임 날짜 주입 + (검색 강제) 프롬프트. + 하니스: reflect_on_tool_use=False(빈 검색 크래시 방지), critic에 '출처 5개 미달 시 재검색 지목 반려', MaxMessage 12→18(재검색 루프 여유).
측정(DB)
grounded 0→100% · 검색 0→17.3회 · 출처 0→4.7 · 토큰 29,409→97,089 (3.3×) · 지연 280→602s · 턴 ~10.3.
근인
"무조건 한 턴에 5회+ 검색" + "critic이 출처 5개 미만이면 다시 검색하라고 반려" → 검색·재검색 루프가 토큰을 폭발시켰다. grounded가 오른 건 보너스가 아니라 도구가 생겨서다.
판정
promoted — 능력은 분명히 얻음. 단 비용 폭증이 다음 숙제로 남음.
바뀐 프롬프트 전문 (analyst·critic, before → after)
# ── 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}이다. '최근/올해'는 이 날짜 기준, 학습 시점이 아니다."
🔬 실제 로그 증거 — 과검색 + critic 재검색 루프 (DB eval_ph1.events · 배치 analysis_grounded_20260613)

analyst가 "한 턴에 최소 5회 병렬 검색" 지시를 유효 쿼리 6~9개 + 빈 쿼리 연타로 도구를 난사 → DuckDuckGo가 rate-limit(timed out) → 출처 없는 리포트 → critic이 '출처 5개 미만' 반려 → 재검색. 이 루프가 토큰을 3.3×로 밀어올린 실체다.

표의 'task 3개'가 무엇인지 (혼동 주의): 아래 3행은 서로 다른 분석 주제 3개다 — 한 프롬프트가 셋을 다 검색한 게 아니다. 각각 별도 run이고 자기 주제만 검색한다:
반도체: "반도체 업계 최근 동향을 분석해줘"  ·  ② EV 배터리: "전기차 배터리 산업의 최근 동향을 분석해줘"  ·  ③ AI 가속기: "데이터센터 AI 가속기(GPU) 시장의 최근 경쟁 구도를 분석해줘"
3개를 쓰는 이유 = N=3: 한 주제만 보면 우연일 수 있어, 성격이 다른 3개 평균이 더 믿을 만하다(그래서 §5·§6 헤드라인 수치 = 이 3개 평균). 아래 '반도체 궤적'의 검색어가 전부 반도체/HBM/파운드리인 게 "자기 주제만 검색"의 증거다.
task = 주제 (3개 모두 다른 프롬프트)유효 검색빈쿼리 연타이벤트 합critic 반려토큰지연
AI 가속기1919383회13143,275878s
EV 배터리1818361회521,214222s
반도체 (이 페이지 주제)1515302회13126,778704s
평균17.317.334.72.010.397,089601s

→ §5·§6의 헤드라인 "검색 17.3회 · 토큰 97k"가 바로 이 배치의 평균이다(검색 17.3 = 유효 쿼리 기준; 빈 쿼리 연타까지 세면 이벤트 ~34.7건/run, 지연 601s ≈ 602s, 턴 10.3).

반도체 run 궤적 전문 (13턴 · critic 반려 2회 · seq=event 순번)
v3-analyst-web · 질문: "반도체 업계 최근 동향을 분석해줘." · 13턴 · 126,778 tok · 704s

── 라운드 1 ─────────────────────────────────────
 s2  planner      소주제 분해 지시
 s4  analyst    🔍 "2026년 1분기 글로벌 반도체 매출 WSTS SIA 성장률"
 s5  analyst    🔍 "HBM4 2026 양산 양산일정 NVIDIA AMD"
 s6  analyst    🔍 "2026년 1분기 파운드리 점유율 TSMC 삼성전자 SMIC"
 s7  analyst    🔍 "2026년 미국 반도체 수출 통제 규제 GAA HBM"
 s8  analyst    🔍 "2026년 반도체 웨이퍼 리드타임 가격 원자재"
 s9  analyst    🔍 "2026년 반도체 시장 성장률 전망 투자은행 JP Morgan"
 s10–15 analyst 🔍 web_search ×6   ← 빈 쿼리로 도구 연타(헛호출)
 s16 analyst    ⚠️ (검색 실패: URLError: urlopen error timed out)   ← DDG rate-limit
 s18 synthesizer 💬 FINAL ANSWER (출처 없이 작성)
 s20 critic     ❌ 반려: "출처 URL 미포함 — '출처 URL 5개 이상'이 전혀 제공되지 않음"

── 라운드 2 (critic 반려 → 재검색 루프) ──────────────
 s22 planner      재지시
 s24 analyst    🔍 "SIA WSTS 2026 Q1 global semiconductor sales revenue..."
 s25 analyst    🔍 "TSMC 2nm GAA production yield 2026"
 s26 analyst    🔍 "SK Hynix HBM4 mass production 2026 NVIDIA"
 s27 analyst    🔍 "TrendForce 2026 Q1 foundry market share TSMC Samsung"
 s28–32 analyst 🔍 유효 검색 ×5 더 (대만 전력·수출규제·삼성 마진·JP모건·SOX 지수)
 s33–41 analyst 🔍 web_search ×9   ← 또 빈 쿼리 연타
 s42 analyst    ⚠️ (검색 실패: timed out)
 s44 synthesizer 💬 FINAL ANSWER: [검색 실패: 데이터 미확보]
 s46 critic     ❌ 반려 (또 출처 없음)

── 라운드 3 ─────────────────────────────────────
 s48 planner      재지시
 s50 analyst    💬 (검색 없이 기억으로 리포트 작성)
 s52 synthesizer 💬 FINAL ANSWER
 s54 critic     ✅ APPROVE   ← 13턴 만에 겨우 종료

합계: web_search 이벤트 30건(유효 15 + 빈쿼리 15) · critic 반려 2회 · 재검색 라운드 3

대조 — v6(EXP-68) 같은 "반도체" run: 5턴 · 검색 1라운드 · critic 반려 0회 · 21,498 tok · 143s로 끝. 같은 주제·같은 grounding을 토큰 1/6 · 지연 1/5에. (도구가 web_search_deduped라 연타·중복이 도구 단에서 차단되고, 완화 게이트라 재검색 루프가 없음)

부연 해석: "도구를 처음 쥐여줬더니 신이 나서 과하게 쓰는 신입"에 가깝다. 결정적 관찰은 — grounded가 100%가 된 건 '17번 검색해서'가 아니라 '검색 도구가 생겨서'다. 이 구분이 바로 다음 실험(EXP-66)의 씨앗이 된다.

EXP-66 · v3-web → v4-lite — 검색 다이어트: 능력은 두고 비용만 깎다 promoted

한 줄: 과검색·재검색 루프를 빼서 같은 grounding을 1/5 비용으로. "제약을 빼는 게 개선"의 대표 사례.
가설
토큰 폭발의 주범은 '턴당 5+회 강제 + critic 재검색 루프'다. 검색을 1라운드로 줄이고 critic을 완화하면 grounded는 유지되고 비용만 떨어진다.
1변수
analyst 프롬프트 "최소 5회+재검색" → "소주제당 1회씩 약 5회만, 재검색 루프 없이". critic "5개+재검색 지목" → "3개면 APPROVE, 한 번만 간단 반려". MaxMessage 18→10.
측정(DB)
토큰 97,089→17,805 (−82%) · 턴 10.3→5.0 · 지연 −65% · grounded 100% 그대로(검색 5.7회).
근인
grounding은 도구를 한 번이라도 쓰면 달성된다. 검색 횟수는 비용만 늘릴 뿐 grounding을 더 올리지 않는다 — 검색 17→5로 줄여도 grounded 100% 유지가 그 증거.
판정
promoted — 게다가 stale base(29k)보다도 싸짐(능력↑·비용↓ 동시 달성).
바뀐 프롬프트 전문 (analyst·critic, v3 → v4)
# ── 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)   # 루프 없으니 상한 낮춤
부연 해석: 신입이 일을 익힌 뒤 효율을 찾은 셈. 보통은 품질을 위해 제약을 더하려 하지만, 여기선 제약을 빼서 비용이 5분의 1이 됐다. "더 많이 검색=더 grounded"라는 직관이 틀렸음을 데이터로 보인 게 이 실험의 진짜 수확이다.

EXP-67 · v4-lite → v5-cited — '출처 전부 나열' 강제: 역행 rejected

한 줄: 출처 수를 늘리려 "전부 인용"을 강제했다가 중복·노이즈·비용으로 기각된, 교훈이 큰 실패.
가설
v4에서 출처가 4.7→2.3으로 준 게 아쉽다. synthesizer에 "검색 결과 URL을 하나도 빠뜨리지 말고 전부 나열"을 강제하면 출처 수가 회복될 것.
1변수
synthesizer 프롬프트만 변경(analyst·critic·도구 전부 불변) — "모든 URL 빠짐없이 인라인+## 출처에 나열(최소 4개)".
측정(DB)
출처 2.3→15.7(한 task는 37개 덤프) · 토큰 +61% · grounded 100→67%.
근인
'전부 나열'은 같은 매체 반복(trendforce 등) 중복·노이즈를 그대로 토해냄. 출처 리스트가 본문을 밀어내며 일부 run이 검색 없이 작성으로 흐르자 grounded까지 역행.
판정
rejected — 순개선 아님. team_after를 베이스라인으로 채택하지 않음(버려진 가지).
바뀐 프롬프트 전문 (synthesizer만, v4 → v5)
# ── synthesizer (이 줄 하나만 추가됨) ──
- (v4) 각 수치에 기준 시점. 맨 끝 '## 출처'에 인용 URL을 나열. 첫 줄 '기준 시점:' 명시.
+ (v5) 각 수치에 기준 시점. 검색 결과에 등장한 출처 URL은 하나도 빠뜨리지 말고 해당 사실
+  옆에 인라인으로 달고, 맨 끝 '## 출처'에 검색 결과의 모든 URL을 빠짐없이 나열한다
+  (최소 4개 이상).   ← 양으로 밀어붙임 = 노이즈·비용
부연 해석: "양으로 밀면 질이 죽는다." 그리고 더 중요한 메타 교훈 — 이건 프롬프트로 메트릭을 직접 밀어붙인 개입이었다. 이 라인에서 프롬프트 개입은 거의 다 실패했고, 진짜 해법은 다음 실험(EXP-68)의 도구 레벨 수정이었다. 즉 v5의 목표(출처 회복)는 옳았고 수단(프롬프트 강제)이 틀렸다.

EXP-68 · v4-lite → v6-dedup — dedup 도구 + 출처 상한: 현재 챔피언 🏆 promoted

한 줄: v5의 목표(출처 회복)를 프롬프트 양치기 대신 '도구 dedupe + 상한'으로 더 싸고 안정적으로 달성.
가설
출처가 부푸는 진짜 원인은 같은 도메인 반복이다. 검색 도구 단에서 도메인 중복을 제거(상위 4 고유)하고 synthesizer는 핵심 5~8개만 인용하면, 비용은 v4 수준 유지하며 출처가 건강한 범위로 회복된다.
1변수
① 새 도구 web_search_deduped(도메인당 1개·상위 4) ② analyst 도구 교체 ③ synthesizer "중복 제거 후 핵심 5~8개만". critic·planner·날짜함수는 v4에서 그대로 import — 1변수 import 규율의 실증.
측정(DB)
출처 2.3→4.7 회복(과다 없음) · 토큰 21,285 · 지연 177s(전 버전 최저) · grounded 100%.
근인
노이즈를 '생성 후 프롬프트로 거르기'(v5) 대신 '입력 단에서 애초에 안 만들기'(도구 dedup)가 깔끔하고 안정적. 같은 목표를 다른 레벨에서 풀었다.
판정
promoted → 현재 베이스라인 챔피언.
실제 코드 전문 (web_search_deduped 도구 + synthesizer)
# ── 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개만 '## 출처'에 인용한다
+  (전부 나열 금지, 대표성 있는 것만).
부연 해석: "정리는 손으로 치우지 말고 애초에 안 어지르게." v5와 v6은 같은 목표·다른 수단이다 — 그리고 도구/입력 레벨 수정(v6)이 프롬프트 강제(v5)를 이겼다. 이 한 쌍이 이 프로젝트 전체의 핵심 교훈을 압축한다.

EXP-69 · v6 재측정 (개입 0) — "출처 4.7은 진짜였나?" inconclusive

한 줄: 챔피언의 출처 수가 안정적인지 N을 늘려 재측정했더니 매우 불안정으로 판명 — 평가 시스템이 자기 결론을 검증하는 자정 작용.
가설
EXP-68의 '출처 4.7'이 재현되는 안정적 값인가? (개선 가설이 아니라 검증 가설)
1변수
없음(개입 0). 같은 v6를 9 run으로 재측정만.
측정(DB)
web_search는 8/9가 썼으나 report_sources≥1은 3/9뿐. "4.7은 N=3 행운", 실측 분포는 "대개 0, 가끔 분출".
근인
grounding(검색 행위)은 events로 견고하지만, synthesizer가 그 URL을 리포트로 옮기는지는 비결정적(LLM 변덕).
판정
inconclusive — report_sources를 KPI로 신뢰 불가. 다음 과제(클레임 단위 교차검증·인용 강제)로 분리.
부연 해석: "측정값 하나가 좋아 보여도 N을 늘리면 무너질 수 있다 — 단발 dramatic은 노이즈." 이 실험이 중요한 이유는, 좋은 평가 시스템은 자기가 'promoted'라고 적은 숫자조차 다시 의심한다는 것을 보여주기 때문이다. (그래서 이 페이지 §5의 '출처' 수치엔 정직한 ⚠️ 주의가 붙어 있다.)
5개 실험을 관통하는 한 문장: 능력은 도구 한 번(EXP-65)에 얻고, 이후는 전부 "어떻게 더 싸고 깔끔하게"의 튜닝이다 — 그리고 그 튜닝에서 이긴 건 전부 도구/입력 레벨(EXP-66 루프 제거, EXP-68 dedup 도구)이고 진 건 프롬프트로 메트릭을 직접 민 것(EXP-67 전부 인용)이며, 좋아 보이는 숫자조차 재측정(EXP-69)으로 의심한다.
8 team config — AutoGen 네이티브 vs 우리 스냅샷 + 버전 변경점 + 전체 파일
같은 팀을 두 포맷으로: AutoGen 네이티브(team.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-deepv6-analyst-web · 기본 ↔ 개선 config 완전 diff (라인 단위) · 무엇이 같고 무엇이 바뀌었나
동일(불변): 토폴로지(RoundRobin) · 에이전트 4명과 순서 · LLM 설정(glm-4.7 · seed 42 · temperature 0.2) · planner·synthesizer·critic의 도구 없음.
🔧 변경(개선): ① analyst 도구([] → web_search_deduped) · ② 4개 에이전트 프롬프트(sha 전부 변경) · ③ 날짜 주입 · ④ synthesizer 출처 상한 · ⑤ critic 완화 게이트.
이 diff 읽는 법 — 줄 맨 앞 부호가 전부다:   회색 줄 = 양쪽 동일(안 바뀜) ·   빨강 = base에만 있던 것(제거/이전 버전) ·  + 초록 = 개선에서 새로 들어간 것 ·  회색 # … = 설명 주석.
sha
프롬프트의 '지문'(내용 해시값). 글자 하나만 달라도 sha가 바뀐다 → sha가 다르면 프롬프트 내용이 바뀐 것. 4개 에이전트 sha가 전부 달라진 게 "프롬프트를 전부 손댔다"의 증거.
tools: []
그 에이전트가 쓸 수 있는 도구 목록. [] = 없음(빈손). 두 팀 통틀어 analyst만 검색 도구를 갖는다(나머지 3명은 양쪽 다 빈손).
team_type /
participants
팀 작동 방식(RoundRobin = 정해진 순서로 돌아가며 발언) / 에이전트 명단·순서. 둘 다 두 팀 동일.
model_client
어떤 LLM을 어떤 설정(seed·temperature)으로 쓰는지. 두 팀 완전 동일 = LLM은 안 건드렸다는 뜻(1변수 원칙의 핵심).
reflect_on_tool_use:
False
도구 결과를 모델이 다시 곱씹을지 여부. False라야 빈 검색에 안 죽는다. base에선 무의미(도구가 없어서), 개선 analyst에선 load-bearing(없으면 크래시하는 핵심 설정).
… (말줄임)
프롬프트가 길어 핵심만 보이고 줄였다는 표시. 전문(全文)은 아래 버전별 카드의 'AutoGen 네이티브' 펼치기에서.
# ══ 토폴로지 · 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. 그 외 한 번만 간단히 반려(재검색 루프 안 끔).  # ← 완화 게이트
↑ 의미 단위 diff (핵심 변경만). 각 버전의 완전한 JSON(우리 스냅샷 config_full + AutoGen 네이티브 dump_component())은 아래 버전별 카드에서 펼쳐 볼 수 있다.
📄 전체 config 파일 (생성된 그대로 · 내려받기) · AutoGen Studio로 그대로 임포트/load_component() 재로드
아래 버전별 카드의 'AutoGen 네이티브'를 펼치면 각 팀의 완전한 JSON 전문(全文)이 보인다. 그 파일은 코드의 한 명령으로 생성된다 — 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는 마스킹됨.
📜 v2 ↔ v6 전체 네이티브 JSON 직접 표기 · 개선 영향 부분 강조 · 다운로드 없이 페이지에서 바로
초록 = v6에서 새로 들어가거나 바뀐 줄(=결과에 개선 영향을 준 부분) · 빨강 = v2의 이전 줄 · 나머지(회색) = 두 팀 완전 동일(model_client·토폴로지·구조). 노란 주석이 "왜 개선인지"를 표시. v6에서 강조된 줄은 26개뿐 — 그게 v2 대비 바뀐 전부다.
🟢 v6-analyst-web · 전체 네이티브 JSON (319줄, 개선 줄 강조)
{  "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  }}
🔴 v2-analyst-deep · 전체 네이티브 JSON (303줄, v6와 다른 줄 강조)
{  "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  }}
이 JSON이 곧 §8 상단 다운로드 버튼이 주는 파일이다(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 프롬프트 변경
🔧 도구 변경 · 📝 프롬프트 변경 · 📅 날짜맥락 주입. (날짜 자체는 prompt_bundle_hash에서 제외 — 실행맥락이라 팀 정체성 아님)
v2-analyst-deep · base · 웹 없음
우리 관측 스냅샷 · config_full
{
    "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
}
AutoGen 네이티브 · team.dump_component()
{
  "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 · EXP-65 · 웹검색+날짜
Δ v2-analyst-deep 대비 · 📝 planner 프롬프트 변경 · 📅 planner 날짜맥락 주입 · 🔧 analyst 도구 [없음] → [web_search] · 📝 analyst 프롬프트 변경 · 📅 analyst 날짜맥락 주입 · 📝 synthesizer 프롬프트 변경 · 📅 synthesizer 날짜맥락 주입 · 📝 critic 프롬프트 변경 · 📅 critic 날짜맥락 주입
우리 관측 스냅샷 · config_full
{
    "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
}
AutoGen 네이티브 · team.dump_component()
{
  "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 · EXP-66 · 검색 다이어트
Δ v3-analyst-web 대비 · 📝 planner 프롬프트 변경 · 📝 analyst 프롬프트 변경 · 📝 synthesizer 프롬프트 변경 · 📝 critic 프롬프트 변경
우리 관측 스냅샷 · config_full
{
    "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
}
AutoGen 네이티브 · team.dump_component()
{
  "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 · EXP-67 · 전부 인용 (rejected)
Δ v4-analyst-web 대비 · 📝 synthesizer 프롬프트 변경
우리 관측 스냅샷 · config_full
{
    "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
}
AutoGen 네이티브 · team.dump_component()
{
  "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 · EXP-68 · dedup+상한 (현재)
Δ v4-analyst-web 대비 · 🔧 analyst 도구 [web_search] → [web_search_deduped] · 📝 analyst 프롬프트 변경 · 📝 synthesizer 프롬프트 변경
우리 관측 스냅샷 · config_full
{
    "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
}
AutoGen 네이티브 · team.dump_component()
{
  "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
  }
}
9 일반화 — 같은 개선을 5개 주제에 (어디서 돕고, 어디서 안 돕나)

반도체 한 주제만으론 "운"일 수 있다. 같은 base↔개선 팀을 성격이 다른 5개 주제에 돌려 개선의 경계를 본 결과(총 10 run). 웹 근거의 가치는 만능이 아니라 '최신 사실' 주제에 한정됨이 드러난다.

주제 (자극하는 차원)base 검색/출처개선 검색/출처경계 판정
주요 LLM 경쟁 (2026)
post-cutoff 신제품
0 / 017 / 6✅ 명확한 우위 — 본문+출처 모두 성공
엔비디아 실적 (2026)
시점 민감 실적
0 / 05 / 0🟡 본문 grounded·인용 누락(flaky)
미 대중국 규제 (2026)
정책·규제 시점성
0 / 06 / 0🟡 본문 grounded·인용 누락
한국 반도체 업황 (2026)
한국어·로컬 출처
0 / 06 / 0🛡️ 안전성 우위 — 정직한 거부
동적 계획법 개념
timeless 개념 (대조군)
0 / 05 / 0⚖️ 무승부 — 검색이 무용
검색은 events(web_search 호출수), 출처는 리포트 '## 출처' 파싱. 전체 리포트 원문은 리서치 비교 갤러리에서.
경계가 가르쳐준 것 (정직하게): ① 검색 grounding은 항상 일어난다(base 0회 vs 개선 5~17회).   ② 본문 최신성은 robust — 최신-사실 주제(LLM·엔비디아·규제)에서 개선이 '2026' 언급 2~7배·실제 dated 수치.   ③ 그러나 출처 URL 인용은 flaky — synthesizer가 종종 '## 출처'를 누락(5주제 중 LLM·반도체만 성공). 이는 위 EXP-69가 잡아낸 바로 그 비결정성이다.   ④ 한국 주제: 검색이 빈약하자 개선은 정직하게 '데이터 부족 — 작성 불가'로 거부, base는 출처 0으로 한국 통계를 환각으로 지어냄(환각보다 거부가 안전).   ⑤ DP(대조군): timeless 개념엔 검색이 무용 → 무승부(의도대로 '검색이 안 돕는 경계'를 확인).   → 즉 개선의 가치는 '최신 사실'에 한정되고, 출처 인용 신뢰성은 다음 개선 과제다.
10 ★ 사내에서 이 베스트 프랙티스를 재현하는 법 (가이드)
전제: eval_ph1 코드(어제 버전)는 이미 사내에 전송됨. 아래는 그 코드로 같은 루프를 도는 절차다. 핵심은 도구가 아니라 규율 — "한 번에 1변수 → 측정 → 판정 → 원장 기록 → promoted면 새 베이스라인" 루프를 반복하면 base가 챔피언이 된다.
0
전제 셋업 (한 번)

LLM 키: .envEVAL_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 직렬화에도 안전.

1
DB·평가셋 준비
$ 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만 새로 쓴다.

2
베이스라인 측정 (= 우리의 v2-deep)
$ 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 수치는 믿지 않는다.

3
특이점(anomaly) 찾기 → 다음 1변수 후보

report에서 metric 이상치를 본다. 우리 경우엔 grounded%=0 · 출처=0(events에 web_search 0건) → "리포트에 출처가 한 개도 없다"가 특이점이었다. "무엇이 비정상인가"가 곧 다음에 바꿀 한 곳이다.

4
1변수 개입 (코드 구조로 강제)

새 팀 모듈 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');
5
개입 측정 + paired(matched A/B) 비교
$ 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만 신뢰.

6
판정 → 원장 UPDATE
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 마커를 자동으로 찍는다.

7
반복 — 새 특이점이 다음 실험

EXP-65의 특이점(토큰 97k) → EXP-66(검색 다이어트) → 출처 감소 → EXP-67(실패) → EXP-68(dedup) … 루프. "제약 추가는 비용·부작용, 제약 제거/도구 레벨 수정이 dominant" 패턴을 활용하면 수렴이 빠르다.

8
산출물 — JSON 추출 & 대시보드
$ python cli.py export-team --team v6-analyst-web --out v6.json   # Studio 임포트용(키 스크럽)

Grafana 패널(experiment-metrics·analysis-grounding·team-config)로 원장·메트릭·config를 한 화면에. 이 비교 페이지처럼 정적 대시보드로도 공유 가능.

가드레일 7계명 (이 라인이 비싸게 배운 것 — 사내에 그대로 적용): grounding은 events(도구 호출수)로 재라 — LLM 자기보고 절대 금지.   한 번에 1변수·1파일(나머지는 import) — 안 그러면 어느 변경이 효과인지 귀속 불가.   paired(matched A/B)만 신뢰 — raw 팀 평균은 난이도 교란.   측정 전 하니스 버그부터(채점 오염·빈 검색 크래시) — 숫자가 거짓이면 다 거짓.   N≥3 · pass^k — 단발 dramatic은 노이즈(EXP-69가 증명).   제약 추가보다 제거/도구 레벨 수정이 이긴다(프롬프트 강제는 비단조적).   메트릭을 좇기 전 그 메트릭이 맞는지 먼저 검증(유령 메트릭 주의).
▸ 재현 시 자주 묻는 것 (FAQ) — "config만 들고 가면 똑같이 나오나?"
Q1. v2→v6에서 개선된 건 결국 team config인가? (모델·인프라는 그대로?)
맞다 — 개선은 100% team config 안에 있다(프롬프트 + 도구 + 게이트). 모델(glm-4.7)·seed·temperature·실행환경 전부 동일. 단 용어 주의: config_full(우리 스냅샷)은 사람이 읽는 관측용이라 LLM 설정이 없고 로드 불가다. 실제로 돌리는 파일은 AutoGen 네이티브 dump_component() JSON(model_client·도구 source_code·reflect 플래그까지 포함, load_component()/Studio 임포트 가능). → §8 다운로드의 v6-analyst-web.json이 그것.
Q2. 그 JSON을 AutoGen Studio에 올려 돌리면 이 페이지의 메트릭과 같은 결과가 나오나?
메트릭 '레벨'은 재현되지만, '정확한 숫자'는 안 나온다. 구분이 중요하다:
✅ 재현됨 (구조적 차이)
v2 → grounded 0%·출처 0·stale / v6 → grounded ~100%·검색 1라운드·인용·v3식 과검색보다 쌈·빠름. "도구 유무/완화 게이트/dedup"이 만드는 질적 패턴은 그대로.
❌ 똑같진 않음 (정확한 값)
① 웹이 라이브·외부 → 다른 날 = 다른 기사 = 다른 수치(546억$·72.3%는 2026-06-13 기사). ② 비결정성(temp=0이어도 흔들림 — EXP-69가 증명). ③ 헤드라인은 N=3 평균(단일 run은 출렁). ④ 검색 백엔드(Firecrawl vs DDG)에 따라 큰 차이.
돌리기 전 채워야 할 것: 네이티브 JSON의 api_key(마스킹됨)를 본인 키로 채우고 · 검색 백엔드 도달 가능하게(Firecrawl 키 or DDG; 폐쇄망이면 web_search 본문을 사내 검색/RAG로 교체) · AutoGen Studio 버전이 컴포넌트 스키마와 호환.
한 줄 정리
개선은 전부 team config에 담겨 있고(모델·인프라 불변), 네이티브 JSON을 Studio에 올리면 "grounded·저비용·인용"이라는 메트릭 레벨은 재현된다. 하지만 웹이 라이브이고 측정이 비결정적이라 정확한 숫자는 재현되지 않는다 — 통제 비교는 같은 시점·같은 검색 백엔드·N≥3 평균에서만 의미가 있다.
Q3. 그럼 멀티에이전트 개선은 결국 team config 속 '시스템 프롬프트' 개선이 주요 항목인가? 절대적 포션인가?
아니다 — 오히려 이 프로젝트의 핵심 발견은 그 반대였다.
  • team config는 프롬프트 담지 않는다: 프롬프트 + 도구 + 토폴로지 + 종료조건 + 에이전트 플래그(reflect_on_tool_use 등) + 모델설정. "바꿀 거리"의 대부분이 여기 있는 건 맞지만, 그 안에서 프롬프트는 한 조각일 뿐이다.
  • 이 라인의 메타패턴: 채택(promoted)은 전부 도구/입력 레벨(EXP-65/66/68), 기각(rejected)은 전부 프롬프트/구조 추가(EXP-67·57·63). 즉 승자는 도구 쪽이었다.
  • 가장 큰 단일 점프(grounded 0→100%)는 프롬프트가 아니라 도구(web_search) 하나가 만들었다.
  • 프롬프트는 비단조적·취약: EXP-63은 "더 신중히" 프롬프트 한 줄로 pass@1 0.8→0.27 붕괴. 지시 강화가 working flow를 깬다.
일반 원리 — 최고 레버는 '병목'에 따라 다르다:
병목 (무엇이 문제인가)가장 높은 레버이 프로젝트 증거
필요한 정보/능력이 없다도구 · 검색 · groundingweb_search → grounded 0→100% (최대 효과)
정보는 있는데 잘못 쓴다프롬프트 · 역할 설계여기선 대개 기각(비단조적)
협업이 깨진다토폴로지 · 종료조건 · 라우팅selector→round_robin 한 줄로 0.967→0.067 붕괴(load-bearing)
비용이 문제다제약 제거 · dedup · 상한과검색 해제 −82% · 도구 dedupe
한 줄로: "무엇을 시킬까(프롬프트)"보다 "무엇을 할 수 있게 할까(도구·검색)"와 "어떻게 협업시킬까(토폴로지)"가 더 높고 신뢰할 만한 레버였다. 단 이건 이 regime(이미 높은 베이스라인 + grounding이 정답을 좌우하는 과제)의 결론 — 모델이 도구는 충분한데 형식/톤이 문제면 프롬프트가 지배적일 수 있다. (§6 '메타 패턴' 참고)
B 심층 기술 부록 — 메트릭·DB·코드·AutoGen 내부 (엔지니어용)
여기서부터는 "이 베스트 프랙티스가 시스템 안에서 실제로 어떻게 측정·기록·구동되는가"를 끝까지 파고든다. ① pg_db가 모으는 모든 메트릭을 EXP별 수치 표로, ② 각 단계 개선 Action의 선정 근거·사람 판단법·OSS 도구, ③ 전체 DB, ④ 전체 코드, ⑤ 코드가 AutoGen 네이티브 컴포넌트를 만들고 도는 법, ⑥ 메트릭이 코드를 거쳐 DB에 꽂히는 전 경로. 위 §1–§10이 "무엇/왜"라면, 여기는 "정확히 어떻게".
11 ★ pg_db가 수집하는 모든 메트릭 × 각 EXP 실측치 (순서대로)

무엇을 보나: 각 실험(batch)이 돌 때 DB가 자동으로 채운 모든 메트릭을 실험 순서대로 나열했다. 메트릭은 세 곳에서 온다 — runs(실행 1건의 결과/비용), research_grounding(인용 채점, 멀티홉 QA용), v_analysis_grounded(개방형 분석의 검색 근거 뷰). 수치는 모두 실제 DB 쿼리 결과(2026-06 기준 배치).

A. 리서치 라더 — 멀티홉 사실 QA (사내 doc corpus, exact-match 채점)
batch / teamEXP·판정npass@1
(success)
format
_valid
total
_turns
total
_tokens
latency
(s)
cite
_f1
n
_cited
cite_pcite_rsources
_present
research_base
v1-research-pipeline
기준300.9670.9676.96,528233.50.9671.90.9670.9670.97
research_grounded
v2-research-grounded
EXP-57
rejected
300.9671.0006.95,864313.60.9671.90.9670.9670.97
research_fast
v2-research-fast
EXP-60
rejected
300.0670.2004.67,962193.70.0830.20.1000.0780.10
research_hard_base
v1-research-pipeline
hard 기준300.8000.8007.57,464406.80.8001.70.8000.8000.80
research_hard_disambig
v2-research-disambig
EXP-63
rejected
300.2670.6676.114,567502.90.3000.90.3000.3000.43
research_hard_cleanret
v2-research-cleanret
EXP-64
promoted
300.8670.9007.06,761251.90.8671.80.8670.8670.87

읽는 법: cite_f1=인용한 출처(doc_id)가 정답 출처와 맞는 정도(P·R 조화평균). EXP-64만 도구 레벨 개입(검색이 비활성 decoy 강등) — pass@1·cite_f1·지연·토큰이 동시에 개선된 유일한 dominant 케이스. EXP-63은 같은 목표를 프롬프트로 시도했다가 0.80→0.27 붕괴.

B. 분석 라더 — 개방형 장문 리포트 (웹 grounding, 구조 채점)
batch / teamEXP·판정nsuccess
(=format)
total
_turns
total
_tokens
latency
(s)
grounded%
호출기준
grounded%
성공기준
search
/run
search_ok
/run
report
_sources
analysis_stale
v2-analyst-deep
base31.005.029,409279.700000
analysis_grounded
v3-analyst-web
EXP-65
promoted
31.0010.397,089601.6100017.304.7
analysis_lite
v4-analyst-web
EXP-66
promoted
31.005.017,805209.410005.702.3
analysis_cited
v5-analyst-web
EXP-67
rejected
31.006.328,626306.36705.0015.7
analysis_dedup
v6-analyst-web
EXP-68
promoted
31.005.021,285177.11001004.04.04.7
analysis_v6_k3
v6-analyst-web
EXP-69
inconclusive
90.8895.019,333195.8335.01.89cited 3/9
⚠ 두 개의 grounded% — 가장 중요한 메트릭 함정: 분석 라더에는 grounded%가 두 정의로 적혀 있다. ① 호출 기준(원장 EXP 기록): web_search1회라도 호출했나 → 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%)은 호출 기준임을 명시한다.
C. 운영·신뢰성 지표 — mv_batch_summary · mv_passk · mv_tool_summary (전부 수집됨)

pass@1·토큰·지연 외에 형식오류율·무한루프율·성공당토큰·p95지연·pass@k/pass^k·도구에러도 모두 집계된다(materialized view, 배치 종료 시 REFRESH). 아래는 같은 12개 배치의 실측.

batchEXP·판정format
_invalid
loop
_rate
tokens
/success
p95
지연(s)
pass@kpass^kk도구 호출
(에러)
research_base기준0.0330.0006,753389.60.9670.9671search_corpus 60 (0)
research_grounded57 rej0.0000.0006,067611.80.9670.9671search_corpus 60 (0)
research_fast60 rej0.8000.100119,429435.00.0670.0671search_corpus 36 (0)
research_hard_basehard 기준0.2000.1009,330632.10.8000.8001search_corpus 74 (0)
research_hard_disambig63 rej0.3330.10054,628864.10.2670.2671search_corpus 61 (0)
research_hard_cleanret64 prom0.1000.0007,802381.10.8670.8671search_corpus 62 (0)
analysis_stalebase0.0000.00029,409296.41.0001.0001(도구 없음)
analysis_grounded65 prom0.0000.00097,089861.01.0001.0001web_search 52 (0)
analysis_lite66 prom0.0000.00017,805246.91.0001.0001web_search 17 (0)
analysis_cited67 rej0.0000.00028,626392.91.0001.0001web_search 15 (0)
analysis_dedup68 prom0.0000.00021,285237.31.0001.0001web_search_deduped 12 (0)
analysis_v6_k369 inc0.1110.00021,749249.61.0000.6673web_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 등).

이 표가 말하는 것 (메트릭이 실패를 어떻게 즉시 포착했나): ① tokens_per_success가 rejected를 정확히 때린다 — EXP-60 119k·EXP-63 55k(정답이 적어 분모↓+토큰↑로 폭증), EXP-65 97k(능력↑ 비용 트레이드오프). ② format_invalid_rate: EXP-60 0.80(답 형식 붕괴)·EXP-63 0.33(planner가 템플릿텍스트 출력). ③ loop_rate: 배치평균 0.10이나 hop2 레벨에선 0.30(EXP-60·63·hard_base) — 멀티홉이 깨지면 max_messages까지 헛돈다. ④ 도구 에러는 전부 0 — 그런데 이게 함정: 검색 빈손은 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.2EXP-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)채우는 코드의미
successruns.successscorer.score() → runner UPDATE정답(GAIA/research) 또는 형식유효(analysis)
format_validruns.format_validscorer.score()'FINAL ANSWER:' 패턴 존재
total_turnsruns.total_turnslogger: agent_message 카운트에이전트 발화 수
total/input/output_tokensruns.total_tokenslogger: models_usage 합산LLM 비용 ≈ 토큰
latency_msruns.latency_mslogger: 첫↔끝 event ts 차run 1회 소요시간
terminated_byruns.terminated_bylogger stop_reason → runnerapprove / max_messages / error
(이벤트 전체)events.* (seq별 1행)TraceLogger.log_stream()메시지·도구호출·selector 결정 원장
cite_f1 / precision / recallresearch_grounding.cite_*research/grounding.score_batch()인용 출처 vs 정답 출처 일치도
n_cited / n_gold / sources_presentresearch_grounding.*research/scorer.extract_sources()인용 개수·정답 개수·인용 유무
grounded (성공기준)v_analysis_grounded (뷰)events 쿼리 (저장 아님)실결과 반환 검색 ≥1
web_search/run, search_okevents 집계 / 뷰events tool_name LIKE '%search%'검색 시도·성공 횟수
pass@1, format_invalid_rate, loop_rate, avg_turns, avg_tokens, tokens_per_success, p95_latencymv_batch_summary002_views.sql (배치 종료 REFRESH)배치×팀×레벨(hop) 집계 — 슬라이스별 격차가 여기서 보인다
pass@k (pass_any) / pass^k (pass_all)mv_passk002_views.sqltask당 k회 중 1회↑ / k회 전부 성공 = 반복 신뢰성
calls / errors / avg_latency (도구별)mv_tool_summary002_views.sql도구별 호출·에러·지연. 빈손 검색은 에러 아님(tool_result)
12 ★ 각 단계 개선 Action — 선정 방법 · 근거 · 사람이 직접 판단하는 법 · OSS 도구

이 절의 목적: 각 EXP에서 왜 그 개입을 골랐고(method), 무엇이 근거였으며(evidence), 사람이 이 판단을 자동화 없이 직접 하려면 어떤 절차를 밟고, 어떤 오픈소스/툴을 어떻게 쓰는지를 단계마다 적었다. (도구의 근거등급: ★공식 = 해당 프레임워크 1급 기능 / ◆de-facto = 업계 널리 쓰는 OSS / ▷내추론 = 이 프로젝트의 적용 제안.)

모든 단계를 관통하는 방법론(1변수 측정 루프): 특이점 발견 → 가설 1개 → 딱 한 곳 개입(새 team_id 강제) → 같은 task로 paired 측정 → 판정 → 원장 기록 → promoted면 새 베이스라인. 이 루프 자체가 "베스트 프랙티스"의 본체다.
EXP-57 · 리서치 그라운딩 강제 rejected (v1 → v2-research-grounded)
선정한 방법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을 박아 회귀 검출)가 먼저다.
EXP-60 · RoundRobin 토폴로지 (효율) rejected (selector → round_robin)
선정한 방법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 테이블)나 트레이싱.
EXP-63 vs 64 · decoy 대응 (프롬프트 vs 도구) 63 rejected 64 promoted

같은 약점(이름충돌 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 같은 역효과를 사전 검출.
EXP-65 · 웹 grounding 추가 (★핵심 점프) promoted (v2-deep → v3-web)
선정한 방법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로 교체(▷).
EXP-66 · 검색 다이어트 (비용 절감) promoted (v3 → v4-lite)
선정한 방법검색 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 ★공식)으로 강제.
EXP-67 · 출처 전부 나열 강제 rejected (v4 → v5-cited)
선정한 방법synthesizer 프롬프트만 '검색 결과 URL 전부 나열' 강화(인용 회복 목표).
근거v4의 출처수 2.3 하락이 synthesizer 누락 탓이라는 가설.
실제 결과출처 2.3→15.7로 과다(한 task 37개 덤프=중복·노이즈). 토큰 +61%·지연 +46% 역행(v4가 줄인 바로 그 메트릭). 순개선 아님.
사람이 직접 하려면인용은 '양(전부)'이 아니라 '질(dedupe+상한)'. 프롬프트 '추가'(전부 인용)는 비단조적 부작용 — 양치기 금지.
OSS·툴출처 dedupe·정규화: URL canonicalization, 도메인 클러스터링(▷). 인용 품질 평가는 RAGAS context-precision(◆)로 "관련 없는 인용" 패널티.
EXP-68 · 도구 dedupe + 출처 상한 (★현 챔피언) promoted (v4 → v6-dedup)
선정한 방법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 프롬프트+후처리 이중 가드(▷).
EXP-69 · 안정성 재측정 (개입 없음) inconclusive (v6 × attempts=3)
선정한 방법개입 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 리포트(▷).
13 전체 DB 구조 — 주요 테이블과 연계 (스키마 SSOT)

설계 철학(SPEC): 스키마가 SSOT(ORM 금지, migrations/*.sql로만 변경). runs=실행 1회의 조건·결과, events=run 내 모든 이벤트(seq 순), experiments=개선 원장. 불변식은 애플리케이션이 아니라 CHECK 제약으로 강제한다.

연계도 (FK)
teams
팀 정체·config
runs
실행 1회
tasks
평가 문제
experiments
개선 원장
runs
events
seq별 이벤트 (CASCADE)
research_grounding
인용 채점 1:1

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_byUNIQUE(batch,task,attempt). completed→success NOT NULL. temp/seed 기록 강제(비교 가능성)
eventsrun 내 전 이벤트 원장(run_id,seq) PK, message_type, agent_name, content, tool_name, tool_args/result(JSONB), selector_reason, tokens, tstool_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_presentgrounding.score_batch()가 upsert. 일반 뷰라 REFRESH 불필요
experiments개선 원장(ledger)exp_id(PK), hypothesis, intervention, team_before/after, batch_before/after, verdict, key_evidence(JSONB), lessonverdict∈{promoted,rejected,inconclusive}. promoted의 team_after=새 베이스라인. Grafana annotation 소스
message_types / failure_stages / root_causeslookupcode(PK)events/runs FK 대상. failure_stage/root_cause는 스키마만(자동 라벨링 scope-out)
claims / evidence2차용 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, groundedevents 실시간 쿼리(저장 아님). 010 마이그레이션
v_team_config / v_team_orchestrationconfig_full 평탄화system_prompt, tools, selector_prompt, terminationgit 없이 팀 구성 확인(004)
로깅은 평가를 죽이지 않는다: events INSERT 실패는 경고 후 계속(missed_inserts 카운트만). 배치 중단 사유는 DB 연결 소실뿐. 비밀정보 마스킹: tool_args의 password/token/secret/api_key/authorization 키 값은 저장 전 ***(logger 단일 지점).
14 전체 코드 구조 — 모듈과 호출 관계
호출 흐름 (1 run)
cli.py
typer
runner
run_batch
registry
build_team
teams/*
AutoGen 팀
team.run_stream
이벤트
tracing/logger
+ mapping
events 테이블
scorer
success
runs UPDATE
research/grounding
score_batch
research_grounding
config_capture
teams.config_full
모듈책임주요 심볼 → 호출
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.pyteam_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이벤트→DBTraceLogger.log_stream → _build_*_record → _insert_event → RunResult(집계)
tracing/mapping.py이벤트 타입→message_typeget_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.pyasyncpg 풀·마이그레이션 / 설정create_pool, refresh_views / pydantic-settings(env)

연계 핵심: 개선은 teams/*.py 한 파일에 격리된다(나머지는 import). runner·logger·scorer·DB는 팀과 무관하게 불변 — 그래서 "팀만 바꿔 측정"이 성립한다. web_tool은 어느 팀이 쓰든 동일 구현(impl_sha로 DB에서 구분).

15 코드가 AutoGen 네이티브 컴포넌트를 어떻게 만들고 구동하나

핵심: 우리 build_team()은 AutoGen의 1급 객체 — OpenAIChatCompletionClient, AssistantAgent, FunctionTool(자동), SelectorGroupChat/RoundRobinGroupChat, *Termination — 를 조립할 뿐이다. 실행은 team.run_stream()이 이벤트를 흘리고, 직렬화는 dump_component()가 한다.

① 모델 클라이언트 (runner.make_openai_model_client)
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_web 예)
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 | BOrTerminationCondition(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 가능)
네이티브(dump_component) vs 스냅샷(config_capture): 네이티브 JSONload_component()·AutoGen Studio 임포트가 되는 실행 가능 표현(§8 다운로드). 반면 config_full(우리 config_capture.py)은 사람이 읽는 관측용 스냅샷 — 프롬프트 전문·도구 impl_sha·종료조건만 담고 model_client가 없어 로드 불가. 둘을 혼동하지 말 것(§10 FAQ Q1).
16 ★ 각 메트릭이 코드를 거쳐 DB에 꽂히는 전 경로 (상세 도식)

한 문장: run_stream()이 흘린 AutoGen 이벤트를 TraceLogger가 한 건씩 events로 INSERT하면서 토큰·턴·지연을 누적(RunResult)하고, 끝나면 runner가 scorer를 돌려 runs를 UPDATE한다. 인용·grounding은 별도 패스다.

메인 경로 (실시간, run 진행 중)
AutoGen event
run_stream
mapping
타입 판별
_build_*_record
마스킹·절단
_insert_event
events (seq별)
models_usage
토큰 누적
RunResult
집계
scorer.score
success
runs UPDATE
메트릭출처 이벤트/필드코드 경유도착 (table.column)
total/input/output_tokensTextMessage·ToolCallRequestEvent의 models_usage_extract_tokens → total_*_tokens 누적 → RunResultruns.total_tokens 외 (runner UPDATE)
total_turnsagent_message 이벤트log_stream: msg_type==agent_message마다 +1runs.total_turns
latency_ms (run)첫·마지막 event 수신 ts(last_ts − first_ts)·1000runs.latency_ms
latency_ms (이벤트별)직전 event ts_calc_latency_ms(prev_ts, ts)events.latency_ms
success / format_validFINAL ANSWER 텍스트final_answer(_full) → benchmark별 scorer.scoreruns.success, runs.format_valid
final_answerplanner FINAL ANSWER(한 줄) / synthesizer 전문_extract_final_answer (critic CoT 오염 차단; full은 non-critic 마지막)runs.final_answer
terminated_byTaskResult.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 초과시 절단 + sha256events.tool_result, events.tool_result_hash
selected_next_agent / reasonSelectorEvent / SelectSpeakerEventparse_selector_reason (JSON "next" 추출)events.selected_next_agent, selector_reason
별도 패스 — 인용·grounding (run 종료 후)
events
planner FINAL ANSWER
extract_sources
SOURCES 줄 파싱
score_citations
vs gold_sources
research_grounding
events
tool_name~'%search%'
v_analysis_grounded
실결과(≠'(…)') 필터
grounded (쿼리시 계산)
왜 grounding을 events로 재나(텍스트 아님): 리포트 본문의 출처 URL 수(report_sources)는 synthesizer가 URL을 옮겨 적었는지에 좌우돼 비결정적(EXP-69: 검색 8/9인데 리포트 인용 3/9). 그래서 "검색 도구를 실제 호출/성공했나"(events)를 1급 grounding 신호로 삼고, report_sources는 참고 지표로만 둔다. 인용 채점(cite_f1)도 critic CoT 오염을 피해 planner의 FINAL ANSWER 메시지로 한정해 추출한다(EXP-57이 가르친 교훈).
C 전체 과정 도식 갤러리 & 최신 트렌드·진화 방향
17 ★ 전체 과정 도식 모음 — 코드·역할·메트릭 수집·상황 판단·개선 방향

같은 시스템을 5개의 다른 렌즈로 그렸다. ① 코드가 어떻게 짜였나(컴포넌트) ② 누가 무슨 일을 하나(역할) ③ 숫자가 어떻게 모이나(센서) ④ 그 숫자를 보고 무엇을 의심하나(판단) ⑤ 그래서 무엇을 바꾸나(개선 루프). 마지막은 전 과정 한 장.

① 컴포넌트·레이어 맵 — "코드가 어떻게 구성되나" (제어면 vs 데이터면)
cli.py
typer 진입
runner
배치 오케스트레이션
registry
team_id→빌더
teams/*
AutoGen 팀
run_stream
이벤트 방출
이벤트
tracing/logger
센서
events·runs
scorer/grounding
채점
research_grounding · mv_*
report / 페이지

윗줄=제어면(무엇을 실행할지 결정·구동), 아랫줄=데이터면(무슨 일이 일어났는지 기록·채점·표시). 개선은 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 진행중
logger
토큰·턴·지연·이벤트
events + runs
종료 직후
scorer
success·format
별도 패스
grounding-score
research_grounding(cite_f1)
배치 종료
REFRESH
mv_* (pass@k·loop·tok/succ)
쿼리 시점
v_analysis_grounded

네 가지 타이밍: 실시간(이벤트마다)·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 0synthesizer 인용 누락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반복 측정 후 판정(원장)
⑤ 개선 방향 수립 루프 — "그래서 무엇을 바꾸나"
특이점
메트릭 이상치
가설
1변수
레버 선택
병목별
개입
새 team_id
paired 측정
N≥3
판정
원장 → ↺ promoted면 새 베이스라인
병목 (무엇이 문제)가장 높은 레버이 라더 증거
능력/정보가 없다도구·검색·groundingweb_search → grounded 0→100%(최대 효과)
정보는 있는데 잘못 쓴다프롬프트·역할(취약)여기선 대개 기각(비단조적)
협업이 깨진다토폴로지·종료조건selector→round_robin 0.97→0.07
비용이 문제다제약 제거·dedup·상한과검색 해제 −82%·도구 dedupe
한 장 요약(전 과정): 팀(AutoGen)을 돌려 run_stream이 이벤트를 흘리면 → logger가 센서로 events·runs에 기록 → scorer/grounding이 채점 → 메트릭 표·결정트리로 특이점을 읽고 → 병목별 레버로 1변수 개입 → paired·N≥3로 측정 → 원장 판정 → promoted면 베이스라인 교체. 이 루프를 도는 규율 자체가 "베스트 프랙티스"의 본체다.
18 ★ 최신 트렌드·진화 방향 — Ralph loop · harness engineering · 자동화 (리서치+출처)

프레이밍: 이 페이지는 리서치향 멀티에이전트의 한 예시일 뿐이다. 2025–2026 업계의 큰 흐름은 한 문장으로 — "성능을 좌우하는 건 모델이 아니라 그것을 감싼 harness(도구·맥락·루프·검증)". 아래는 관련 트렌드와 논문을 조사해, 각 트렌드 → 우리 시스템의 현황 → 다음 액션으로 매핑한 것. (근거등급: ★공식=벤더/1차 / ◆de-facto=널리 인용된 논문·OSS / ▷내추론=우리 적용 제안. 출처는 각 항목 끝 링크.)

이 §18은 실행·자동화·검증 '기법'(harness·Ralph·자기개선·MAST)에 초점이다. 업계 전반·대형 서비스가 실제로 화제로 삼는 멀티에이전트 지형 전체(프로토콜·프레임워크·엔터프라이즈 플랫폼·플래그십 제품·메모리·옵저버빌리티·보안)는 바로 아래 §19에 따로 정리했다.
A. Harness / Context Engineering — "harness가 성능을 좌우한다"

무엇: 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)

B. Ralph Loop — "지속 실행 루프 + 깨끗한 맥락 + 외부 메모리"

무엇: 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

C. 자동 에이전트 설계·자기개선 — ADAS · Gödel Agent · GEPA · AlphaEvolve

무엇: 메타-에이전트가 에이전트를 설계/진화시킨다. 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)

D. 검증·신뢰성 — MAST 실패 분류 · pass^k · LLM-as-Judge · inference-time verification

무엇: "정답률 한 줄"을 넘어 왜 실패하는지·얼마나 안정적인지를 잰다. 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)

E. 평가 자체의 함정 — 벤치 누수·lab↔prod 격차·세 리더보드

무엇: 헤드라인 점수는 부풀려진다. 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 engineeringharness가 성능 좌우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다차원 게이트·스냅샷 코퍼스
한 줄 결론: 프런티어의 공통 메시지는 "모델을 키우기보다 harness·루프·검증을 엔지니어링하라"이고, 이 시스템은 그 방향에 이미 정렬돼 있다(MAST 채택·pass^k 측정·events 기반 grounding·1변수 원장 루프). 남은 진화는 그 루프를 자동화(Ralph류)하고, 메타-에이전트로 설계를 진화(ADAS/GEPA)시키며, 검증을 다차원·교차검증으로 확장하는 것이다. ※ 위 수치·주장은 표기한 출처 기준이며, 일부는 2025–2026 시점 자료다(빠르게 갱신되는 분야이므로 원문 확인 권장).
19 ★ 업계 전반 멀티에이전트 지형도 (2025–2026) — 대형 서비스·프레임워크가 실제로 다루는 것

왜 이 절이 필요한가: §18의 Ralph loop·harness engineering은 '실행/자동화 기법'의 일부다. 정작 업계 전반·대형 서비스·표준화에서 지금 가장 크게 다루는 멀티에이전트 아이템은 따로 있다. 아래 8개 축으로 정리하고, 각 축에서 이 시스템이 어디에 있는지를 표기했다. (시장 수치·제품 사실은 벤더/애널리스트·언론 보도 기준, 2025–2026 시점 — 빠르게 갱신됨.)

★ 우리가 차용한 공인 레퍼런스 (분류·지표의 근거)

아래 8축 지형도는 공식 단일 표준이 아니라 업계 'agent stack' 정리를 종합한 것이다(O'Reilly·LangChain·서베이). 반면 우리 시스템 자체의 분류·점수는 공인 레퍼런스를 그대로 차용했다 — 발표 시 이 셋을 근거로 대면 된다:

① 에이전트 구조 분류 → Anthropic Building Effective Agents 패턴 (가장 많이 인용)
우리 역할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)

실패/평가 분류 → MAST (NeurIPS 2025) — 이미 채택(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축 지형도만 '종합 정리'로, 단일 표준 아님.)

가장 큰 세 가지 흐름 (먼저)
① 상호운용 프로토콜 — "에이전트의 HTTP/USB-C" 표준 전쟁

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 프레임워크 등).

8개 축 × 주요 플레이어 × 이 시스템의 위치 (스파인)
무엇이 화제인가주요 플레이어·제품이 시스템의 위치
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 gateplanner-분배 + critic 게이트(HITL의 자동판) + selector(supervisor 유사)
F. 메모리·상태지속 메모리·agentic RAGLetta(MemGPT)·Mem0·Zep(Graphiti)·LangMem·permission-aware RAG현재 run 무상태; 외부기억=events DB; RAG=search_corpus/web_search
G. 옵저버빌리티·평가·가드레일트레이스·eval·eval→guardrailLangSmith·Langfuse·Arize Phoenix·AgentOps·Braintrust·Galileo본체가 이것 — trace DB(events)+메트릭+원장 = 자체구축 옵저버빌리티·평가
H. 보안·신뢰성·거버넌스인젝션 전파·신원·메모리오염OWASP/NIST·TRiSM·멀티에이전트 보안 연구폐쇄망·비밀 마스킹·CHECK 불변식; 인젝션 방어는 향후 과제
이 시스템은 지형도 어디에 있나 (정직한 위치): 이 프로젝트는 B(AutoGen 오케스트레이션)로 만든 D(딥리서치형 MAS)G(옵저버빌리티·평가) 관점에서 측정하는 도구다 — 즉 "프레임워크로 만든 에이전트를, 자체 트레이스 DB·메트릭·실험 원장으로 평가"하는 데 특화돼 있다(LangSmith/Langfuse를 직접 만든 셈). 대형 서비스가 파는 C(엔터프라이즈 플랫폼)A(프로토콜 표준) 자체는 아니지만, 그들이 빠뜨리기 쉬운 "개입의 효과를 1변수·paired·N≥3로 증명하는 평가 규율"이 차별점이다. 다음 확장 후보는 자연히 A(도구 MCP화)·F(지속 메모리)·H(멀티에이전트 보안) 쪽이다.

출처(대표): 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 에이전트 전망 ◆(보도 기준)

D 직접 평가 돌리기 — 운영자 실행 가이드 (셋업 → 실행 → 리포트)
20 ★ 빈손에서 첫 pass@1까지 — 각 단계의 명령·목적·연관 (폐쇄망 머신 기준)

왜 이 절이 필요한가: §1–19가 '무엇을 측정하고(개념·지표), 어떻게 동작하나(코드·스키마)'였다면, 이 절은 코드를 폐쇄망 머신으로 옮겨 직접 돌리는 절차다. 평가 1회는 한 명령으로 끝나지 않는다 — 환경 → 설정 → 스키마 → 문제 적재 → LLM 점검 → 스모크 → 본 평가가 사슬로 이어지고, 앞 고리가 빠지면 뒤가 즉시 무너진다. 각 단계를 명령 한 줄 + 존재 이유 + 다음 단계로의 연결로 읽는다.

🐍 venv
+폐쇄망 설치
📝 .env
DSN·vLLM
🗄 init-db
스키마 멱등
📥 load-gaia
tasks 적재
🔌 smoke_llm
tool-call 왕복
▶ run --limit 2
스모크
📊 report
pass@1
🚀 eval_all_teams.py · 전체·재개
#명령무엇을 한다왜 · 다음 단계와의 연관
0 python3 -m venv .venv
source .venv/bin/activate
pip 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_smoke
python 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 직렬 — 동시 두 평가 금지.
폐쇄망 머신에서만 추가로 필요한 것 (단계 0): 옮긴 머신엔 .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)만 쓴다.
벤치마크 → 적재 명령 (돌릴 것만)
벤치마크로더
gaiacli.py load-gaia
researchcli.py load-research
cppxcli.py load-cppx
learn_algocli.py load-learn
analysisscripts/load_analysis.py
sqlscripts/load_sql.py
extractscripts/load_extract.py
coding · codetestscripts/load_code.py
meeting · ocrproxy · rolescripts/load_roles.py
cli.py run 주요 파라미터
플래그
--batch 필수실행 묶음 ID = 재개 키. 같은 값으로 재실행하면 이어서.
--teamteam_id (기본 v1-baseline, 52팀 등록)
--benchmarkgaia/research/cppx
--limit NN개만 (스모크용)
--exclude-web웹 필요 태스크 제외
--concurrency N동시 실행 수 (z.ai면 1 권장)
--attempts N태스크당 반복 (pass@k용)
--retry-crashedcrashed run 삭제 후 재실행
--task-ids a,b특정 태스크만
직접 돌리기 전 충돌 점검: z.ai(GLM)는 직렬이라 동시에 두 평가를 돌리면 서로 간섭한다. 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