优化顾问模块 — 实现方案对比

三个方案的核心区别在于 LLM 调用边界和代码入侵程度

A

独立后处理器(轻量集成)

新增 rag_eval/advisor/ 包,run_scenario() 末尾调用一行 maybe_run_advisor(result, scenario)

文件结构:

  • rag_eval/advisor/__init__.py
  • rag_eval/advisor/rules.py — 规则引擎,输入 score_rows,输出诊断列表
  • rag_eval/advisor/llm_analyzer.py — 把规则诊断 + 低分样本交给 judge_model
  • rag_eval/advisor/writer.py — 写 optimization_advice.md,打日志摘要

优点

  • 改动最小,runner.py 只加 3 行
  • advisor 完全独立,可单独测试
  • 与现有分层架构完全吻合

缺点

  • 无法拿到 per-metric 的原始 NaN 率(需从 score_rows 重新算)
B

嵌入 reporting 层(复用写出基础设施)

把 advisor 作为 rag_eval/reporting/ 的一部分,write_run_artifacts() 内部判断是否写 advice。

文件结构:

  • rag_eval/reporting/advisor.py — 规则 + LLM + 写出三合一
  • write_run_artifacts() 里追加 if scenario.optimization_advisor: write_advice(...)

优点

  • artifacts 路径管理统一,advice 自然进 run 目录
  • 文件更少

缺点

  • reporting 层本是"无副作用写文件",混入 LLM 调用破坏这一约定
  • advisor 逻辑和写出逻辑耦合,难以单独测试规则引擎
C

方案 A 变体:advisor 有独立 settings(推荐)

与方案 A 相同的文件结构,但 LLM 调用使用 scenario 已有的 judge_model,不新增任何模型配置——advisor 复用 build_models() 已构建好的 llm 实例。

  • rag_eval/advisor/rules.py — 纯函数,7 条指标诊断规则
  • rag_eval/advisor/llm_analyzer.py — 接收已有 llm 实例,不重新建 client
  • rag_eval/advisor/writer.py — 写 md + 日志
  • rag_eval/advisor/__init__.py — 暴露 run_advisor()

优点

  • 不重复创建 LLM client(节省资源)
  • advisor 阈值可通过 YAML 的 optimization_advisor 块扩展配置
  • 独立包边界清晰,易于单测
  • runner.py 改动最小

缺点

  • 需把 llm 实例从 runner 传入 advisor(多传一个参数)