Files
rag/docs/image_processing_flow.md
lacerate551 100d1a06eb init: RAG 知识库服务初始提交
- 后端 API(Flask + Gunicorn)
- RAG 引擎(混合检索 + 云端 Reranker + 引用溯源)
- 文档解析(MinerU + 多格式支持)
- Docker 生产部署配置
- 排除前端项目、敏感配置、模型文件
2026-06-04 17:35:27 +08:00

234 lines
12 KiB
Markdown
Raw Permalink 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.
# 图片处理完整流程分析
## 流程概览
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 图片处理完整流程 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 解析阶段 (MinerU) │
│ ┌─────────────┐ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ PDF/Word │────→│ MinerU 解析 │────→│ MinerUChunk 对象 │ │
│ │ 文件 │ │ parsers/mineru_ │ │ ├── content (文本/标题) │ │
│ └─────────────┘ │ parser.py │ │ ├── chunk_type │ │
│ └──────────────────┘ │ ├── table_html (表格HTML) │ │
│ │ ├── image_path (独立图片) │ │
│ │ └── images (关联图片列表) │ │
│ ↓ │
│ to_page_content() │
│ ↓ │
│ 返回 chunks 列表 │
│ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 2. 存储阶段 (Knowledge Manager) │
│ ┌──────────────────┐ ┌────────────────────┐ ┌───────────────────┐ │
│ │ chunks 列表 │────→│ add_file_to_kb() │────→│ 向量库 metadata │ │
│ │ (MinerUChunk) │ │ knowledge/manager │ │ │ │
│ └──────────────────┘ │ .py │ │ ├── chunk_type │ │
│ │ │ │ ├── source │ │
│ │ ✅ 合并跨页表格 │ │ ├── page │ │
│ │ ✅ 序列化 images │ │ ├── images_json ✅│ │
│ │ ✅ 存储 image_path │ │ └── image_path ✅ │ │
│ └────────────────────┘ └───────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 3. 召回阶段 (RAG 检索) │
│ ┌──────────────────┐ ┌────────────────────┐ ┌───────────────────┐ │
│ │ 用户查询 │────→│ 混合检索 │────→│ 检索结果 │ │
│ │ "表3.1 数据" │ │ search_hybrid() │ │ contexts = [{ │ │
│ └──────────────────┘ │ api/chat_routes.py │ │ "doc": "...", │ │
│ └────────────────────┘ │ "meta": {...} │ │
│ ↓ │ }] │ │
│ ↓ └───────────────────┘ │
│ ┌────────────────────┐ ↓ │
│ │ _extract_rich_media│ ┌───────────────────┐ │
│ │ api/chat_routes.py │ │ 返回给前端 │ │
│ │ │────→│ { │ │
│ │ 读取 images_json │ │ "images": [...],│ │
│ │ 读取 image_path │ │ "tables": [...],│ │
│ │ ✅ 正确处理 │ │ "answer": "..." │ │
│ └────────────────────┘ │ } │ │
│ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
## 关键代码位置
### 1. MinerU 解析 (parsers/mineru_parser.py)
**MinerUChunk 数据结构 (第107-125行)**:
```python
@dataclass
class MinerUChunk:
content: str # 文本内容
chunk_type: str # 类型: text, table, image, equation
page_start: int = 1
page_end: int = 1
title: str = ""
table_html: Optional[str] = None # 表格 HTML如果是表格
image_path: Optional[str] = None # 图片路径(独立图片)
images: Optional[List[Dict]] = None # 关联图片列表: [{"id": "abc.jpg", "order": 1}]
```
**表格切片创建 (第453-469行)**:
```python
chunk = MinerUChunk(
content=table_caption or "表格", # ⚠️ content 只有标题
chunk_type="table",
table_html=table_body, # ✅ 完整表格在 table_html
image_path=img_path, # 表格图片路径
images=table_images # 表格中嵌入的图片
)
```
### 2. 向量库存储 (knowledge/manager.py)
**add_file_to_kb() 核心逻辑 (第228-270行)**:
```python
for i, chunk in enumerate(chunks):
# 1. 获取 chunk_type
chunk_type = getattr(chunk, 'chunk_type', None)
if not chunk_type:
page_info = getattr(chunk, 'page_info', {}) or {}
chunk_type = page_info.get('chunk_type', 'text')
if chunk_type == 'table':
# 2. 表格内容优先使用 table_html
table_md = getattr(chunk, 'table_html', None) or chunk.content
semantic_content = _build_semantic_content_for_table(...)
# 3. 构建 metadata
metadata = {
'chunk_type': chunk_type,
'source': filename,
'page': page_start,
# ...
}
# 4. ✅ 序列化图片信息
if hasattr(chunk, 'images') and chunk.images:
metadata['images_json'] = json.dumps(chunk.images, ensure_ascii=False)
if hasattr(chunk, 'image_path') and chunk.image_path:
metadata['image_path'] = chunk.image_path
```
**跨页表格合并 (第323-420行)**:
```python
def _merge_cross_page_tables(self, chunks: list) -> list:
"""
合并规则:
1. 相邻两个表格切片
2. 页码连续 (page_end + 1 == next.page_start)
3. 第二个表格标题包含"续表"
"""
# 合并 table_html
current.table_html = curr_html + '\n' + next_html
# 合并 image_path 到 images
merged_images = [
{'id': curr_img, 'page': curr_page_end},
{'id': next_img, 'page': next_page_start}
]
current.images = merged_images
```
### 3. 富媒体召回 (api/chat_routes.py)
**_extract_rich_media() 核心逻辑 (第724-843行)**:
```python
def _extract_rich_media(contexts: List[Dict]) -> Dict[str, List]:
images = []
tables = []
for ctx in contexts:
meta = ctx.get("meta", {})
# 1. 独立图片切片 (image_path) - 图片/图表类型
if meta.get("chunk_type") in ("image", "chart") and meta.get("image_path"):
img_id = os.path.basename(meta["image_path"])
images.append({"id": img_id, "url": f"/images/{img_id}", ...})
# 2. 关联图片 (images_json) - 表格/文本嵌入图片
if meta.get("images_json"):
img_list = json.loads(meta["images_json"])
for img_info in img_list:
images.append({"id": img_info["id"], ...})
# 3. 表格图片 (image_path) - 表格类型的图片形式
if meta.get("chunk_type") == "table" and meta.get("image_path"):
img_id = os.path.basename(meta["image_path"])
images.append({"id": img_id, "type": "table_image", ...})
return {"images": images, "tables": tables}
```
## 当前问题分析
### 问题1: 表格显示"0行数据"
**根因**: `_build_semantic_content_for_table()` 接收的 `table_md` 可能是空的
**验证点**:
- MinerU 解析时 `table_html` 是否有值?
- `manager.py` 第240行 `table_md = getattr(chunk, 'table_html', None)` 是否正确获取?
### 问题2: 跨页表格合并不生效
**根因**: 可能是页码不连续或标题匹配失败
**验证点**:
- 检查 `_merge_cross_page_tables()` 的日志输出
- 验证两个表格切片的 `page_end``page_start` 是否连续
### 问题3: 图片重复
**根因**: 可能是 `images_json``image_path` 同时存在导致重复
**验证点**:
- 检查向量库中是否有同时存在 `images_json``image_path` 的切片
- `_extract_rich_media()` 中的去重逻辑是否有效
## 数据存储位置
| 目录 | 用途 |
|------|------|
| `.data/images/` | 全局图片存储(哈希命名,去重) |
| `.data/cache/vlm/` | VLM 图片描述缓存 |
| `.data/docstore/` | 原始表格/图片 JSON 备份 |
| `knowledge/vector_store/chroma/` | ChromaDB 向量数据库 |
## 测试验证步骤
### 1. 检查向量库 metadata
```python
from knowledge.manager import get_kb_manager
kb = get_kb_manager()
coll = kb.get_collection('my_ky')
result = coll.get(limit=10, include=['metadatas'])
for meta in result['metadatas']:
print(f"chunk_type: {meta.get('chunk_type')}")
print(f"images_json: {meta.get('images_json')}")
print(f"image_path: {meta.get('image_path')}")
print("---")
```
### 2. 检查 MinerU 解析结果
```python
from parsers.mineru_parser import parse_with_mineru
result = parse_with_mineru("tests/public/test_report.pdf")
for chunk in result.get('chunks', []):
if chunk.chunk_type == 'table':
print(f"表格标题: {chunk.title}")
print(f"table_html 长度: {len(chunk.table_html or '')}")
print(f"image_path: {chunk.image_path}")
print(f"images: {chunk.images}")
print("---")
```