chore: remove 13 stale files and clean up project structure

Removed:
- app.py (deprecated Streamlit UI, replaced by api_server.py + frontend/)
- start_agent_jrxml.py (old launcher, replaced by start.py)
- test_reorder.py, e2e_test.py (ad-hoc/outdated test scripts)
- ocr_raw_positions.json (debug output)
- ARCHITECTURE.md, CODE_GUIDE.md, RAG_INTEGRATION.md, ROADMAP.md (superseded by CLAUDE.md)
- EVALUATION_REPORT.md (auto-generated)
- scripts/init_kb.py (replaced by init_default_kb.py)
- validation_service/validate.bat (redundant, start.py covers it)
- sessions/*.json (34 test session files, already gitignored)

Updated:
- CLAUDE.md: removed stale file entries from key mapping table
- README.md: updated init script reference and removed validate.bat
- .gitignore: removed EVALUATION_REPORT.md entry
This commit is contained in:
2026-05-24 09:07:15 +08:00
parent bd5bfbac2d
commit e362f530ea
14 changed files with 587 additions and 3240 deletions
+586
View File
@@ -0,0 +1,586 @@
# 对话场景遍历文档
> 从 `agent/graph.py` 状态图递归遍历生成,覆盖所有用户意图 → 节点路径 → 退出条件。
> 最后更新: 2026-05-24
---
## 状态图总览
```
┌──────────────────────────────────────────────────┐
│ 修正循环 (最多 MAX_RETRY=5 次) │
│ ┌─────────┐ ┌──────────────┐ ┌────────┐ │
│ │ validate │───→│ explain_error│───→│correct │ │
│ └────┬─────┘ └──────────────┘ │_jrxml │ │
│ │ pass └───┬────┘ │
│ ▼ │ │
│ ┌─────────┐ retry<5 │
│ │finalize │◄────────────────────────────────┘ │
│ └─────────┘ retry>=5 │
└──────────────────────────────────────────────────┘
load_session ──→ process_input ──→ manage_context ──→ save_state_snapshot
classify_intent
┌────────────┬──────────┬────────┬───────────┼───────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼
retrieve modify_jrxml save_ handle_ handle_ handle_ (兜底)
(新建报表) (修改报表) session consult undo reset
│ │ (预览) (咨询) (撤销) (重置)
┌────────┴────┐ │ │ │ │ │
▼ ▼ │ │ │ │ │
generate generate_ │ │ │ │ │
(1-shot) skeleton │ │ │ │ │
│ │ │ │ │ │ │
│ refine_ │ │ │ │ │
│ layout │ │ │ │ │
│ │ │ │ │ │ │
│ map_fields │ │ │ │ │
│ │ │ │ │ │ │
└──────┬──────┘ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
save_session ◄─────┴──────────┘ finalize ◄─── finalize ◄── finalize
│ ▲
│ (预览/导出跳过验证) │
├───────────────────────────────────────┘
│ (其他意图走验证)
validate ──→ explain_error ──→ correct_jrxml ──→ validate (循环)
│ pass │ retry>=MAX
▼ ▼
finalize ────────────────────────────────→ finalize
```
---
## 节点详细清单
每个节点标注了 **代码行号** (`agent/nodes.py``agent/graph.py`)、**前驱节点** (predecessors)、**后继节点** (successors)。
### 1. load_session — 加载会话
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:77` |
| 前驱 | (入口节点, graph entry_point) |
| 后继 | `process_input` (固定边 graph.py:198) |
| 功能 | 从 `sessions/{session_id}.json` 磁盘加载状态,注入 agent_state。不从磁盘覆盖 `session_id`。 |
| LLM | 否 |
### 2. process_input — 处理用户输入
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:98` |
| 前驱 | `load_session` (graph.py:198) |
| 后继 | `manage_context` (graph.py:199) |
| 功能 | 文件解析(PDF/DOCX/XLSX/图片/文本)→ OCR 字段提取 → 批注检测 → 模板 JRXML 解析。注入 `ocr_extraction_result``layout_schema``ocr_elements``uploaded_template_jrxml`。 |
| LLM | 否(OCR 用 PaddleOCR/EasyOCR |
### 3. manage_context — 上下文管理
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:143` |
| 前驱 | `process_input` (graph.py:199) |
| 后继 | `save_state_snapshot` (graph.py:200) |
| 功能 | Token 计数 → 对话压缩(超限时 LLM 压缩为摘要)→ `compressed_history`。 |
| LLM | 是(压缩时调 LLM |
### 4. save_state_snapshot — 状态快照
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:191` |
| 前驱 | `manage_context` (graph.py:200) |
| 后继 | `classify_intent` (graph.py:201) |
| 功能 | 深拷贝当前状态 → 推入 `history_states` 列表。最多保留 5 个快照。撤销时恢复到最新快照。 |
| LLM | 否 |
### 5. classify_intent — 意图分类
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:200` |
| 前驱 | `save_state_snapshot` (graph.py:201) |
| 后继 | 6 路条件分发 (graph.py:204-215) |
| 功能 | LLM 分类用户意图为 8 种之一。prompt: `prompts/intent_classify.md`。 |
| LLM | 是 |
| 路由函数 | `route_by_intent` (graph.py:67) |
**分类逻辑与路由目标**:
| 意图值 | 路由目标 | 说明 |
|--------|---------|------|
| `initial_generation` | → `retrieve` | 新建报表 |
| `modify_report` | → `modify_jrxml` | 修改现有报表 |
| `preview_report` | → `save_session` | 预览(跳过生成) |
| `export_pdf` | → `save_session` | 导出 PDF(跳过生成) |
| `export_jrxml` | → `save_session` | 下载 JRXML(跳过生成) |
| `consult_question` | → `handle_consult` | 咨询问答 |
| `undo_modification` | → `handle_undo` | 撤销 |
| `reset_session` | → `handle_reset` | 重置 |
| 未知/兜底 | 有 `current_jrxml``modify_jrxml`; 无 → `retrieve` | |
### 6. retrieve — RAG/知识库检索
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:442` |
| 前驱 | `classify_intent` (graph.py:204-215, intent=initial_generation) |
| 后继 | 条件分发: `generate_skeleton``generate` (graph.py:218-224) |
| 功能 | ① ErrorKB 检索历史修正案例 → ② KB 模板检索 → ③ KB 字段定义检索。注入 `retrieved_context``kb_template_jrxml``kb_fields`。 |
| LLM | 否(向量搜索 + 字段匹配) |
| 路由函数 | `route_after_retrieve` (graph.py:94) |
**路由逻辑** (`route_after_retrieve`, graph.py:94-99):
- `layout_schema.total_rows > 0``generate_skeleton` (3 阶段)
- 否则 → `generate` (1-shot)
### 7. generate — 1-shot 生成
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:578` |
| 前驱 | `retrieve` (graph.py:218-224, 无 layout_schema 时) |
| 后继 | `save_session` (graph.py:227-231) |
| 功能 | LLM 一次生成完整 JRXML。注入 OCR 上下文 + 模板上下文。流式输出。截断时续写(最多 3 轮)。 |
| LLM | 是 |
| Prompt | `prompts/initial_generation.md` |
### 8. generate_skeleton — 骨架生成(3 阶段-1
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:657` |
| 前驱 | `retrieve` (graph.py:218-224, 有 layout_schema 时) |
| 后继 | `refine_layout` (固定边 graph.py:233) |
| 功能 | 压缩布局 schema → LLM 生成骨架 JRXML。字段用 `$F{field_N}` 占位。流式输出 + 续写。 |
| LLM | 是 |
| Prompt | `prompts/skeleton_generation.md` |
### 9. refine_layout — 坐标精调(3 阶段-2
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:879` |
| 前驱 | `generate_skeleton` (graph.py:233) |
| 后继 | `map_fields` (固定边 graph.py:234) |
| 功能 | ① `decompose_jrxml()` 拆解为 header + bands → ② 每个 band 窗口化(>4000 字符切分)→ ③ 逐窗口 LLM 精调坐标 → ④ `reassemble_jrxml()` 重组 → ⑤ `validate_element_count()` 校验(>10% 回退)。header 完全不发给 LLM。 |
| LLM | 是(N 次,N = band 窗口数) |
| Prompt | `prompts/refine_layout.md` |
### 10. map_fields — 字段映射(3 阶段-3
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:978` |
| 前驱 | `refine_layout` (graph.py:234) |
| 后继 | `save_session` (graph.py:235-239) |
| 功能 | 纯程序化正则替换 `$F{field_N}` → OCR 真实字段名。`_sanitize_field_name()` 净化非 ASCII 字符。零 LLM 调用。 |
| LLM | 否 |
### 11. modify_jrxml — 修改报表
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:1022` |
| 前驱 | `classify_intent` (graph.py:204-215, intent=modify_report) |
| 后继 | `save_session` (graph.py:242-246) |
| 功能 | 基于现有 JRXML + 用户修改描述 + OCR 上下文 + 模板上下文 → LLM 修改。流式输出 + 续写。空响应守卫。 |
| LLM | 是 |
| Prompt | `prompts/modification.md` |
### 12. handle_consult — 咨询解答
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:261` |
| 前驱 | `classify_intent` (graph.py:204-215, intent=consult_question) |
| 后继 | `finalize` (固定边 graph.py:280) |
| 功能 | LLM 回答 JasperReports 相关知识问题。回答写入 `conversation_history`。 |
| LLM | 是 |
| Prompt | `prompts/consult.md` |
### 13. handle_undo — 撤销
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:281` |
| 前驱 | `classify_intent` (graph.py:204-215, intent=undo_modification) |
| 后继 | `save_session` (graph.py:249-253) |
| 功能 | 从 `history_states` 弹出最近快照,恢复 `current_jrxml``conversation_history``status`。无快照时提示"无可撤销状态"。 |
| LLM | 否 |
### 14. handle_reset — 重置
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:309` |
| 前驱 | `classify_intent` (graph.py:204-215, intent=reset_session) |
| 后继 | `finalize` (固定边 graph.py:281) |
| 功能 | 清空所有状态到 `create_initial_state()` 默认值(保留 `session_id``session_name`)。 |
| LLM | 否 |
### 15. save_session — 保存会话
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:325` |
| 前驱 | `generate``map_fields``modify_jrxml``handle_undo``classify_intent`(预览/导出) |
| 后继 | 条件分发: `validate``finalize` (graph.py:256-260) |
| 功能 | 原子持久化会话 JSON (`tempfile + os.replace`)。序列化 `agent_state``sessions/{session_id}.json`。 |
| LLM | 否 |
| 路由函数 | `route_after_save` (graph.py:118) |
**路由逻辑** (`route_after_save`, graph.py:118-123):
- `intent in (preview_report, export_pdf, export_jrxml)``finalize` (跳过验证)
- 其他 → `validate`
### 16. validate — 验证
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:1235` |
| 前驱 | `save_session` (graph.py:256-260)、`correct_jrxml` (graph.py:273-277) |
| 后继 | 条件分发: `finalize``explain_error` (graph.py:263-267) |
| 功能 | ① 结构检查(字段引用一致性/SQL 存在/pageWidth/pageHeight/name)→ ② XSD 校验(可选)→ ③ 像素对比(有上传图片时 Java 渲染 JRXML→PNG + OpenCV SSIM)。 |
| LLM | 否 |
| 路由函数 | `route_after_validate` (graph.py:127) |
**路由逻辑** (`route_after_validate`, graph.py:127-131):
- `status == "pass"``finalize`
- `status == "fail"``explain_error`
### 17. explain_error — 错误解释
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:1310` |
| 前驱 | `validate` (graph.py:263-267, status=fail) |
| 后继 | `correct_jrxml` (graph.py:268-272) |
| 功能 | LLM 将编译错误翻译为自然语言解释。注入 `natural_explanation`。 |
| LLM | 是 |
| Prompt | `prompts/explain_error.md` |
### 18. correct_jrxml — 自动修正
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:1355` |
| 前驱 | `explain_error` (graph.py:268-272) |
| 后继 | 条件分发: `validate``finalize` (graph.py:273-277) |
| 功能 | 基于错误解释 + OCR 上下文 + 模板上下文 → LLM 修正 JRXML。注入 `last_error_case`。去重检测(输入输出相同则 `retry_count+=2`)。 |
| LLM | 是 |
| Prompt | `prompts/correction.md` |
| 路由函数 | `route_after_correct` (graph.py:139) |
**路由逻辑** (`route_after_correct`, graph.py:139-143):
- `retry_count >= MAX_RETRY` (默认5) → `finalize` (放弃修正)
- `retry_count < MAX_RETRY``validate` (重新验证)
### 19. finalize — 最终处理
| 属性 | 值 |
|------|-----|
| 代码位置 | `agent/nodes.py:1452` |
| 前驱 | `validate`(pass)、`correct_jrxml`(retry>=MAX)、`handle_consult``handle_reset``save_session`(预览/导出) |
| 后继 | `END` (graph.py:284) |
| 功能 | 记录 `jrxml_versions` 版本历史。验证通过时设置 `final_jrxml`。失败时记录 `pending_failure_context` 供下次输入自动注入。 |
| LLM | 否 |
---
## 路由函数索引
| # | 路由函数 | 代码位置 | 条件 | 分支 |
|---|---------|---------|------|------|
| R1 | `route_by_intent` | `graph.py:67` | `state.intent` | 6 路: retrieve / modify_jrxml / save_session / handle_consult / handle_undo / handle_reset |
| R2 | `route_after_retrieve` | `graph.py:94` | `layout_schema.total_rows > 0` | 2 路: generate_skeleton / generate |
| R3 | `route_after_generate` | `graph.py:103` | 无条件 | save_session |
| R4 | `route_after_modify` | `graph.py:108` | 无条件 | save_session |
| R5 | `route_after_undo` | `graph.py:113` | 无条件 | save_session |
| R6 | `route_after_save` | `graph.py:118` | `intent in (preview, export)` | 2 路: finalize / validate |
| R7 | `route_after_validate` | `graph.py:127` | `status == "pass"` | 2 路: finalize / explain_error |
| R8 | `route_after_explain` | `graph.py:133` | 无条件 | correct_jrxml |
| R9 | `route_after_correct` | `graph.py:139` | `retry_count >= MAX_RETRY` | 2 路: finalize / validate |
---
## 完整对话场景
### 场景 1: 新建报表 — 1-shot(无布局 schema
**触发**: `intent=initial_generation` + 无图片/无结构化布局
**用户示例**: "帮我生成一个销售报表"、"生成一个包含客户名和金额的表格"
```
load_session nodes.py:77
→ process_input nodes.py:98
→ manage_context nodes.py:143
→ save_state_snapshot nodes.py:191
→ classify_intent nodes.py:200 意图=initial_generation
└─ R1: route_by_intent graph.py:67 → retrieve
→ retrieve nodes.py:442
└─ R2: route_after_retrieve graph.py:94 layout_schema 为空 → generate
→ generate nodes.py:578 LLM 1-shot 生成完整 JRXML
└─ R3: route_after_generate graph.py:103 → save_session
→ save_session nodes.py:325 持久化到磁盘
└─ R6: route_after_save graph.py:118 intent=initial_generation → validate
→ validate nodes.py:1235 结构检查 + XSD + 像素对比
└─ R7: route_after_validate graph.py:127
├─ status=pass → finalize nodes.py:1452 → END ✓
└─ status=fail → explain_error nodes.py:1310
└─ R8 → correct_jrxml nodes.py:1355
└─ R9:
retry<5 → validate (循环)
retry>=5 → finalize → END ✗
```
**LLM 调用**: `classify_intent` + `generate` + 最多 5× (`explain_error` + `correct_jrxml`)
**退出好结局**: `final_jrxml` 有值, `status=pass`
**退出坏结局**: `pending_failure_context` 有值, `retry_count=5`
---
### 场景 2: 新建报表 — 3 阶段分层生成(有布局 schema)
**触发**: `intent=initial_generation` + 上传图片 + OCR 提取到 `layout_schema.total_rows > 0`
**用户示例**: 上传销售单图片 → "根据这个模板生成报表"
```
load_session nodes.py:77
→ process_input nodes.py:98 OCR提取 + 布局分析
→ manage_context nodes.py:143
→ save_state_snapshot nodes.py:191
→ classify_intent nodes.py:200 意图=initial_generation
└─ R1: route_by_intent graph.py:67 → retrieve
→ retrieve nodes.py:442 KB检索模板+字段
└─ R2: route_after_retrieve graph.py:94 layout_schema.total_rows>0 → generate_skeleton
→ generate_skeleton nodes.py:657 阶段1: 骨架JRXML ($F{field_N}占位)
→ refine_layout nodes.py:879 阶段2: Band级窗口化坐标精调
→ map_fields nodes.py:978 阶段3: 程序化字段映射
└─ R3: route_after_generate graph.py:103 → save_session
→ save_session nodes.py:325
└─ R6: route_after_save graph.py:118 → validate
→ validate nodes.py:1235
└─ R7 同场景1的验证循环
```
**内容保护**:
- `refine_layout`: header (field/param/queryString) 完全不发给 LLM
- `refine_layout`: 每窗口 ~4000 字符, LLM 无法重写整个报表
- `map_fields`: 纯正则替换, 零 LLM, 100% 确定性
- `validate_element_count()`: 每阶段后校验, >10% 变化回退
**LLM 调用**: `classify_intent` + `generate_skeleton` + N×`refine_layout`(N=band窗口数) + 可能的修正循环
---
### 场景 3: 修改已有报表
**触发**: `intent=modify_report`(已有 `current_jrxml`
**用户示例**: "把标题字体改大"、"在底部加合计行"、"删除第三列"
```
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=modify_report
└─ R1: route_by_intent graph.py:67 → modify_jrxml
→ modify_jrxml nodes.py:1022 LLM修改现有JRXML
└─ R4: route_after_modify graph.py:108 → save_session
→ save_session nodes.py:325
└─ R6: route_after_save graph.py:118 → validate
→ (同场景1的验证循环)
```
**特殊逻辑**: `correct_jrxml` 去重检测: 输入输出相同 → `retry_count += 2`
---
### 场景 4: 预览 / 导出(跳过验证)
**触发**: `intent in (preview_report, export_pdf, export_jrxml)`
**用户示例**: "预览报表"、"导出 PDF"、"下载 JRXML"
```
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=preview/export
└─ R1: route_by_intent graph.py:67 → save_session
→ save_session nodes.py:325
└─ R6: route_after_save graph.py:118 intent=preview/export → finalize
→ finalize nodes.py:1452 → END ✓
```
**LLM 调用**: 仅 `classify_intent` (1次)
**跳过**: generate / modify_jrxml / validate / correct_jrxml
---
### 场景 5: 咨询问答
**触发**: `intent=consult_question`
**用户示例**: "JasperReports 里 $F 和 $P 有什么区别?"、"怎么设置页脚?"
```
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=consult_question
└─ R1: route_by_intent graph.py:67 → handle_consult
→ handle_consult nodes.py:261 LLM回答
→ finalize nodes.py:1452 → END ✓
```
**LLM 调用**: `classify_intent` + `handle_consult` (2次)
---
### 场景 6: 撤销
**触发**: `intent=undo_modification`
**用户示例**: "撤销"、"回退"、"恢复到修改前"
```
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=undo_modification
└─ R1: route_by_intent graph.py:67 → handle_undo
→ handle_undo nodes.py:281 恢复history_states快照
└─ R5: route_after_undo graph.py:113 → save_session
→ save_session nodes.py:325
└─ R6 → validate → (验证循环)
```
**LLM 调用**: 仅 `classify_intent` (1次)
**特殊**: 无快照时提示"无可撤销状态",不改变当前状态
---
### 场景 7: 重置
**触发**: `intent=reset_session`
**用户示例**: "重置"、"重新开始"、"清空对话"
```
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=reset_session
└─ R1: route_by_intent graph.py:67 → handle_reset
→ handle_reset nodes.py:309 清空到初始状态
→ finalize nodes.py:1452 → END ✓
```
**LLM 调用**: 仅 `classify_intent` (1次)
---
### 场景 8: 兜底路由(未知意图)
**触发**: LLM 分类返回非标准意图
```
load_session → ... → classify_intent → [未知意图]
└─ R1 fallback (graph.py:87-90):
├─ state有current_jrxml → modify_jrxml (走修改路径, →场景3)
└─ state无current_jrxml → retrieve (走生成路径, →场景1/2)
```
---
## AgentState 字段速查
| 字段 | 类型 | 写节点 | 读节点 |
|------|------|--------|--------|
| `intent` | `str` | classify_intent | R1 route_by_intent, R6 route_after_save |
| `current_jrxml` | `str` | generate, generate_skeleton, refine_layout, map_fields, modify_jrxml, correct_jrxml, handle_undo | validate, save_session, finalize |
| `user_input` | `str` | process_input | classify_intent, manage_context |
| `user_modification_request` | `str` | process_input | modify_jrxml |
| `conversation_history` | `list` | process_input, finalize, handle_consult | manage_context, classify_intent, modify_jrxml |
| `full_conversation_history` | `list` | process_input | manage_context |
| `compressed_history` | `str` | manage_context | modify_jrxml, handle_consult |
| `retry_count` | `int` | correct_jrxml, validate | R7 route_after_correct |
| `status` | `str` | validate | R7 route_after_validate, finalize |
| `error_msg` | `str` | validate | explain_error, finalize |
| `natural_explanation` | `str` | explain_error | correct_jrxml |
| `final_jrxml` | `str` | finalize | (用户下载) |
| `jrxml_versions` | `list` | finalize | (前端展示) |
| `last_error_case` | `dict` | correct_jrxml | retrieve |
| `pending_failure_context` | `dict` | finalize | process_input (下次) |
| `layout_schema` | `dict` | process_input | R2 route_after_retrieve, generate_skeleton |
| `ocr_elements` | `list` | process_input | refine_layout, generate_skeleton |
| `ocr_extraction_result` | `dict` | process_input | map_fields, modify_jrxml, correct_jrxml |
| `history_states` | `list` | save_state_snapshot | handle_undo |
| `kb_id` | `str` | process_input | retrieve |
| `kb_fields` | `list` | retrieve | generate_skeleton |
| `uploaded_template_jrxml` | `str` | process_input | generate, generate_skeleton, modify_jrxml, correct_jrxml |
---
## LLM 调用统计
| 场景 | classify | 生成节点 | 窗口数 | 修正循环 | 总计(最小~最大) |
|------|----------|---------|--------|---------|----------------|
| 1-shot 生成 | 1 | generate=1 | - | 0~5×2 | 2 ~ 12 |
| 3 阶段生成 | 1 | skeleton+refine×N | N | 0~5×2 | 2+N ~ 12+N |
| 修改报表 | 1 | modify=1 | - | 0~5×2 | 2 ~ 12 |
| 预览/导出 | 1 | - | - | - | 1 |
| 咨询 | 1 | consult=1 | - | - | 2 |
| 撤销 | 1 | - | - | - | 1 |
| 重置 | 1 | - | - | - | 1 |
> N = band 窗口数。`销售单.jrxml` (73k 字符) 拆解后 N≈17。
---
## 修正循环流程
```
validate ──fail──→ explain_error ──→ correct_jrxml
▲ │
│ retry_count < MAX_RETRY(5) │
└──────────────────────────────────────┘
│ retry_count >= 5
finalize (放弃, 记录pending_failure_context)
```
**修正轮次推进**:
1. `validate` 失败 → `status="fail"`, `error_msg` 有值
2. `explain_error` → LLM 翻译错误 → `natural_explanation` 有值
3. `correct_jrxml` → LLM 修正 → `retry_count += 1`。去重检测:输入输出相同 → `retry_count += 2`
4. `route_after_correct` → retry<5 → 回到 `validate`; retry>=5 → `finalize`
**失败上下文** (`pending_failure_context`): 重试耗尽后记录 `{error_msg, bad_jrxml, retry_count, ts}`,下次用户消息时 `process_input` 自动注入到 prompt。
---
## 边定义索引(graph.py 全部边)
| 类型 | 源节点 | 目标节点 | 位置 |
|------|--------|---------|------|
| 固定边 | load_session | process_input | line 198 |
| 固定边 | process_input | manage_context | line 199 |
| 固定边 | manage_context | save_state_snapshot | line 200 |
| 固定边 | save_state_snapshot | classify_intent | line 201 |
| 条件边 | classify_intent | retrieve / modify_jrxml / save_session / handle_consult / handle_undo / handle_reset | lines 204-215 |
| 条件边 | retrieve | generate / generate_skeleton | lines 218-224 |
| 条件边 | generate | save_session | lines 227-231 |
| 固定边 | generate_skeleton | refine_layout | line 233 |
| 固定边 | refine_layout | map_fields | line 234 |
| 条件边 | map_fields | save_session | lines 235-239 |
| 条件边 | modify_jrxml | save_session | lines 242-246 |
| 条件边 | handle_undo | save_session | lines 249-253 |
| 条件边 | save_session | validate / finalize | lines 256-260 |
| 条件边 | validate | finalize / explain_error | lines 263-267 |
| 条件边 | explain_error | correct_jrxml | lines 268-272 |
| 条件边 | correct_jrxml | validate / finalize | lines 273-277 |
| 固定边 | handle_consult | finalize | line 280 |
| 固定边 | handle_reset | finalize | line 281 |
| 固定边 | finalize | END | line 284 |