更新 app.py

图片切片改进
This commit is contained in:
Defeng 2026-07-24 13:53:04 +08:00
parent 715829d763
commit fd8b4b6614

151
app.py
View File

@ -40,7 +40,7 @@ from pydantic import BaseModel
from dataprocess import _split_markdown
import uuid
import json
from fileparse_util import process_document
from fileparse_util import process_document, extract_image_caption
# import hanlp
from itertools import groupby
import requests
@ -92,7 +92,7 @@ from default_ontology_config import (
get_relationships,
get_relationship_types,
)
from config import NEO4J_CONFIG,URL,API_URLS,API_OTHER_URLS,VLM_CONCURRENCY
from config import NEO4J_CONFIG,URL,API_URLS,API_OTHER_URLS,VLM_CONCURRENCY,SEARCH_DIR
converter = Doc2PDF()
DATA_DIR = "/app/files"
# ================== 全局资源容器 ==================
@ -102,7 +102,6 @@ _resources: Dict[str, Any] = {
"async_driver": None,
"embedder": None,
}
# 图谱检索和索引生成
try:
from graph_search.graph_service import GraphService
@ -1740,7 +1739,7 @@ async def kg_build(request: KGRequest):
except Exception as e:
logger.warning(f"保存 kg_build 配置缓存失败: {e}")
# 后续进程启动逻辑保持不变 ...
# 后续进程启动逻辑保持不变 ...https://gitea.zkzdht.com/Ascend/htknow
cancel_flag_path = f"/tmp/kg_cancel_{task_id}"
if os.path.exists(cancel_flag_path):
os.remove(cancel_flag_path)
@ -2036,7 +2035,10 @@ class Base64ImageRequest(BaseModel):
)
filename: str = Field(..., description="Original image filename, for example test.png")
image_base64: str = Field(..., description="Base64 image content, with or without data:image/... prefix")
original_filename:Optional[str] = Field(
None,
description="图片 所在pdf文件",
)
async def analyze_image_with_vlm(image_bytes: bytes, suffix: str) -> str:
image_url = image_base64_to_data_url(image_bytes, suffix)
@ -2046,7 +2048,11 @@ async def analyze_image_with_vlm(image_bytes: bytes, suffix: str) -> str:
@app.post("/split_image")
async def image_base64(request_data: Base64ImageRequest):
logger.info(f"Received image: {request_data.filename}")
logger.info(f"内容: {request_data.content}")
logger.info(f"pdf文件名: {request_data.original_filename}")
filename = safe_original_filename(request_data.filename)
logger.info(f"filename: {filename}")
suffix = PathLib(filename).suffix.lower()
if suffix not in {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".gif", ".tif", ".tiff"}:
raise HTTPException(status_code=400, detail="Unsupported image type")
@ -2056,6 +2062,8 @@ async def image_base64(request_data: Base64ImageRequest):
raise HTTPException(status_code=400, detail="decoded image is empty")
temp_path = None
image_caption = []
vlm_text = ""
try:
with tempfile.NamedTemporaryFile(delete=False, suffix=processed_suffix) as tmp:
tmp.write(image_bytes)
@ -2068,28 +2076,47 @@ async def image_base64(request_data: Base64ImageRequest):
logger.warning(f"Failed to delete temp image {temp_path}: {e}")
finally:
temp_path = None
if request_data.original_filename:
image_caption = await extract_image_caption(
SEARCH_DIR,
request_data.original_filename,
request_data.filename,
)
caption_text = "\n".join(image_caption).strip()
# image_caption 不为空时使用 caption
# 否则使用原始图片文件名
if caption_text:
caption_text =caption_text+".jpg"
output_filename = caption_text or filename
logger.info(f"image_caption: {caption_text}")
content = (request_data.content or "").strip()
ocr_full_text = (ocr_data.get("full_text") or "").strip()
ocr_full_text = ((ocr_data or {}).get("full_text") or "").strip()
# content 和 OCR 都为空时调用 VLM
if not content and not ocr_full_text:
vlm_text = await analyze_image_with_vlm(image_bytes, processed_suffix)
image_content = "图片文本描述为:" + (vlm_text or "").strip()
elif content and ocr_full_text:
image_content = f"图片上下文内容为:{content}, {ocr_full_text}"
elif content:
image_content = "图片上下文内容为:" + content
else:
image_content = ocr_full_text
vlm_text = await analyze_image_with_vlm(
image_bytes,
processed_suffix,
)
image_content = "图片文件名为:" + filename + ",图片相关内容为:" + image_content
image_related_content = ocr_full_text+ "\n" +content+ "\n"+vlm_text
# image_content = (
# f"{output_filename}\n"
# f"{image_related_content}"
# )
logger.info(f"output_filename: {output_filename}")
logger.info(f"{image_related_content}")
return JSONResponse(
{
"code": 200,
"message": "ok",
"data": {
"image_content": image_content,
"image_content": image_related_content,
"filename": filename,
"jpg_filename": output_filename
},
}
)
@ -2205,7 +2232,7 @@ async def split_result(
slices = []
for ins in contents:
slices.append({"content": ins, "positions": []})
result_data = {"slices": slices, "images": {}}
result_data = {"slices": slices, "images": {},"summary":''}
elif suffix in [".xlsx"]:
# converter.convert(str(temp_input_path), output_dir=str(DATA_DIR))
# temp_base_name = temp_input_path.stem # 如 tmp2bb0l4cn
@ -2293,96 +2320,6 @@ class GraphSearchRequest(BaseModel):
entry_nodes: Optional[Dict[str, Any]] = None
@app.post("/search_graph——1")
async def search_graph(request: GraphSearchRequest, background_tasks: BackgroundTasks):
"""
========== ========== ========== ========== ========== ==========
纯图谱检索
请求参数
- query: 用户查询文本必填
- top_k: 返回用于展示的路径数量默认10
- graph_timeout: 图谱检索超时时间默认2.0暂未使用
- entry_nodes: 入口节点信息可选不提供则从查询中提取
========== ========== ========== ========== ========== ==========
"""
start_time = time.time()
query = request.query
# ========== 参数验证 ==========
if not query or not query.strip():
raise HTTPException(status_code=400, detail="查询文本不能为空")
query = query.strip()
logger.info(f"[search_graph] 图谱检索请求: query='{query}', top_k={request.top_k}")
try:
# ========== 检查依赖是否可用 ==========
if not GRAPH_AVAILABLE:
raise HTTPException(
status_code=503,
detail="图谱检索功能依赖未安装,请安装: pip install neo4j langchain langchain-community neo4j-graphrag jieba",
)
# ========== 初始化图谱服务复用app.py的neo4j driver ==========
# 注意传入app.py中创建的driver实现连接复用
graph_service = GraphService(driver=driver)
# ========== 执行图谱检索(简单版本:只判断统计/非统计) ==========
# 说明:
# - 直接调用 GraphService.search自动判断统计/非统计查询类型
# - query_type="auto" 会自动识别统计类查询(如"多少"、"比例"等)和明细类查询
result = await graph_service.search(
query=query,
entry_nodes=request.entry_nodes,
top_k=request.top_k,
max_attempts=3,
query_type="auto", # 自动判断统计/非统计
)
# ========== 确保返回格式一致 ==========
if not isinstance(result, dict):
result = {
"code": 200,
"message": "success",
"data": result if isinstance(result, (list, dict)) else ([] if isinstance(result, list) else {}),
"meta": {"elapsed_ms": round((time.time() - start_time) * 1000, 2)},
}
# 确保data存在可以是列表或字典新格式包含 nodes、links、results 的对象)
if "data" not in result:
result["data"] = {}
# 注意:不再强制转换为列表,支持新的对象格式
# 统计结果数量(兼容新旧格式)
if isinstance(result.get("data"), list):
result_count = len(result["data"])
elif isinstance(result.get("data"), dict):
# 新格式:统计 results 数组的长度
result_count = len(result["data"].get("results", []))
else:
result_count = 0
logger.info(f"[search_graph] 图谱检索完成: 返回 {result_count} 条结果")
return result
except HTTPException:
raise
except Exception as e:
elapsed = time.time() - start_time
logger.error(f"[search_graph] 图谱检索失败 (耗时: {elapsed * 1000:.2f}ms): {e}", exc_info=True)
return {
"code": 500,
"message": "图谱检索失败",
"data": [],
"meta": {"elapsed_ms": round(elapsed * 1000, 2), "error": str(e), "query_type": "error"},
}
# ========== ========== ========== ========== ========== ==========
# Neo4j 索引创建接口
# ========== ========== ========== ========== ========== ==========