Compare commits

...

6 Commits

Author SHA1 Message Date
panda cacff6f63a fix: ExtractionResult.to_dict() 序列化 all_elements 完整文本元素层 2026-05-25 22:25:23 +08:00
panda 963c5e41c8 fix: nodes.py 调用 detect_annotations 前将 bbox 从 [x_min,y_min,x_max,y_max] 转为 {x,y,w,h}
annotation_detector._correlate_with_ocr 期望 bbox 格式为 {x,y,w,h},
但 OcrTextElement.to_dict() 返回 [x_min,y_min,x_max,y_max]。
Bug3 的根因在 nodes.py 而非 layout_analyzer。
2026-05-25 22:24:29 +08:00
panda c9344a2715 fix: elements -> all_elements 提取完整原始文本元素层 2026-05-25 22:03:53 +08:00
panda 6d5cfaf29a docs: add jaspersoft-fix evaluation report 2026-05-25 12:09:49 +08:00
panda 0839ba92da WIP: uncommitted rag changes and test image 2026-05-25 12:07:28 +08:00
panda 0adae3e06d fix: strip ns0: namespace prefix in _extract_jrxml() 2026-05-25 00:11:43 +08:00
4 changed files with 146 additions and 6 deletions
+114
View File
@@ -0,0 +1,114 @@
# jaspersoft-fix 评测报告
**项目路径**: `D:\Idea Project\jaspersoft-fix`
**评测时间**: 2026-05-25
**评测维度**: 代码质量 · 安全与稳定性 · 工程实践 · 产品设计
---
## 综合评分
| 维度 | 评分 | 主要问题 |
|------|------|----------|
| 代码质量与架构 | 7.3/10 | nodes.py 1709行 God Module、无文件锁并发风险 |
| 安全与稳定性 | P0×1 + P1×2 + P2×4 | llm.log 写全量 prompt、session 并发覆盖、无 magic bytes 校验 |
| 工程实践 | 3.5/5 | 原子写入优秀、trace_id 传播良好、无 E2E 测试 |
| 产品设计 | 4.2/5 | natural_explanation 透明、非 fix 报告误报进度不透明 |
---
## 一、代码质量与架构(7.3/10)
亮点:**原子写入**tempfile+fsync+replace)设计优秀、v5 Band 级分层精确生成架构、前端 Vue3+Pinia 结构清晰。
主要问题:
| 问题 | 严重度 | 说明 |
|------|--------|------|
| nodes.py 过胖 | P1 | 1709行,14个工作流节点,应拆分到 `agent/utils.py` |
| session.py 无文件锁 | P0 | 多用户并发写同一 session 会互相覆盖(无 flock/fcntl |
| 废弃 Vue 组件 | P1 | `StreamingMessage.vue`/`NodeProgress.vue` 仍在 frontend/components |
---
## 二、安全与稳定性
| 等级 | 数量 | 问题 |
|------|------|------|
| **P0** | 1 | `llm.log` 写全量 prompt`prompt[:10000]`),API Key 可能泄露 |
| **P1** | 2 | session 并发无锁(见 P0);文件上传无 magic bytes 校验 |
| **P2** | 4 | LLM prompt 注入风险;ChromaDB 无认证;CORS 宽松;无 API 认证 |
已做好:`.env` 隔离、`sessions/` gitignore、SQL 注入防护(参数化查询)、hex session_id 校验防路径穿越。
**⚠️ llm.log 泄露风险**
`backend/llm.py` 第 47-49 行写 `prompt[:10000]` 到日志,第 66-67 行写 `response[:10000]`。prompt 中若含用户上传的文档内容(包含敏感字段名)或 API 调用上下文,可能被记录。需要脱敏。
---
## 三、工程实践(3.5/5
亮点:原子写入(tempfile+fsync+replace)优秀、日志 trace_id 传播(contextvars)、JSONFormatter 结构化日志、`nodes.py` 的 namespace 检查修复(五轮修正失败根因)。
主要问题:
| 问题 | 严重度 |
|------|--------|
| 会话并发无文件锁 | P0 — 多用户并发写同一 session 会互相覆盖 |
| 无 E2E 测试 | P1 — 无 Playwright 测试 |
| 废弃 Vue 组件未删除 | P1 — `StreamingMessage.vue`/`NodeProgress.vue` |
| 冷启动慢(llm.py 初始化) | P2 |
---
## 四、产品设计(4.2/5
亮点:错误修正循环设计优秀、五轮自动修正+失败上下文注入、`SummaryCard.vue` 正确展示 `natural_explanation`(非 fix 报告误报"进度不透明"是错的)。
主要问题:
| 优先级 | 问题 |
|--------|------|
| P0 | 会话并发无文件锁(影响稳定性) |
| P1 | `export_pdf` 未实现(需标记"敬请期待" |
| P1 | 意图分类无用户确认机制 |
| P2 | 流式输出无 XML 语法高亮 |
| P2 | 空白状态无引导示例 |
---
## 优先修复路线图
### P0(立即修复)
1. **会话并发文件锁**:在 `save_session()``fcntl.flock()` 保护先读后写
2. **LLM 日志脱敏**prompt/response 中截断或替换 API Key 为 `[REDACTED]`
### P1(近期处理)
3. 删除废弃 Vue 组件(`StreamingMessage.vue`/`NodeProgress.vue`
4. 实现 `export_pdf` 或标记"敬请期待"
5. 意图分类结果标签化供用户确认
6. 添加 Playwright E2E 测试
### P2(有空再搞)
7. 流式输出 XML 语法高亮
8. 空白状态引导示例
---
## 与 jaspersoft(非 fix)的关键差异
| 项目 | jaspersoft(非 fix | jaspersoft-fix |
|------|---------------------|----------------|
| commit | `2d5183d` OCR fidelity reform | `0839ba9` WIPrag + test image |
| namespace 前缀 | 未处理 | 已修复 `_extract_jrxml()` |
| 五轮修正失败根因 | 旧评分公式 | 已修复(去掉 field_coverage 权重) |
| OCR 自动发现文档类型 | 需手动 | 已实现 |
| 进度透明度 | 非 fix 报告误报"不透明" | 实际展示 natural_explanation ✅ |
---
*评测时间: 2026-05-25 (Asia/Hong_Kong)*
*评测工具: Mavis AI Agent*
+24 -6
View File
@@ -151,11 +151,22 @@ def process_input(state: AgentState) -> Dict:
# 同时更新工作对话历史中的最后一条
conv_history[-1]["content"] = user_input
# 批注检测(圈选/箭头标记)
elements = ocr_result.get("elements", [])
elements = ocr_result.get("all_elements", [])
if elements:
try:
from backend.annotation_detector import detect_annotations
ann_result = detect_annotations(uploaded_path, elements)
elem_dicts = []
for e in elements:
d = e.to_dict() if hasattr(e, "to_dict") else (e if isinstance(e, dict) else {"text": str(e), "bbox": [], "confidence": 0})
# annotation_detector 期望 bbox 为 {x,y,w,h},但 OcrTextElement.to_dict() 返回 [x_min,y_min,x_max,y_max]
b = d.get("bbox", [])
if isinstance(b, (list, tuple)) and len(b) == 4:
d["bbox"] = {"x": b[0], "y": b[1], "w": b[2] - b[0], "h": b[3] - b[1]}
elif isinstance(b, dict) and "x" not in b:
# 已经是 [x,y,w,h] 形式的 list 但被当成 dict 的情况
d["bbox"] = {"x": b.get(0, 0), "y": b.get(1, 0), "w": b.get(2, 0) - b.get(0, 0), "h": b.get(3, 0) - b.get(1, 0)}
elem_dicts.append(d)
ann_result = detect_annotations(uploaded_path, elem_dicts)
if ann_result.get("total", 0) > 0:
state["annotation_result"] = ann_result
_node_log.info(
@@ -663,14 +674,18 @@ def _format_ocr_context(state: AgentState) -> str:
)
# 所有原始文本(用于表格匹配等需要全文的场景)
elements = ocr_result.get("elements", [])
elements = ocr_result.get("all_elements", [])
if elements:
parts.append("\n全部文本元素(含坐标):")
for e in elements:
bbox = e.get("bbox", {})
x, y, w, h = bbox.get("x", 0), bbox.get("y", 0), bbox.get("w", 0), bbox.get("h", 0)
bbox = e.get("bbox", [])
if isinstance(bbox, list) and len(bbox) >= 4:
x_min, y_min, x_max, y_max = bbox[0], bbox[1], bbox[2], bbox[3]
x, y, w, h = x_min, y_min, x_max - x_min, y_max - y_min
else:
x, y, w, h = 0, 0, 0, 0
parts.append(
f" [{x},{y} {w}×{h}] {e['text']} "
f" [{x},{y} {w}×{h}] {e.get('text','')} "
f"(置信度={e.get('confidence',0):.2f})"
)
@@ -1677,6 +1692,9 @@ def _extract_jrxml(text: str) -> str:
3. 纯 JRXML 无包装
"""
text = text.strip()
# 清理 LLM 输出的 ns0: 命名空间前缀和声明
text = text.replace("ns0:", "")
text = re.sub(r'\s+xmlns:ns0="[^"]*"', "", text)
# 检测并提取 markdown 代码块中的内容
# 如果第一个代码块的内容看起来是完整 JRXML(以 <?xml 或 <jasperReport 开头),
# 则返回它;否则跳过该块,回退到其他提取方式。
+8
View File
@@ -98,6 +98,14 @@ class ExtractionResult:
}
for f in self.fields
],
"all_elements": [
{
"text": e.text,
"bbox": e.bbox,
"confidence": e.confidence,
}
for e in self.all_elements
],
"total_elements": len(self.all_elements),
"errors": self.errors,
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB