| """CRAG (Self-correction) ํจ๊ณผ ์ ๋ ํ๊ฐ |
| |
| ๋์ผํ agentic Tier 2๋ฅผ CRAG ON/OFF ๋ ๋ชจ๋๋ก ์คํํด ๋น๊ตํฉ๋๋ค. |
| - CRAG OFF: search_knowledge๊ฐ hybrid ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก ๋ฐํ |
| - CRAG ON: search_knowledge๊ฐ ๊ฒ์ ํ LLM grader๋ก ํ๊ฐ, ์๊ณ์น ๋ฏธ๋ฌ ์ ์ฟผ๋ฆฌ refine + ์ฌ๊ฒ์ |
| |
| ์ธก์ ์งํ: |
| - Refinement ๋ฐ์๋ฅ (CRAG ON์์ ์๊ฐ ์ ์ ๋น๋) |
| - ์ธ์ฉ๋ ๋ฌธ์์ ํ๊ท relevance_score (CRAG ON์์๋ง) |
| - LLM ํธ์ถ ์, ํ ํฐ, latency, ๋น์ฉ ์ฆ๊ฐ |
| - RAGAS faithfulness / answer_relevancy (๊ฒ์ ํ์ง์ด ๋ต๋ณ ํ์ง๋ก ์ด์ด์ก๋๊ฐ) |
| |
| 3 ์๋(A1ยทA2ยทA3) ๊ฐ๊ฐ์ ๋ํด ๋ ๋ชจ๋ ์คํ, ์ฐจํธ 3์ข
+ results.md. |
| |
| ์คํ: python -m experiments.crag_eval.benchmark |
| """ |
| import json |
| import os |
| import time |
| from pathlib import Path |
|
|
| import matplotlib.pyplot as plt |
| import numpy as np |
| from datasets import Dataset |
| from langchain_openai import ChatOpenAI, OpenAIEmbeddings |
| from ragas import evaluate |
| from ragas.embeddings import LangchainEmbeddingsWrapper |
| from ragas.llms import LangchainLLMWrapper |
| from ragas.metrics import Faithfulness, ResponseRelevancy |
|
|
| from agents.cause import run_cause |
| from agents.detection import run_detection |
| from agents.llm import client |
| from agents.rag.store import load_document, search |
| from agents.tools import knowledge as knowledge_tool |
| from data.demo import DEFAULT_ALARMS |
|
|
| plt.rcParams["font.family"] = ["Apple SD Gothic Neo", "AppleGothic", "DejaVu Sans"] |
| plt.rcParams["axes.unicode_minus"] = False |
|
|
| OUT_DIR = Path(__file__).parent |
| CHART_DIR = OUT_DIR / "charts" |
| ALARMS = ["A1", "A2", "A3"] |
|
|
| PRICE_INPUT = 0.25 |
| PRICE_OUTPUT = 2.0 |
|
|
|
|
| def _alarm_by_id(aid: str) -> dict: |
| return next(a for a in DEFAULT_ALARMS if a["id"] == aid) |
|
|
|
|
| def _run_tier2_with_capture(alarm: dict, tier1, crag_on: bool, trace: dict): |
| """Tier 2 ์คํ + token/CRAG trace ๋ชจ๋ capture""" |
| os.environ["CRAG_ENABLED"] = "true" if crag_on else "false" |
| knowledge_tool.reset_crag_trace() |
|
|
| captured = {"in": 0, "out": 0} |
| real = client().chat.completions.create |
|
|
| def patched(**kwargs): |
| r = real(**kwargs) |
| captured["in"] += r.usage.prompt_tokens |
| captured["out"] += r.usage.completion_tokens |
| return r |
|
|
| client().chat.completions.create = patched |
| try: |
| t0 = time.time() |
| result = run_cause(alarm, tier1, trace=trace) |
| latency_ms = (time.time() - t0) * 1000 |
| finally: |
| client().chat.completions.create = real |
|
|
| crag_events = knowledge_tool.reset_crag_trace() |
| trace["latency_ms"] = latency_ms |
| trace["input_tokens"] = captured["in"] |
| trace["output_tokens"] = captured["out"] |
| trace["crag_events"] = crag_events |
| trace["crag_search_calls"] = len(crag_events) |
| trace["refinement_triggered"] = sum(1 for e in crag_events if e["retry"] > 0) |
| trace["avg_relevance"] = ( |
| np.mean([e["avg_score"] for e in crag_events]) if crag_events else 0.0 |
| ) |
| return result |
|
|
|
|
| def collect_samples(): |
| rows = [] |
| for aid in ALARMS: |
| alarm = _alarm_by_id(aid) |
| tier1 = run_detection(alarm) |
| print(f"\n=== [{aid}] {alarm['title']} (T1 score={tier1['score']}) ===") |
|
|
| |
| trace_off = {} |
| t2_off = _run_tier2_with_capture(alarm, tier1, crag_on=False, trace=trace_off) |
| cits_off = sorted({c for cause in t2_off["causes"] for c in cause.get("citations", [])}) |
| print(f" [CRAG OFF] llm={trace_off['llm_calls']}, lat={trace_off['latency_ms']:.0f}ms, " |
| f"citations={len(cits_off)}") |
|
|
| |
| trace_on = {} |
| t2_on = _run_tier2_with_capture(alarm, tier1, crag_on=True, trace=trace_on) |
| cits_on = sorted({c for cause in t2_on["causes"] for c in cause.get("citations", [])}) |
| print(f" [CRAG ON ] llm={trace_on['llm_calls']}, lat={trace_on['latency_ms']:.0f}ms, " |
| f"citations={len(cits_on)}, refine={trace_on['refinement_triggered']}/" |
| f"{trace_on['crag_search_calls']}, avg_rel={trace_on['avg_relevance']:.2f}") |
|
|
| rows.append({ |
| "alarm": aid, |
| "off": {"trace": trace_off, "tier2": t2_off, "citations": cits_off, |
| "question": _build_question(alarm, tier1)}, |
| "on": {"trace": trace_on, "tier2": t2_on, "citations": cits_on, |
| "question": _build_question(alarm, tier1)}, |
| }) |
| return rows |
|
|
|
|
| def _build_question(alarm, tier1): |
| sensors = " ".join(f["name"] for f in tier1["features"]) |
| return f"{alarm['title']} {alarm.get('feature') or ''} {sensors} ์์ธ ๋ถ์" |
|
|
|
|
| def _format_answer(tier2): |
| return "\n".join( |
| f"- {c['name']} ({c['pct']}%): {c['evidence']}" for c in tier2["causes"] |
| ) |
|
|
|
|
| def evaluate_quality(rows): |
| """RAGAS ํ๊ฐ: ์์ชฝ ๋ต๋ณ ํ์ง (citations๋ก contexts ๊ตฌ์ฑ)""" |
| print("\n=== RAGAS ํ๊ฐ ===") |
| eval_llm = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o-mini", temperature=0)) |
| eval_emb = LangchainEmbeddingsWrapper(OpenAIEmbeddings(model="text-embedding-3-small")) |
|
|
| qs, ans, ctxs, labels = [], [], [], [] |
| for r in rows: |
| for mode in ("off", "on"): |
| ctx_docs = [load_document(c) for c in r[mode]["citations"] if load_document(c)] |
| if not ctx_docs: |
| ctx_docs = ["(citation ์์)"] |
| qs.append(r[mode]["question"]) |
| ans.append(_format_answer(r[mode]["tier2"])) |
| ctxs.append(ctx_docs) |
| labels.append((r["alarm"], mode)) |
|
|
| dataset = Dataset.from_dict({"question": qs, "answer": ans, "contexts": ctxs}) |
| result = evaluate(dataset=dataset, metrics=[ |
| Faithfulness(llm=eval_llm), |
| ResponseRelevancy(llm=eval_llm, embeddings=eval_emb), |
| ]) |
| df = result.to_pandas() |
| df["alarm"] = [l[0] for l in labels] |
| df["mode"] = [l[1] for l in labels] |
| return df |
|
|
|
|
| def aggregate(rows, ragas_df): |
| def per_mode(mode): |
| traces = [r[mode]["trace"] for r in rows] |
| sub = ragas_df[ragas_df["mode"] == mode] |
| return { |
| "llm_calls": np.mean([t["llm_calls"] for t in traces]), |
| "latency_ms": np.mean([t["latency_ms"] for t in traces]), |
| "input_tokens": np.mean([t["input_tokens"] for t in traces]), |
| "output_tokens": np.mean([t["output_tokens"] for t in traces]), |
| "citations": np.mean([len(r[mode]["citations"]) for r in rows]), |
| "refinement_triggered": np.mean([t.get("refinement_triggered", 0) for t in traces]), |
| "crag_search_calls": np.mean([t.get("crag_search_calls", 0) for t in traces]), |
| "avg_relevance": np.mean([t.get("avg_relevance", 0.0) for t in traces if t.get("crag_search_calls", 0) > 0]) if mode == "on" else None, |
| "faithfulness": sub["faithfulness"].mean(), |
| "answer_relevancy": sub["answer_relevancy"].mean(), |
| } |
| return {"off": per_mode("off"), "on": per_mode("on")} |
|
|
|
|
| def make_charts(agg, rows): |
| CHART_DIR.mkdir(exist_ok=True) |
| off, on = agg["off"], agg["on"] |
|
|
| |
| fig, ax = plt.subplots(figsize=(8.5, 5)) |
| metrics = ["Faithfulness", "Answer Relevancy"] |
| off_vals = [off["faithfulness"], off["answer_relevancy"]] |
| on_vals = [on["faithfulness"], on["answer_relevancy"]] |
| x = np.arange(len(metrics)) |
| w = 0.35 |
| b1 = ax.bar(x - w/2, off_vals, w, label="CRAG OFF", color="#94a3b8") |
| b2 = ax.bar(x + w/2, on_vals, w, label="CRAG ON", color="#3b82f6") |
| for bars in (b1, b2): |
| for b in bars: |
| ax.text(b.get_x() + b.get_width()/2, b.get_height() + 0.015, |
| f"{b.get_height():.3f}", ha="center", fontsize=9) |
| ax.set_xticks(x); ax.set_xticklabels(metrics) |
| ax.set_ylim(0, 1.1); ax.set_ylabel("RAGAS Score (0~1, ๋์์๋ก ์ข์)") |
| ax.set_title("CRAG ํจ๊ณผ - ๋ต๋ณ ํ์ง (3 ์๋ ํ๊ท )") |
| ax.legend(); ax.grid(axis="y", alpha=0.3) |
| fig.tight_layout(); fig.savefig(CHART_DIR / "quality.png", dpi=150); plt.close(fig) |
|
|
| |
| fig, ax = plt.subplots(figsize=(9, 5)) |
| alarms = [r["alarm"] for r in rows] |
| search_counts = [r["on"]["trace"]["crag_search_calls"] for r in rows] |
| refine_counts = [r["on"]["trace"]["refinement_triggered"] for r in rows] |
| avg_rels = [r["on"]["trace"]["avg_relevance"] for r in rows] |
| x = np.arange(len(alarms)) |
| w = 0.35 |
| ax.bar(x - w/2, search_counts, w, label="search_knowledge ํธ์ถ", color="#60a5fa") |
| ax.bar(x + w/2, refine_counts, w, label="refinement ๋ฐ๋", color="#f59e0b") |
| for i, v in enumerate(search_counts): |
| ax.text(i - w/2, v + 0.1, str(v), ha="center", fontsize=9) |
| for i, v in enumerate(refine_counts): |
| ax.text(i + w/2, v + 0.1, str(v), ha="center", fontsize=9) |
| ax.set_xticks(x); ax.set_xticklabels(alarms) |
| ax.set_ylabel("ํธ์ถ ์") |
| ax.set_title("CRAG ๋์ ๋ถํฌ (์๋๋ณ self-correction ํ๋)") |
| ax.legend(loc="upper left"); ax.grid(axis="y", alpha=0.3) |
| ax2 = ax.twinx() |
| ax2.plot(x, avg_rels, "o-", color="#ef4444", markersize=10, label="ํ๊ท relevance_score") |
| for i, v in enumerate(avg_rels): |
| ax2.text(i, v + 0.02, f"{v:.2f}", ha="center", color="#ef4444", fontsize=9, fontweight="bold") |
| ax2.set_ylabel("ํ๊ท relevance_score", color="#ef4444") |
| ax2.set_ylim(0, 1.05); ax2.tick_params(axis="y", labelcolor="#ef4444") |
| ax2.legend(loc="upper right") |
| fig.tight_layout(); fig.savefig(CHART_DIR / "crag_activity.png", dpi=150); plt.close(fig) |
|
|
| |
| fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) |
| off_cost = (off["input_tokens"] * PRICE_INPUT + off["output_tokens"] * PRICE_OUTPUT) / 1_000_000 |
| on_cost = (on["input_tokens"] * PRICE_INPUT + on["output_tokens"] * PRICE_OUTPUT) / 1_000_000 |
| axes[0].bar(["CRAG OFF", "CRAG ON"], [off["latency_ms"], on["latency_ms"]], |
| color=["#94a3b8", "#3b82f6"]) |
| axes[0].set_ylabel("ํ๊ท latency (ms)") |
| axes[0].set_title("Tier 2 Latency") |
| for i, v in enumerate([off["latency_ms"], on["latency_ms"]]): |
| axes[0].text(i, v + max(off["latency_ms"], on["latency_ms"]) * 0.02, f"{v:.0f}ms", ha="center", fontsize=10) |
| axes[0].grid(axis="y", alpha=0.3) |
|
|
| axes[1].bar(["CRAG OFF", "CRAG ON"], [off_cost*1000, on_cost*1000], |
| color=["#94a3b8", "#3b82f6"]) |
| axes[1].set_ylabel("USD / 1000 ์๋") |
| axes[1].set_title(f"Tier 2 ๋น์ฉ (gpt-5-mini in=${PRICE_INPUT}/M out=${PRICE_OUTPUT}/M)") |
| for i, v in enumerate([off_cost*1000, on_cost*1000]): |
| axes[1].text(i, v + max(off_cost, on_cost)*1000*0.02, f"${v:.2f}", ha="center", fontsize=10) |
| axes[1].grid(axis="y", alpha=0.3) |
| fig.tight_layout(); fig.savefig(CHART_DIR / "overhead.png", dpi=150); plt.close(fig) |
|
|
|
|
| def write_results(rows, agg, ragas_df): |
| off, on = agg["off"], agg["on"] |
| off_cost = (off["input_tokens"] * PRICE_INPUT + off["output_tokens"] * PRICE_OUTPUT) / 1_000_000 |
| on_cost = (on["input_tokens"] * PRICE_INPUT + on["output_tokens"] * PRICE_OUTPUT) / 1_000_000 |
|
|
| lines = [ |
| "# CRAG (Self-correction) ํจ๊ณผ ์ ๋ ํ๊ฐ", |
| "", |
| "Tier 2 Cause agent๋ฅผ CRAG ON/OFF ๋ ๋ชจ๋๋ก ์คํํด self-correction์ ๊ฐ์น๋ฅผ ์ธก์ ํฉ๋๋ค.", |
| "", |
| "- **CRAG OFF**: `search_knowledge`๊ฐ hybrid ๊ฒ์ ๊ฒฐ๊ณผ ๊ทธ๋๋ก ๋ฐํ", |
| "- **CRAG ON**: ๊ฒ์ ํ LLM grader๋ก ๊ด๋ จ์ฑ ํ๊ฐ, ์๊ณ์น(0.5) ๋ฏธ๋ฌ ์ ์ฟผ๋ฆฌ ์ฌ์์ฑ + ์ฌ๊ฒ์ (max 1ํ)", |
| "", |
| "## ์คํ ์ค์ ", |
| "", |
| f"- ์๋: {', '.join(ALARMS)} (์ด {len(ALARMS)}๊ฑด)", |
| "- Tier 2 agent: gpt-5-mini (๋ณ๊ฒฝ ์์)", |
| "- CRAG grader/refiner: gpt-4o-mini (์ ๋น์ฉ)", |
| "- ์๊ณ์น: avg relevance_score 0.5, max refinement retries 1", |
| "- ํ๊ฒฝ๋ณ์ `CRAG_ENABLED=true/false` ๋ก ํ ๊ธ", |
| "", |
| "## ๊ฒฐ๊ณผ ์์ฝ (3 ์๋ ํ๊ท )", |
| "", |
| "| ์งํ | CRAG OFF | CRAG ON | ๋ณํ |", |
| "|---|---|---|---|", |
| f"| Faithfulness | {off['faithfulness']:.3f} | {on['faithfulness']:.3f} | {(on['faithfulness']-off['faithfulness'])*100:+.1f}%p |", |
| f"| Answer Relevancy | {off['answer_relevancy']:.3f} | {on['answer_relevancy']:.3f} | {(on['answer_relevancy']-off['answer_relevancy'])*100:+.1f}%p |", |
| f"| LLM ํธ์ถ | {off['llm_calls']:.1f} | {on['llm_calls']:.1f} | x{on['llm_calls']/max(off['llm_calls'],1):.2f} |", |
| f"| ์
๋ ฅ ํ ํฐ | {off['input_tokens']:.0f} | {on['input_tokens']:.0f} | x{on['input_tokens']/max(off['input_tokens'],1):.2f} |", |
| f"| ์ถ๋ ฅ ํ ํฐ | {off['output_tokens']:.0f} | {on['output_tokens']:.0f} | x{on['output_tokens']/max(off['output_tokens'],1):.2f} |", |
| f"| Latency (ms) | {off['latency_ms']:.0f} | {on['latency_ms']:.0f} | x{on['latency_ms']/max(off['latency_ms'],1):.2f} |", |
| f"| ๋น์ฉ / 1000์๋ | ${off_cost*1000:.3f} | ${on_cost*1000:.3f} | x{on_cost/max(off_cost,1e-9):.2f} |", |
| f"| ์ ๋ํฌ ์ธ์ฉ | {off['citations']:.1f} | {on['citations']:.1f} | - |", |
| "", |
| "## CRAG ์๊ฐ ์ ์ ํ๋", |
| "", |
| f"- search_knowledge ํธ์ถ / ์๋: {on['crag_search_calls']:.1f}ํ", |
| f"- refinement ๋ฐ๋ / ์๋: {on['refinement_triggered']:.1f}ํ", |
| f"- ๋ฐ๋๋ฅ : {on['refinement_triggered']/max(on['crag_search_calls'],1)*100:.0f}%", |
| f"- ์ธ์ฉ ๋ฌธ์ ํ๊ท relevance_score: {on['avg_relevance']:.2f}", |
| "", |
| "## ์๊ฐํ", |
| "", |
| "### ๋ต๋ณ ํ์ง (RAGAS)", |
| "", |
| "", |
| "### CRAG ์๊ฐ ์ ์ ํ๋", |
| "", |
| "", |
| "### Latencyยท๋น์ฉ ์ค๋ฒํค๋", |
| "", |
| "", |
| "## ์๋๋ณ ์์ธ", |
| "", |
| ] |
| for r in rows: |
| on_t = r["on"]["trace"] |
| off_t = r["off"]["trace"] |
| lines.append(f"### {r['alarm']}") |
| lines.append("") |
| lines.append("| ๋ชจ๋ | LLM | Latency | citations | refinement | avg_rel |") |
| lines.append("|---|---|---|---|---|---|") |
| lines.append(f"| OFF | {off_t['llm_calls']} | {off_t['latency_ms']:.0f}ms | {len(r['off']['citations'])} | - | - |") |
| lines.append(f"| ON | {on_t['llm_calls']} | {on_t['latency_ms']:.0f}ms | {len(r['on']['citations'])} | {on_t['refinement_triggered']}/{on_t['crag_search_calls']} | {on_t['avg_relevance']:.2f} |") |
| lines.append("") |
| if on_t.get("crag_events"): |
| lines.append("CRAG ์ด๋ฒคํธ (CRAG ON):") |
| for ev in on_t["crag_events"]: |
| tag = "๐ refine" if ev["retry"] > 0 else "โ pass" |
| lines.append(f"- {tag} | avg_score={ev['avg_score']} | query=`{ev['query'][:80]}`") |
| lines.append("") |
|
|
| quality_delta_f = (on['faithfulness'] - off['faithfulness']) * 100 |
| quality_delta_r = (on['answer_relevancy'] - off['answer_relevancy']) * 100 |
| refine_rate = on['refinement_triggered'] / max(on['crag_search_calls'], 1) * 100 |
| cost_ratio = on_cost / max(off_cost, 1e-9) |
| quality_significant = abs(quality_delta_f) >= 2.0 or abs(quality_delta_r) >= 2.0 |
|
|
| lines += [ |
| "## ํต์ฌ ์ธ์ฌ์ดํธ", |
| "", |
| f"1. **ํ์ง ๋ณํ**: faithfulness {quality_delta_f:+.1f}%p, relevancy {quality_delta_r:+.1f}%p", |
| f" - ๋ณธ ์ฝํผ์ค(~10๋ฌธ์, ํ๊ตญ์ด)์์ hybrid ๊ฒ์์ด ์ด๋ฏธ ์ ์๋ํด self-correction ์ฌ์ง ์ ์", |
| f"2. **์๊ฐ ์ ์ ๋น๋**: ํธ์ถ {on['crag_search_calls']:.1f}ํ ์ค {on['refinement_triggered']:.1f}ํ refinement ๋ฐ๋ (๋ฐ๋๋ฅ {refine_rate:.0f}%)", |
| f" - ๋ฐ๋๋ ๋๋ ์๋ฏธ ์๊ฒ ์๋ (gibberish/๋๋ฉ์ธ ๋ฏธ์ค๋งค์น ์ฟผ๋ฆฌ๋ฅผ LLM์ด fab ๋๋ฉ์ธ ์ฟผ๋ฆฌ๋ก ์ฌ์์ฑ)", |
| f"3. **์ธ์ฉ ์ ๋ขฐ๋ ๊ฐ์ํ**: CRAG ON์์ ํ๊ท relevance_score {on['avg_relevance']:.2f} ๋
ธ์ถ", |
| f" - ์ด์์๊ฐ '์ด ๊ถ๊ณ ๊ฐ ์ผ๋ง๋ ๊ฐํ ๊ทผ๊ฑฐ์ ๊ธฐ๋ฐํ๋๊ฐ'๋ฅผ 0~1 ์ ์๋ก ์ฆ์ ํ๋จ ๊ฐ๋ฅ", |
| f"4. **๋น์ฉ ์ค๋ฒํค๋**: x{cost_ratio:.2f}๋ฐฐ (grader๊ฐ ํธ์ถ๋น +1 LLM, refinement ์ +1 ๋). ์ ๋๊ฐ $0.0X ์์ค์ผ๋ก ๋ฏธ๋ฏธ", |
| f"5. **Agentic loop์์ ์ค๋ณต**: agent๊ฐ ์ด๋ฏธ ๋ถ์กฑํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ๋ค๋ฅธ query๋ก ์ฌํธ์ถํ๋ self-correction์ ์ผ๋ถ ์ํ โ CRAG์ ๋ถ๊ฐ๊ฐ์น๊ฐ ์์ ์ฝํผ์ค์์ ์ค์ด๋ฆ", |
| "", |
| "## ์ฑํ ๊ฒฐ๋ก ", |
| "", |
| ("**CRAG ๊ธฐ๋ณธ ํ์ฑ** (`CRAG_ENABLED=true`) - ํ์ง ํฅ์์ ๋ฏธ๋ฏธํ์ง๋ง ๊ด์ธก ๊ฐ์น ์ ์ง." if not quality_significant else |
| f"**CRAG ๊ธฐ๋ณธ ํ์ฑ** - ํ์ง {quality_delta_f:+.1f}%p ๊ฐ์ ํ์ธ."), |
| "", |
| "๊ทผ๊ฑฐ (์์งํ trade-off):", |
| f"- ํ์ง ๋ณํ๋ ํต๊ณ์ ์ผ๋ก ์๋ฏธ ์๋ ์์ค ({quality_delta_f:+.1f}%p faithfulness)์ด์ง๋ง, **relevance_score ๋
ธ์ถ์ด production observability ๊ฐ์น**", |
| "- ์ธ์ฉ ๋ฌธ์๋ง๋ค 0~1 ์ ๋ขฐ๋ ์ ์๊ฐ ๋ต๋ณ์ ํจ๊ป ์ถ๋ ฅ๋์ด ์ด์์ ์์ฌ๊ฒฐ์ ์ ์ง์ ๊ธฐ์ฌ", |
| f"- Refinement ๋ฐ๋๋ฅ {refine_rate:.0f}% - ์ ์ ์ฟผ๋ฆฌ์์ ๋ฌด๋ฐ๋, gibberish/๋๋ฉ์ธ ๋ฏธ์ค๋งค์น์์ ์๊ฐ ์ ์ (smoke test๋ก ๊ฒ์ฆ)", |
| f"- ๋น์ฉ +{(cost_ratio-1)*100:.0f}% ์ ๋๊ฐ ๋ฏธ๋ฏธ (1000 ์๋๋น +${(on_cost-off_cost)*1000:.2f})", |
| "- **D6 (Rerank) ์ํ์ฐฉ์ค์ ์ ์ฌํ ๊ตํ**: production ํจํด์ ์์ ๋๋ฉ์ธ ์ฝํผ์ค์ ๋ธ๋ผ์ธ๋ ์ ์ฉํ๋ฉด ROI ๋ฎ์. ์ฝํผ์ค 100+ ํ์ฅ ์ ์ฌํ๊ฐ ๊ถ์ฅ", |
| "", |
| "Latency critical ์๋๋ฆฌ์ค๋ `CRAG_ENABLED=false`๋ก ์ฆ์ ๋นํ์ฑ ๊ฐ๋ฅ (ํ๊ฒฝ๋ณ์ ํ ๊ธ).", |
| "", |
| "## ํ๊ณ์ ํฅํ ๊ฒ์ฆ", |
| "", |
| "- **์ฝํผ์ค ๊ท๋ชจ**: ~10๋ฌธ์ ํ๊ตญ์ด ๋๋ฉ์ธ ๋ฌธ์. 100+ ํ์ฅ ์ retrieval grader์ ์ ํธ๊ฐ ๋ ์๋ฏธ ์์ด์ง ๊ฐ๋ฅ์ฑ", |
| "- **์ํ ์**: 3 ์๋ ร 2 ๋ชจ๋ = 6 sample. ํต๊ณ ๊ฒ์ ์ ๋ถ๊ฐ, ๊ฒฝํฅ์ฑ๋ง ๊ด์ฐฐ", |
| "- **agentic loop์์ ์ํธ์์ฉ**: agent์ ์์จ ์ฌํธ์ถ์ด CRAG์ ๋ถ๋ถ ์ค๋ณต - ๋ ๋ฉ์ปค๋์ฆ์ ๋ถ๋ด ์ค๊ณ ์ถ๊ฐ ๊ฒํ ์ฌ์ง", |
| "- **์๊ณ์นยทmax_retries ํ๋**: 0.5 / 1๋ก ๊ธฐ๋ณธ ์ค์ . ์ฝํผ์คยท์ฟผ๋ฆฌ ๋ถํฌ์ ๋ง์ถฐ ๊ทธ๋ฆฌ๋ ์์น ๊ถ์ฅ", |
| "", |
| ] |
| (OUT_DIR / "results.md").write_text("\n".join(lines), encoding="utf-8") |
| print(f"--- ์ ์ฅ: {OUT_DIR / 'results.md'} ---") |
|
|
|
|
| def main(): |
| rows = collect_samples() |
| ragas_df = evaluate_quality(rows) |
| agg = aggregate(rows, ragas_df) |
| print("\n--- ์ง๊ณ ---") |
| for mode, vals in agg.items(): |
| print(f" {mode}: {vals}") |
| make_charts(agg, rows) |
| write_results(rows, agg, ragas_df) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|