Files
agent_jrxml/CLAUDE.md
T
panda 1210b926c3 fix: MAX_RETRY 5 + rolling continuation + namespace-aware JRXML extraction
- MAX_RETRY: 3→5 (graph.py:35, nodes.py:25) with env override
- Rolling continuation: _generate_with_continuation() auto-detects
  truncated JRXML and sends anchor-based continuation, max 3 rounds
- JRXML extraction: regex/end-tag now namespace-prefix aware
  (ns0:jasperReport, ns:jasperReport, etc.)
- All 5 generation nodes refactored to use continuation helper
- Tests updated: scenario1 accepts ns-prefixed root, max_retry
  verifies graph termination
- stop_reason capture + WARNING log on max_tokens truncation
- Correction prompt now injects OCR context + layout schema
2026-05-23 10:58:46 +08:00

388 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md — JRXML 生成代理
## 项目概述
一个**本地桌面应用**,通过自然语言多轮对话帮助非技术用户创建 JasperReports 模板(JRXML 文件)。核心技术栈:Vue 3 前端 + FastAPI SSE 后端 + LangGraph 状态机 + LLM 生成/修改 + 自动验证修正循环。
**一句话**:用户用中文描述报表需求 → LLM 生成 JRXML → 自动验证 → 失败则自动修正(最多5次) → 重试耗尽后失败上下文自动注入下一轮 → 返回可编译的 JRXML 文件。
## 启动命令
**方式 1 — 一键启动(Windows)**:双击 `start.bat`,自动打开三个窗口分别运行验证服务、后端 API、前端开发服务器。停止用 `stop.bat`
**方式 2 — 手动启动**
```bash
# 终端 1 — 验证服务(必须先启动)
python -m uvicorn validation_service.main:app --port 8001 --host 0.0.0.0
# 终端 2 — 后端 APISSE + REST
python -m uvicorn api_server:app --port 8000 --host 0.0.0.0
# 终端 3 — 前端开发服务器
cd frontend && npm run dev
```
浏览器打开 `http://localhost:5173`
## 当前配置(.env
- **OCR**: PaddleOCR(精确识别首选,ppocr-v4)→ EasyOCR(回退,ch_sim+en),两者均未安装时仅返回图片元信息
- **LLM**: `cloud` / `anthropic` → MiniMax Anthropic 兼容 API (`MiniMax-M2.7`)
- Base URL: `https://api.minimaxi.com/anthropic`
- 认证: Anthropic SDK 自动读取 `ANTHROPIC_API_KEY`fallback `OPENAI_API_KEY`
- **嵌入模型**: `local` / `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`
- **向量库**: ChromaDB 持久化在 `./db/chroma`
- **验证服务**: FastAPI `localhost:8001`
- **日志**: JSON 格式化,`logs/app.log` + `logs/llm.log`,中国时区 (UTC+8)
- **MAX_RETRY**: 5
## 架构
```
前端 (Vue 3 + Vite, 端口 5173)
│ src/
│ ├── api/client.ts SSE 客户端 + fetch 封装
│ ├── stores/chat.ts Pinia: 消息/流式/节点进度
│ ├── stores/session.ts Pinia: 会话管理
│ ├── components/
│ │ ├── Sidebar.vue 会话列表 + 下载 + 历史版本
│ │ ├── ChatMessages.vue 消息列表渲染
│ │ ├── ProcessSection.vue 过程折叠区(替代 StreamingMessage + NodeProgress
│ │ ├── UnifiedInput.vue 统一输入框(文本+文件拖拽/粘贴/芯片)
│ │ └── SummaryCard.vue 结果摘要卡片(含耗时)
│ └── utils/format.ts 工具函数
▼ HTTP + SSE (Server-Sent Events)
api_server.py (FastAPI, 端口 8000)
│ POST /api/sessions/{id}/chat → SSE 流式响应
│ CRUD /api/sessions/... → 会话管理
│ POST /api/upload → 文件上传
│ GET /api/download/... → JRXML 下载
│ GET /api/health, /api/config
│ 包装 LangGraph Agent(不变)──► agent/
validation_service/ (FastAPI, 端口 8001) — 不变
```
## 关键文件映射
| 文件 | 职责 | 修改频率 |
|------|------|---------|
| `api_server.py` | FastAPI SSE 后端,REST API + 流式推送 | **高** |
| `frontend/src/` | Vue 3 聊天 UI(替代旧 app.py | **高** |
| `agent/state.py` | AgentState 类型定义(~28 字段) | 低 |
| `agent/nodes.py` | 18 个工作流节点 + 流式生成 + 错误记录 | **高** |
| `agent/graph.py` | 状态图编译 + 路由函数 + node_start 回调 | 中 |
| `prompts/loader.py` | Prompt 加载器(从 .md 文件热重载) | 低 |
| `prompts/*.md` | 10 个独立 Prompt 模板 | **高** |
| `backend/llm.py` | LLM 工厂,统一 `_BaseLLM` 接口(invoke + stream+ `_LLMLoggingWrapper` | 中 |
| `backend/logger.py` | 集中日志模块:JSON 格式化 + trace_id + 独立 llm.log | 低 |
| `backend/rag_adapter.py` | RAGSearcher 单例,语义搜索接口 | 中 |
| `backend/error_kb.py` | ErrorKB — 错误指纹去重 + ChromaDB 持久化 + 语义检索 | 中 |
| `backend/file_parser.py` | 文件解析: PDF/DOCX/XLSX/XLS/DOC/图片(EasyOCR→PaddleOCR回退)/文本 | 中 |
| `backend/layout_analyzer.py` | A4模板分析: 比例检测/EasyOCR→PaddleOCR元素提取/行分组/JRXML行匹配/布局schema提取 | 中 |
| `backend/ocr_extractor.py` | OCR字段精确提取: 4策略(exact→kv_pair→regex→table_match) + 置信度 | 中 |
| `backend/annotation_detector.py` | 批注检测: 圈选(cv2 HoughCircles) + 箭头(HoughLinesP聚类) + OCR关联 + LLM格式化 | 中 |
| `backend/embeddings.py` | 嵌入模型工厂 (HuggingFace/OpenAI) | 低 |
| `backend/validation.py` | 验证服务 HTTP 客户端 | 低 |
| `backend/session.py` | 会话 JSON 文件 CRUD | 低 |
| `validation_service/main.py` | FastAPI 验证服务 | 低 |
| `scripts/init_kb.py` | 知识库初始化/模型下载 | 低 |
| `app.py` | ~~旧 Streamlit UI~~(已由 api_server.py + frontend/ 替代) | 废弃 |
## 关键约定
1. **LLM 调用接口**: 所有节点通过 `get_llm().invoke(prompt)` 同步调用,或用 `get_llm().stream(prompt)` 流式调用。三个后端(Anthropic/OpenAI/Ollama)通过 `_BaseLLM` 统一接口。
2. **流式生成**: generate/modify_jrxml/correct_jrxml 使用 `get_stream_writer()` 发射自定义事件,UI 通过 `stream_mode=["updates", "custom"]` 捕获逐字输出。
3. **JRXML 提取**: `_extract_jrxml()` 处理 LLM 响应 —— 去掉 markdown 代码块标记,提取 XML 内容。
4. **状态持久化**: 每个会话存为 `sessions/{session_id}.json`LangGraph 节点间通过 AgentState dict 传递。
5. **Token 计数**: 使用 `tiktoken` (gpt-4o encoder) 估算,不管实际模型是什么。
6. **RAG 子模块**: `rag/` 是一个独立的 git submodule,其内部的生成产物 (`models/`, `embeddings/`, `chroma_db/`, `jrxml_source_chunks/`) 不在 git 中。
## Prompt 模板位置
所有 Prompt 在 `prompts/` 目录,`.md` 文件可直接编辑,无需重启应用:
| 文件 | 用途 |
|------|------|
| `prompts/intent_classify.md` | 8 分类意图识别 |
| `prompts/initial_generation.md` | 首次生成 JRXML |
| `prompts/modification.md` | 修改现有 JRXML |
| `prompts/correction.md` | 自动修正错误 |
| `prompts/explain_error.md` | 错误转人话 |
| `prompts/compression.md` | 对话压缩摘要 |
| `prompts/consult.md` | 咨询解答 |
| `prompts/skeleton_generation.md` | 分层生成-骨架 |
| `prompts/refine_layout.md` | 分层生成-精调 |
| `prompts/field_mapping.md` | 分层生成-字段映射 |
## 新增功能 (v2)
### 流式输出 + 节点平铺
- LLM 生成时逐字展示 XML(不再是空白等待)
- 节点以"处理过程"折叠区展开,不相互覆盖
- 完成后自动折叠,展示总结卡片
### 错误自增长知识库
- `backend/error_kb.py` — ChromaDB 集合 `jrxml_error_cases`
- 错误指纹去重(标准化 + MD5):相同结构错误不重复录入
- 记录内容:错误信息 + 修正前后 JRXML + 修正 prompt + 工具链
- `retrieve` 节点自动注入历史修正案例
- 流程:correct_jrxml 保存 last_error_case → validate 通过时自动入库
### 文件上传
- **对话区域上传(v3**: `st.file_uploader` 位于聊天输入框上方,支持图片/PDF/DOCX/XLSX/文本
- **粘贴/拖拽(v3**: 全局 paste/drop 事件监听 + `sessionStorage` + 轮询桥接组件,Ctrl+V 粘贴或拖拽文件到页面任意位置
- **文件预览芯片(v3)**: 上传后显示在对话区域,可逐文件移除(自动清理临时文件)
- 侧边栏多文件上传(可逐文件移除,向后兼容保留)
- 支持: PDF(pdfplumber+PIL) / DOCX(python-docx) / XLSX(openpyxl, v3) / 图片(PIL+EasyOCR优先→PaddleOCR回退) / 纯文本
- 上传文本自动注入下一条消息前缀
- 根据 `can_use_vision()` 判断是否走原生多模态(当前 MiniMax 不支持)
### 对话区域文件粘贴/拖拽技术方案(v3)
- `st.html()` 注入全局 paste/drop/dragover 监听器 → 文件转 base64 → 写入 `sessionStorage`
- `components.html(height=0)` 桥接组件每 800ms 轮询 `sessionStorage``Streamlit.setComponentValue` 回传 Python
- Python 解码 base64 → 临时文件 → `parse_file` + `analyze_layout` 双层 OCR 解析
- 上限:单文件 20MB,单次最多 10 个文件
### A4 模板识别
- `backend/layout_analyzer.py` — 三种处理路径:
- **完整 A4**: 比例匹配 + OCR 元素 → 全量布局描述
- **行片段 + 有现有报表**: 行匹配到 JRXML section → 定位修改
- **行片段 + 无现有报表**: 按 A4 模板生成完整报表
- PaddleOCR(可选安装)提供精确元素位置/字号
- 行分组:Y 轴容差自动聚类;行匹配:文本相似度搜索 JRXML band
### 会话历史下载
- `AgentState.jrxml_versions` 追踪每次生成/修改的版本
- 侧边栏"历史版本"折叠区,每版本独立下载按钮
### 预览修复
- `route_after_save` 新增意图判断:预览/导出跳过验证直通 finalize
### Ctrl+C 修复
- JS 注入拦截 Streamlit 裸 `c` 键清缓存,保留 Ctrl+C 复制
### 结构化日志系统
- `backend/logger.py` — JSON 格式化 + trace_id + 国际时区
- `_LLMLoggingWrapper` — 包装所有 LLM 后端,记录完整 prompt/response
- `@log_node` / `@_log_route` — 装饰器自动记录节点和路由
- 日志分离: `logs/app.log` (业务) + `logs/llm.log` (AI 调用)
## 新增功能 (v3/v4)
### OCR 单据字段精确提取 (v3)
- `backend/ocr_extractor.py` — 4 策略优先级提取: exact_match → kv_pair → regex → table_match
- PaddleOCR 首次识别后将原始结果(含所有文本元素 + bbox坐标)持久化
- `_format_ocr_context()` — 将 OCR 结果(字段 + 原始元素坐标)格式化为 LLM prompt 注入
- OCR 结果在 `modify_jrxml``generate` 节点中自动注入 prompt
- `process_input` 节点在上传图片时自动触发 OCR 字段提取
- 结果持久化到会话文件(`save_session_node` / `load_session_node`
### 多模态聊天输入 + 多格式文件 (v4)
- `app.py``st.chat_input` 替换为 `st_multimodal_chatinput`(支持 Ctrl+V 粘贴 + 拖拽 + 文件按钮)
- `_process_uploaded_file()` — 提取共享文件处理逻辑(侧边栏 + 聊天共用,消除 ~70 行重复代码)
- 新增文件格式支持: XLSX (openpyxl)、XLS (xlrd)、DOC (olefile)
- 剪贴板粘贴文件通过 base64 解码 + MIME type → 扩展名推断
- 侧边栏上传器类型列表中新增 xlsx/xls/doc
### 批注检测 (v4)
- `backend/annotation_detector.py` — 识别用户在手写单据上的圈选和箭头标记
- **圆圈检测**: 红色通道增强 → HoughCircles → 圆形度验证
- **箭头检测**: Canny边缘 → HoughLinesP → 线段方向聚类 → 端点边缘密度判定方向
- **OCR 关联**: 批注与附近 OCR 文本元素关联(15% 图片尺寸内)
- **LLM 注入**: `format_annotation_context()` 将批注结果格式化为中文提示
- `process_input` 节点在 OCR 提取后自动运行批注检测
- `annotation_result` 字段持久化到 AgentState + 会话文件
### OCR 上下文提示增强 (v3/v4)
- `prompts/modification.md` — 新增 `{ocr_context}` 占位符
- `modify_jrxml` 节点 — 将 OCR 上下文注入 modification prompt
- OCR 上下文包含: 结构化字段、全部文本元素(含坐标)、批注检测结果
## 新增功能 (v5)
### 分层精确生成
- 解决 A4 报表图片 OCR 元素过多(数百个)导致 LLM prompt 超长的问题
- **3 阶段管线**(仅对 `initial_generation` + 有布局 schema 时触发):
1. `generate_skeleton` — 压缩的布局 schema → 骨架 JRXML (`$F{field_N}` 占位)
2. `refine_layout` — 采样坐标(表头+首行数据+末行)→ 像素级位置精调
3. `map_fields` — OCR 字段名 → 替换占位符
- `backend/layout_analyzer.py` — 新增 `extract_layout_schema()`: 列聚类 + 区域分类 + schema_text
- `agent/graph.py` — 新增 `route_after_retrieve()`: 有 schema 走 3 阶段,无 schema 走原有 1-shot
- `prompts/` — 新增 `skeleton_generation.md`, `refine_layout.md`, `field_mapping.md`
- 文本请求和所有其他意图零行为变更
## 已知注意点
- **环境变量优先级**: `backend/llm.py` 使用 `load_dotenv(override=True)` 确保 `.env` 值**始终覆盖**系统环境变量。曾因系统级 `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic` 覆盖 `.env` 中的 MiniMax URL,导致 401 认证失败。新增 LLM 相关环境变量时,必须在 `.env` 中显式设置 `ANTHROPIC_*` 变量(而非仅设 `OPENAI_*` fallback),否则残留的系统环境变量会污染请求目标。
- **Anthropic SDK**: 使用原始 `anthropic` 包(非 `langchain-anthropic`),因为需要直连 MiniMax 兼容端点。API Key 优先读 `ANTHROPIC_API_KEY`fallback `OPENAI_API_KEY`。Anthropic SDK 会自动将 key 放入 `x-api-key` header。
- **MiniMax 模型名称**: `MiniMax-M2.7`(不是 `minimax-2.7`),大小写敏感。
- **Streamlit headless**: Windows 下必须设 `STREAMLIT_SERVER_HEADLESS=true` 跳过邮箱采集提示。
- **日志分析**: 通过 `trace_id` 字段可追踪一次请求的全链路。LLM 调用日志在 `logs/llm.log`,包含完整 prompt 和 response(各截断 10000 字符)。
- **验证服务结构检查**: 字段引用一致性 (`$F{field}` vs `<field>` 声明)、SQL SELECT 存在性、pageWidth/pageHeight/name 属性。
- **XSD 校验可选**: 需要 `validation_service/schemas/jasperreport_7_0_6.xsd` 存在。
- **rag 子模块**: 内部有独立的管线脚本(`batch_chunker.py``embed_chunks.py``import_to_chroma.py`),通常不需要在主项目中运行。
- **OCR 引擎**: 优先 PaddleOCR 2.9.x(精确识别,`pip install paddleocr`),回退 EasyOCR 1.7+。两者均未安装时仅返回图片元信息。PaddlePaddle 3.x 在 Windows 上有 ONEDNN bug,固定在 2.6.x。
- **OCR 字段提取**: `process_input` 自动检测上传图片,调用 `OcrExtractor` 提取常见中文字段(发票代码/号码/金额/日期等),提取结果自动注入 LLM 上下文。
- **会话持久化**: `session_id` 现已包含在 `save_session_node` 的持久化字段中,避免切换会话时因 `session_id` 丢失导致的无限 rerun bug。`create_session` 存盘前强制写入 `agent_state["session_id"] = sid``load_session_node` 不从磁盘覆盖 `session_id`。切换会话增加 `_last_switched_to` 哨兵防止重复触发。
- **MAX_RETRY**: 默认 5 次。重试耗尽后 `pending_failure_context` 记录失败信息,下次用户输入时自动注入。
- **验证最小内容检查**: 验证服务额外检查至少 1 个 `<band>` + 1 个 `<textField>``<staticText>`,拦截空壳 JRXML。
- **XLSX 支持 (v3)**: 需要 `openpyxl>=3.1.0`(已加入 requirements.txt)。表格按工作表逐行读取,单元格用 `|` 分隔。
- **粘贴功能限制**: 文件以 base64 编码在 sessionStorage 中传递,单文件上限 20MB。大文件建议使用 file_uploader 按钮。
- **torchvision**: `transformers` 库的懒加载需要 `torchvision`,已作为依赖安装。
- **opencv-python-headless**: 批注检测(圈选/箭头)依赖,通过 `pip install -r requirements.txt` 安装。
- **st-multimodal-chatinput**: Streamlit 聊天输入增强组件,替代 `st.chat_input`,支持粘贴/拖拽文件。返回 base64 编码文件内容。
- **xlwt**: 仅在测试中使用(生成 .xls 测试文件)。
- **分层精确生成**: 3 阶段管线仅在 `layout_schema.total_rows > 0` 时触发。文本请求和 `modify_report` 等意图不受影响,走原有 `generate` 节点。中间阶段(骨架/精调)跳过验证,只有最终 mapped 结果进入 `validate`
## 新增功能 (v6)
### 5-Issue Fix — 图片解析 Bug + 前端功能补全
**Fix 1 — 图片后缀 dot 缺失**: `file_parser.py` 后缀规范化(`"jpg"``".jpg"`),`api_server.py` 使用 `Path.suffix` 替代 `rsplit`。所有图片上传之前均因后缀不匹配回退到文本解析器,OCR/布局分析从未实际触发。
**Fix 2 — Vue 前端功能补全**:
- `ProcessSection.vue` 替代 `StreamingMessage.vue` + `NodeProgress.vue`,使用 `<details>`/`<summary>` 原生可折叠区域
- `Sidebar.vue` 新增历史版本下载列表(`jrxml_versions` 索引下载)
- `UnifiedInput.vue` 已集成文件拖拽/粘贴/芯片/移除(v5 已完成)
**Fix 3 — OCR 两层日志**: `agent/nodes.py` 新增 `_log_ocr_layers()``[内容层]` OCR 文本+字段提取,`[位置层]` 布局 schema 列×行+区域分类,`[合并]` 管线选择(3阶段 vs 单阶段)
**Fix 4 — 全过程流式输出+自动折叠**:
- `api_server.py` `node_start` 事件携带 `step_index`
- `chat.ts` 新增 `ProcessSection[]` 模型:per-section stream routing、完成自动折叠、运行中自动展开
- `ProcessSection.vue` 渲染步骤编号/标签/耗时/内容(XML 代码高亮)
**Fix 5 — 消息耗时显示**: `api_server.py` `agent_complete` 事件新增 `total_duration_ms``SummaryCard.vue` 显示总耗时,`chat.ts` 暴露 `lastDurationMs` + `formatDuration()`
## 已安装的 Claude Code 插件/Skills
| 插件 | 来源 | 关键 Skill |
|------|------|-----------|
| `superpowers` | `obra/superpowers-marketplace` | `tdd-workflow`(红-绿-重构)、`verification-loop`(修复验证)、`systematic-debugging`(根因分析) |
| `example-skills` | `anthropics/skills` | `webapp-testing`Playwright E2E 浏览器自动化)、`skill-creator` |
**测试工作流**:需求澄清 → TDD 红-绿-重构 → `webapp-testing` 浏览器验证 → `verification-loop` 确认 → 提交。
**E2E 测试前置条件**Chrome 已安装 (`C:\Program Files\Google\Chrome\Application\chrome.exe`)Playwright MCP Bridge 扩展需手动安装。
## 更新 (v7 — 2026-05-22)
### 会话持久化 & 多轮对话记忆修复
**原子写入** (`backend/session.py`): `save_session` 改用 tempfile + os.replace 原子写入,防止进程崩溃时 JSON 截断导致会话损坏。
**graph.stream 状态修复** (`api_server.py`): LangGraph 的 `graph.stream()`
只产出事件,不修改传入的 `agent_state``_run_graph_sync` 改为手动收集每个节点的
返回 dict 并 `agent_state.update()`,确保 done 事件到达时 agent_state 已是完整状态。
此修复解决了第二次请求时 `current_jrxml` 为空、导致多轮对话"失忆"的问题。
**save_session 调用时机**: 从 `stream_and_save` 末尾移至 `_sse_generator` 中 done 分支
yield `agent_complete` 之前),消除前端 `refreshFromApi()` 的竞态。
### OCR 管线打通
**uploaded_file_path 传递** (`api_server.py`): `_process_files` 返回的 `uploaded_paths`
注入 `agent_state["uploaded_file_path"]`,使 `process_input` 节点的 `OcrExtractor` 字段
精确提取和 `annotation_detector` 批注检测得以触发。此前 `uploaded_file_path` 始终为空,
第二层 OCR 从未执行。
### 前端体验改进
**下载区常驻** (`Sidebar.vue`): 下载区域始终可见,无文件时显示灰色"暂无下载文件",
生成完成后自动出现下载链接。
**侧边栏自动刷新** (`stores/session.ts`, `App.vue`): 新增 `refreshFromApi()` 方法,
`agent_complete` 后自动从 API 重新加载会话状态,下载按钮无需手动刷新即可出现。
**节点进度完整展示** (`api_server.py`): 移除 `node_complete` 事件的 SKIP_NODES 过滤,
所有节点(包括加载会话等内部节点)的 start/complete 事件均正常发送,前端可看到
完整流转(running → done)。
### modification_request 宽松化
原有 `status == "pass"` 条件去除:只要 `current_jrxml` 存在即设置
`user_modification_request`,确保修改意图的请求能携带完整上下文。
## 更新 (v8 — 2026-05-22)
### Prompt 花括号转义修复
**问题**: `skeleton_generation.md``$F{field_1}` 是给 LLM 看的占位字段名指令,
但 Python `.format()``{field_1}` 当作格式化占位符,因缺少对应 kwarg 抛出 `KeyError: 'field_1'`
所有图片上传触发的 `generate_skeleton` 节点均因此崩溃。
**修复**: 3 个 prompt 文件中 6 处 `{field_N}` / `{...}` 转义为 `{{field_N}}` / `{{...}}`
- `prompts/skeleton_generation.md``$F{field_1}``$F{{field_1}}`
- `prompts/field_mapping.md` — 4 处
- `prompts/refine_layout.md` — 1 处
Python 将 `{{` 输出为字面量 `{`LLM 看到的内容不变。
## 更新 (v9 — 2026-05-22)
### 测试基础设施全面补齐
**单元测试** (76 测试):
- `tests/test_session.py` — 27 测试:会话 CRUD、原子写入、唯一 ID、损坏 JSON 跳过
- `tests/test_error_kb.py` — 24 测试:指纹去重、关键词提取(中/英/JRXML)、ErrorKB CRUD、搜索、统计
- `tests/test_agent.py` — 5 个软断言强化为严格断言(`status`/`current_jrxml` 存在性检查)
- 已有测试:`test_ocr_extraction.py`49)、`test_layered_generation.py`19)、`test_validation.py`6)、`test_file_parser_formats.py`4)、`test_annotation_detector.py`7)、`test_e2e_ocr.py`3
**集成测试** (25 测试, `tests/test_api_integration.py`):
- FastAPI TestClient 全覆盖:健康检查、配置、会话 CRUD、文件上传、下载、Chat SSE、安全边界(路径穿越/非法 JSON/大 payload
- Mock LangGraph graph 避免真实 LLM 调用
**E2E 测试** (8 测试, `frontend/tests/e2e/main-flows.spec.ts`):
- Playwright 浏览器自动化:页面加载、侧边栏、会话管理、聊天流程、输入 UX
- 全量 API Mock`page.route`)无需后端运行
- 配置: `frontend/playwright.config.ts`, `npm run test:e2e`
**运行测试**:
```bash
# 全部单元+集成测试
cd D:\Idea Project\jaspersoft && python -m pytest tests/ -v
# 仅 E2E(需要前端 dev server
cd frontend && npx playwright test
```
### Bug 修复: create_session 参数缺失
`backend/session.py``create_session()` 新增可选参数 `session_id: Optional[str] = None`
`api_server.py:507` 调用 `create_session(session_id=session_id)` 时之前会抛出 `TypeError`
## 更新 (v10 — 2026-05-23)
### 5-Fix — 生成可靠性全面加固
**问题诊断**: 上传车辆历史卡片图片后,`map_fields` 节点 LLM 返回 0 字符,导致 ~11,500 字符的骨架 JRXML 被空字符串覆盖,修正循环无法恢复,最终输出 934 字符的占位桩(与原始图片内容完全不符)。
**Fix 1 — 空响应保护**: 所有 5 个生成节点(`generate_skeleton`, `refine_layout`, `map_fields`, `modify_jrxml`, `correct_jrxml`)增加空响应守卫。LLM 返回空字符串时拒绝更新 `current_jrxml`,保留前一有效版本。
**Fix 2 — max_tokens 扩容**: `backend/llm.py``max_tokens` 从 4096 → 8192。MiniMax-M2.7 支持最大 131K 输出 token8192 在生成复杂 JRXML(通常 5000-15000 字符)时提供充裕空间。
**Fix 3 — 快照回退**: 5 个生成节点在 LLM 输出 JRXML 短于 200 字符时,回退到生成前的 `prev_jrxml` 版本,防止 LLM 输出无意义短文本污染状态。
**Fix 4 — 修正循环注入 OCR 上下文**: `correct_jrxml` 节点将 OCR 提取结果(`ocr_context`)和布局 schema`layout_schema_text`)注入修正 prompt。此前修正节点"盲修"——只看到 JRXML 和编译错误,不理解原始单据的字段结构和布局意图。
**Fix 5 — 滚动续写机制**: 当 LLM 输出因 `max_tokens` 限制被截断(JRXML 不以 `</jasperReport>` 结尾),自动发送续写请求(附最后 800 字符锚点),最多 3 轮(1 正常 + 2 续写)。
- `backend/llm.py``MiniMaxLLM.stream()` 捕获 `stop_reason``_LLMLoggingWrapper``max_tokens` 截断时记录 WARNING
- `agent/nodes.py` — 新增 `_generate_with_continuation()` 辅助函数,5 个生成节点全部重构使用
- `_extract_jrxml()` — 正则表达式支持命名空间前缀 JRXML(`<\w+:jasperReport`
- 内容去重:续写文本直接拼接,依赖 `_extract_jrxml` 提取完整 XML
**MAX_RETRY 调整**: 默认值从 3 → 5(环境变量 `MAX_RETRY`),配合续写机制确保复杂报表有充分修正机会。
**JRXML 提取命名空间兼容**: `_extract_jrxml()``_generate_with_continuation()` 的完整性检查统一支持 `</ns0:jasperReport>` 等命名空间前缀闭合标签。