- 后端 API(Flask + Gunicorn) - RAG 引擎(混合检索 + 云端 Reranker + 引用溯源) - 文档解析(MinerU + 多格式支持) - Docker 生产部署配置 - 排除前端项目、敏感配置、模型文件
256 lines
6.9 KiB
Python
256 lines
6.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
图片处理完整链路测试
|
|
|
|
测试文件: documents/public_kb/三峡公报_16-30页.pdf
|
|
MinerU 临时输出: .data/mineru_temp
|
|
|
|
测试步骤:
|
|
1. MinerU 解析 → 检查 chunk.images 和 chunk.image_path
|
|
2. 同步到向量库 → 检查 metadata.images_json 和 metadata.image_path
|
|
3. RAG 检索 → 检查图片召回结果
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
# 测试文件路径
|
|
TEST_PDF = "documents/public_kb/三峡公报_16-30页.pdf"
|
|
TEMP_DIR = ".data/mineru_temp"
|
|
KB_NAME = "my_kb"
|
|
|
|
|
|
def step1_mineru_parse():
|
|
"""步骤1: MinerU 解析,检查图片信息"""
|
|
print("\n" + "=" * 60)
|
|
print("步骤1: MinerU 解析")
|
|
print("=" * 60)
|
|
|
|
if not os.path.exists(TEST_PDF):
|
|
print(f"❌ 测试文件不存在: {TEST_PDF}")
|
|
return None
|
|
|
|
print(f"📄 解析文件: {TEST_PDF}")
|
|
|
|
# 清理临时目录
|
|
if os.path.exists(TEMP_DIR):
|
|
shutil.rmtree(TEMP_DIR)
|
|
|
|
# 调用 MinerU 解析
|
|
from parsers.mineru_parser import parse_with_mineru_persistent
|
|
|
|
result = parse_with_mineru_persistent(
|
|
TEST_PDF,
|
|
output_base=TEMP_DIR,
|
|
cleanup_after_image_move=False # 保留临时文件用于检查
|
|
)
|
|
|
|
if not result:
|
|
print("❌ MinerU 解析失败")
|
|
return None
|
|
|
|
chunks = result.get('chunks', [])
|
|
images = result.get('images', [])
|
|
|
|
print(f"\n📊 解析结果:")
|
|
print(f" - 切片数量: {len(chunks)}")
|
|
print(f" - 图片数量: {len(images)}")
|
|
|
|
# 检查 chunk.images 和 chunk.image_path
|
|
chunks_with_images = 0
|
|
chunks_with_image_path = 0
|
|
image_chunks = 0
|
|
|
|
print(f"\n📋 切片图片信息(前 20 个):")
|
|
print("-" * 40)
|
|
|
|
for i, chunk in enumerate(chunks[:20]):
|
|
chunk_type = getattr(chunk, 'chunk_type', 'text')
|
|
has_images = hasattr(chunk, 'images') and chunk.images
|
|
has_image_path = hasattr(chunk, 'image_path') and chunk.image_path
|
|
|
|
if has_images:
|
|
chunks_with_images += 1
|
|
if has_image_path:
|
|
chunks_with_image_path += 1
|
|
if chunk_type in ('image', 'chart'):
|
|
image_chunks += 1
|
|
|
|
print(f"[{i+1}] type={chunk_type}")
|
|
if has_images:
|
|
print(f" images: {chunk.images}")
|
|
if has_image_path:
|
|
print(f" image_path: {chunk.image_path}")
|
|
|
|
print("-" * 40)
|
|
print(f"\n📈 统计:")
|
|
print(f" - 有 images 字段的切片: {chunks_with_images}")
|
|
print(f" - 有 image_path 字段的切片: {chunks_with_image_path}")
|
|
print(f" - 图片类型切片: {image_chunks}")
|
|
|
|
return result
|
|
|
|
|
|
def step2_sync_to_kb(result):
|
|
"""步骤2: 同步到向量库,检查 metadata"""
|
|
print("\n" + "=" * 60)
|
|
print("步骤2: 同步到向量库")
|
|
print("=" * 60)
|
|
|
|
from knowledge.manager import get_kb_manager
|
|
|
|
kb = get_kb_manager()
|
|
|
|
# 检查/创建知识库
|
|
if KB_NAME not in kb.list_collections():
|
|
kb.create_collection(KB_NAME, display_name="测试知识库")
|
|
print(f"✅ 创建知识库: {KB_NAME}")
|
|
else:
|
|
# 清空现有数据
|
|
coll = kb.get_collection(KB_NAME)
|
|
if coll:
|
|
existing = coll.get()
|
|
if existing['ids']:
|
|
coll.delete(ids=existing['ids'])
|
|
print(f"🗑️ 清空知识库: {len(existing['ids'])} 条记录")
|
|
|
|
# 同步文件
|
|
print(f"📤 同步文件: {TEST_PDF}")
|
|
count = kb.add_file_to_kb(KB_NAME, TEST_PDF)
|
|
print(f"✅ 同步完成: {count} 个切片")
|
|
|
|
# 检查 metadata
|
|
coll = kb.get_collection(KB_NAME)
|
|
data = coll.get(include=['metadatas', 'documents'])
|
|
|
|
has_images_json = 0
|
|
has_image_path = 0
|
|
|
|
print(f"\n📋 Metadata 检查(前 20 个):")
|
|
print("-" * 40)
|
|
|
|
for i, meta in enumerate(data['metadatas'][:20]):
|
|
images_json = meta.get('images_json')
|
|
image_path = meta.get('image_path')
|
|
|
|
if images_json:
|
|
has_images_json += 1
|
|
if image_path:
|
|
has_image_path += 1
|
|
|
|
print(f"[{i+1}] type={meta.get('chunk_type')}")
|
|
if images_json:
|
|
print(f" images_json: {images_json[:80]}...")
|
|
if image_path:
|
|
print(f" image_path: {image_path}")
|
|
|
|
print("-" * 40)
|
|
print(f"\n📈 统计:")
|
|
print(f" - 有 images_json 的切片: {has_images_json}")
|
|
print(f" - 有 image_path 的切片: {has_image_path}")
|
|
|
|
# 验证结果
|
|
print(f"\n✅ 验证结果:")
|
|
if has_images_json > 0:
|
|
print(f" ✅ images_json 字段正确写入")
|
|
else:
|
|
print(f" ❌ images_json 字段未写入")
|
|
|
|
if has_image_path > 0:
|
|
print(f" ✅ image_path 字段正确写入")
|
|
else:
|
|
print(f" ⚠️ image_path 字段未写入(可能没有独立图片切片)")
|
|
|
|
return has_images_json > 0 or has_image_path > 0
|
|
|
|
|
|
def step3_rag_recall():
|
|
"""步骤3: RAG 检索,检查图片召回"""
|
|
print("\n" + "=" * 60)
|
|
print("步骤3: RAG 检索图片召回")
|
|
print("=" * 60)
|
|
|
|
from core.engine import get_engine
|
|
|
|
engine = get_engine()
|
|
if not engine._initialized:
|
|
engine.initialize()
|
|
|
|
# 测试查询
|
|
queries = [
|
|
"三峡公报中有哪些图表?",
|
|
"文档中的图片",
|
|
"有哪些数据图表",
|
|
]
|
|
|
|
for query in queries:
|
|
print(f"\n🔍 查询: {query}")
|
|
print("-" * 40)
|
|
|
|
result = engine.search_knowledge(
|
|
query=query,
|
|
top_k=10,
|
|
collections=[KB_NAME]
|
|
)
|
|
|
|
if not result or not result.get('ids'):
|
|
print(" ❌ 无检索结果")
|
|
continue
|
|
|
|
print(f" 📊 检索到 {len(result['ids'])} 个切片")
|
|
|
|
# 统计图片
|
|
images_found = []
|
|
for meta in result.get('metadatas', []):
|
|
images_json = meta.get('images_json')
|
|
image_path = meta.get('image_path')
|
|
|
|
if images_json:
|
|
try:
|
|
imgs = json.loads(images_json)
|
|
images_found.extend(imgs)
|
|
except:
|
|
pass
|
|
if image_path:
|
|
images_found.append({'id': image_path})
|
|
|
|
if images_found:
|
|
print(f" ✅ 召回 {len(images_found)} 张图片:")
|
|
for img in images_found:
|
|
print(f" 📷 {img.get('id', img)}")
|
|
else:
|
|
print(" ⚠️ 未召回图片")
|
|
|
|
|
|
def main():
|
|
print("=" * 60)
|
|
print("图片处理完整链路测试")
|
|
print("=" * 60)
|
|
|
|
# 步骤1: MinerU 解析
|
|
result = step1_mineru_parse()
|
|
if not result:
|
|
return
|
|
|
|
# 步骤2: 同步到向量库
|
|
success = step2_sync_to_kb(result)
|
|
|
|
# 步骤3: RAG 检索
|
|
step3_rag_recall()
|
|
|
|
print("\n" + "=" * 60)
|
|
print("测试完成")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if sys.platform == 'win32':
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
|
|
main()
|