From fd8b4b6614c373879642af6839a1c2cfc5802920 Mon Sep 17 00:00:00 2001 From: Defeng Date: Fri, 24 Jul 2026 13:53:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20app.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 图片切片改进 --- app.py | 151 +++++++++++++++++---------------------------------------- 1 file changed, 44 insertions(+), 107 deletions(-) diff --git a/app.py b/app.py index 526700f..0c13133 100644 --- a/app.py +++ b/app.py @@ -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 索引创建接口 # ========== ========== ========== ========== ========== ==========