feat(advisor): add optimization advisor module

- rag_eval/advisor/: new package with rules engine, LLM analyzer, writer
  - rules.py: 7-metric diagnostic rules (warning/critical thresholds, top-3 low samples)
  - llm_analyzer.py: Chinese optimization report via judge_model, graceful fallback
  - writer.py: writes optimization_advice.md + log summary
  - __init__.py: run_advisor() entry point (no-op when optimization_advisor=False)
- Scenario.optimization_advisor: new bool field (default False)
- ScenarioModel: same field added, loader.py透传
- RunArtifactPaths.advice_md: new path field
- factory.py: build_models() now public; build_metric_pipeline() accepts pre-built llm/embeddings
- runner.py: lifts llm, passes to pipeline and advisor; calls run_advisor() at end
- siemens online YAML: optimization_advisor: true enabled
- tests: 9 rules tests + 6 writer tests, all pass
- docs: advisor section added to engine-flow.md and architecture.md

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-16 17:06:19 +08:00
parent d68399d39b
commit f5c2dce64a
17 changed files with 2381 additions and 9 deletions

View File

@@ -318,6 +318,10 @@ metrics:
- answer_relevancy
- context_recall
- context_precision
# 可选:鲁棒性 / 端到端指标(需数据集含 ground_truth完整列表见 §9.4
# - noise_sensitivity
# - factual_correctness
# - semantic_similarity
output_dir: runs/legal-assistant-offline-baseline
runtime:
batch_size: 4
@@ -338,7 +342,7 @@ runtime:
- `embedding_model`
- 负责向量相关指标的模型
- `metrics`
- 本次启用的指标列表
- 本次启用的指标列表(完整可选项与依赖见 §9.4
- `output_dir`
- 本次运行结果输出目录
- `runtime.batch_size`
@@ -399,6 +403,32 @@ app_adapter:
- embedding model
- 指标实例
当前支持的指标(`rag_eval/metrics/registry.py` 中的 `SUPPORTED_METRICS`
| 指标名 | 层面 | 依赖 |
|---|---|---|
| `faithfulness` | 生成 | judge model |
| `answer_relevancy` | 生成 | judge model + embedding |
| `context_recall` | 检索 | judge model + ground_truth |
| `context_precision` | 检索 | judge model + ground_truth |
| `noise_sensitivity` | 鲁棒性 | judge model + ground_truth |
| `factual_correctness` | 端到端 | judge model + ground_truth |
| `semantic_similarity` | 端到端 | embedding + ground_truth无 LLM 调用) |
后四项以 `ground_truth`(标准答案)为参照,数据集必须提供该字段。新增指标统一在 `registry.py` / `factory.py` / `pipeline.py` 三处对齐装配。
**Optimization Advisor§11 优化策略落地):**
评测结束后,若场景配置 `optimization_advisor: true`,则自动调用 `rag_eval/advisor/` 模块:
- 规则引擎(`rules.py`)对 7 个指标各自设阈值,识别触发项并选取 top-3 低分样本
- LLM 分析器(`llm_analyzer.py`)结合低分样本生成中文 Markdown 优化建议(复用 judge_model失败自动降级为纯规则报告
- 写出层(`writer.py`)输出 `optimization_advice.md` 并打日志摘要
```yaml
# 场景配置示例
optimization_advisor: true
```
### 9.5 并发控制
执行层负责并发上限,不把并发策略散落到各指标实现中。

View File

@@ -316,11 +316,21 @@ adapter 层的目标是:**把不同类型的目标应用,统一成同一套
当前支持的指标包括:
核心检索 / 生成指标(始终可用):
- `faithfulness`
- `answer_relevancy`
- `context_recall`
- `context_precision`
鲁棒性 / 端到端指标(架构设计 §10.2,需数据集含 `ground_truth`
- `noise_sensitivity` —— 鲁棒性:对检索噪声的敏感度
- `factual_correctness` —— 端到端:回答相对标准答案的事实正确性
- `semantic_similarity` —— 端到端:回答与标准答案的语义相似度(基于 embedding无 LLM 调用)
所有指标都通过同一套装配点接入:`registry.py`(校验白名单)、`factory.py`(实例化)、`pipeline.py``ascore` 入参分发),新增指标只需在这三处对齐即可。
所以 metric pipeline 的职责可以总结为:
**把标准样本转换成结构化评分结果。**
@@ -414,3 +424,39 @@ main.py
- 可以把每次实验的资产稳定留住
这也是它和一次性离线脚本的根本区别。
---
## 15. Optimization Advisor 链路
相关代码:
- `rag_eval/advisor/__init__.py` — 外部入口 `run_advisor()`
- `rag_eval/advisor/rules.py` — 规则引擎(纯函数,无 LLM7 条指标诊断规则
- `rag_eval/advisor/llm_analyzer.py` — LLM 分析器(复用 judge_model llm 实例,失败自动降级)
- `rag_eval/advisor/writer.py` — 写出 `optimization_advice.md` + 日志摘要
Advisor 在 `write_run_artifacts()` 之后触发,仅当场景配置 `optimization_advisor: true` 时生效,默认关闭。
执行链路:
```text
run_advisor(result, scenario, llm)
-> rules.diagnose(score_rows, metrics) # 识别异常指标,选取 top-3 低分样本
-> llm_analyzer.analyze(diagnoses, llm) # LLM 生成中文建议(失败自动降级为纯规则报告)
-> writer.write_advice(...) # 写 optimization_advice.md + 日志摘要
```
输出产物追加在现有 run 目录:
```text
outputs/online/siemens-pdf-question-bank/<run_id>/
scenario.snapshot.yaml
scores.csv
invalid.csv
summary.md
metadata.json
optimization_advice.md <- 新增optimization_advisor: true 时生成)
```
规则引擎对 7 个指标各自设 warning / critical 双档阈值,`noise_sensitivity` 为"越低越好"(方向相反)。所有诊断均附带 top-3 低分样本,喂给 LLM 生成针对具体内容的中文建议。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
# 优化顾问模块设计 Spec
- 日期2026-06-16
- 状态:已确认,进入实现。
## 1. 目标
在现有 RAG 评测流程结束后,新增一个**优化顾问模块**Optimization Advisor根据本次评测的多项指标分数与低分样本自动诊断指标偏低的原因并给出针对性的优化建议输出为中文 Markdown 报告 + 日志摘要。
对应架构设计 §11优化策略将"指标到动作的映射"§11.2)从文档形式落地为代码自动执行。
---
## 2. 决策摘要
| 决策点 | 选择 |
|---|---|
| 输出形式 | `optimization_advice.md`(文件)+ 控制台/日志摘要(双输出) |
| 生成机制 | 规则引擎定位异常指标 → LLM 结合低分样本二次解读(两层) |
| 触发方式 | YAML 场景文件显式声明 `optimization_advisor: true`,默认关闭 |
| LLM 实例 | 复用 `build_models()` 已创建的 `llm` 实例,不重建 client |
| 包位置 | `rag_eval/advisor/`(独立包,对外暴露 `run_advisor()` 单一入口) |
---
## 3. 架构
### 3.1 执行链路
```
run_scenario()
→ load_scenario() # 读 YAML解析 optimization_advisor 字段
→ build_models() # 已有:创建 llm, embeddings
→ build_metric_pipeline() # 已有
→ Evaluator.evaluate() # 已有:打分 → EvaluationResult
→ write_run_artifacts() # 已有scores.csv / summary.md / ...
→ run_advisor( # 新增3 行)
result, scenario, llm, artifact_paths
)
→ rules.diagnose(score_rows) # 规则引擎:返回 Diagnosis 列表
→ llm_analyzer.analyze(diags, samples) # LLM生成中文 Markdown 建议
→ writer.write(advice, paths) # 写文件 + 打日志
```
### 3.2 新增文件
```
rag_eval/advisor/
__init__.py ← 暴露 run_advisor(),外部唯一入口
rules.py ← 纯函数规则引擎,无 LLM可单独单测
llm_analyzer.py ← 接收 llm 实例 + 诊断结构 → 中文 Markdown
writer.py ← 写 optimization_advice.md打日志摘要
```
### 3.3 修改文件(最小改动)
| 文件 | 改动 |
|---|---|
| `rag_eval/shared/models.py` | `Scenario``optimization_advisor: bool = False` 字段 |
| `rag_eval/config/schema.py` | `ScenarioModel` 加同名字段 + 透传到 `Scenario` |
| `rag_eval/config/loader.py` | 透传 `optimization_advisor``Scenario` 构造 |
| `rag_eval/reporting/artifacts.py` | `RunArtifactPaths``advice_md: Path` 字段 + `build_artifact_paths()` 加赋值 |
| `rag_eval/execution/runner.py` | `run_scenario()` 末尾:`build_models` 返回 llm 传入,条件调用 `run_advisor()` |
### 3.4 输出产物
```
outputs/online/siemens-pdf-question-bank/<run_id>/
scenario.snapshot.yaml
scores.csv
invalid.csv
summary.md
metadata.json
optimization_advice.md ← 新增optimization_advisor: true 时生成)
```
---
## 4. 规则引擎rules.py
### 4.1 数据结构
```python
@dataclass
class Diagnosis:
metric: str # 指标名
mean_score: float # 本次均值
threshold: float # 警戒阈值
severity: str # "warning" | "critical"
root_causes: list[str] # 可能原因(来自架构设计 §11.2
suggested_actions: list[str] # 对应可调阶段
low_samples: list[dict] # 分数最低的 N 条样本(含 question/answer/ground_truth
```
### 4.2 七条指标诊断规则
阈值参考 RAG 评测最佳实践,分 warning / critical 两档:
| 指标 | warning | critical | 根因方向 | 对应优化阶段§11.2 |
|---|---|---|---|---|
| `faithfulness` | < 0.7 | < 0.5 | 生成未严格基于检索片段 / 幻觉 | 生成 prompt grounding开启校验 |
| `answer_relevancy` | < 0.7 | < 0.5 | 回答偏离问题 / 格式冗余 | 查询改写生成 prompt 格式 |
| `context_recall` | < 0.7 | < 0.5 | 检索遗漏关键信息 | 多查询问题分解Step-back加大过召回 |
| `context_precision` | < 0.6 | < 0.4 | 检索引入过多噪声 / 排序差 | 后检索重排压缩相关性过滤 |
| `noise_sensitivity` | > 0.3 | > 0.5 | 回答被噪声片段干扰(越低越好) | 后检索相关性过滤、重排 |
| `factual_correctness` | < 0.6 | < 0.4 | 回答事实与标准答案偏差大 | 检索与生成综合优化 |
| `semantic_similarity` | < 0.7 | < 0.5 | 回答语义与标准答案差距大 | 生成 prompt检索质量 |
> 注:`noise_sensitivity` 越低越好0=完全不受噪声影响),其阈值方向与其余相反。
### 4.3 低分样本选取
每个触发诊断的指标取该指标分数最低的 **top-3** 样本排除 NaN附入 `Diagnosis.low_samples`字段包含 `sample_id / question / answer / ground_truth / <metric_score>`
---
## 5. LLM 分析器llm_analyzer.py
### 5.1 输入
- `diagnoses: list[Diagnosis]` 规则引擎输出仅触发阈值的指标
- `llm` 已有 RAGAS LLM 实例scenario judge_model
- `scenario_name: str` 用于报告标题
### 5.2 Prompt 设计
使用**一次 LLM 调用**把所有触发诊断的指标和低分样本一起发送
```
你是一个 RAG 系统优化专家,正在分析西门子医疗 CT 文档问答系统的评测结果。
请用中文撰写一份优化建议报告,格式为 Markdown。
## 评测诊断摘要
{for each diagnosis: 指标名、均值、阈值、可能原因、建议动作}
## 低分样本示例
{for each diagnosis: top-3 低分样本的 question / answer / ground_truth}
## 要求
1. 按指标分节(## 指标名),先解释"为什么低",再给出"具体怎么改"
2. "具体怎么改"要结合低分样本的具体内容,而不只是泛泛建议
3. 最后写一节 ## 优先优化次序,按性价比排序(参考:不增加调用次数的优先)
4. 语言简洁,面向工程师,不要废话
```
### 5.3 输出
LLM 返回的 Markdown 字符串直接写入 `optimization_advice.md`在报告头部追加运行元信息)。
### 5.4 失败降级
LLM 调用失败超时/异常降级为**纯规则报告**只输出规则引擎的诊断结构不含 LLM 解读文件照常写出错误信息写入报告末尾不阻断整个评测流程
---
## 6. 写出层writer.py
### 6.1 文件写出
`optimization_advice.md` 结构
```markdown
# 优化建议报告 — <scenario_name>
- run_id: `<run_id>`
- 生成时间: `<timestamp>`
- judge_model: `<model>`
---
<LLM 生成的 Markdown 正文>
```
### 6.2 日志摘要
`run_advisor()` 完成后向 `logger.info` 打印一条精简摘要单行适合 `run_eval.bat` 结束后一眼扫到
```
[advisor] 触发诊断 3 项: faithfulness(0.42, critical) context_recall(0.58, warning) noise_sensitivity(0.41, critical)
[advisor] 优化建议已写出: outputs/online/.../optimization_advice.md
```
---
## 7. YAML 配置
场景文件新增一个顶层字段
```yaml
optimization_advisor: true # 默认 falsetrue 时评测结束后自动生成优化建议
```
后续若需精细配置阈值覆盖top-N 低分样本数可扩展为
```yaml
optimization_advisor:
enabled: true
top_low_samples: 3 # 每个指标取几条低分样本(默认 3
# thresholds: # 可选:覆盖默认阈值
# faithfulness: 0.65
```
本轮实现仅支持 `optimization_advisor: true/false`扩展接口预留但不实现
---
## 8. 测试策略
| 测试 | 文件 | 说明 |
|---|---|---|
| 规则引擎单测 | `tests/test_advisor_rules.py` | 纯函数 LLM覆盖每条规则的 warning/critical 触发NaN 跳过low_samples 选取 |
| writer 单测 | `tests/test_advisor_writer.py` | mock Diagnosis 列表验证 md 文件写出格式和日志输出 |
| 集成可选 | 现有 `tests/test_online_eval.py` | 验证 `optimization_advisor: true` 场景下 advice_md 存在 |
LLM 分析器不写单测依赖网络由集成场景覆盖
---
## 9. 不覆盖(本轮边界)
- 不支持跨版本对比分析只分析本次 run
- 不支持批量场景聚合建议
- 不建设 Web UI 展示
- LLM 分析器 prompt 本轮不做多语言适配直接中文
- advisor 阈值本轮硬编码在 `rules.py`不从 YAML 读取