From 94e1b66ec88c5976f05a9a7b58c9bd80a0325b04 Mon Sep 17 00:00:00 2001 From: wsb1224 Date: Sat, 4 Jul 2026 10:12:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=89=A9=E6=B5=81=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=88=AA=E5=9B=BE=E8=AF=86=E5=88=AB=E6=94=B9=E7=94=A8LLM?= =?UTF-8?q?=E8=A7=86=E8=A7=89=E8=83=BD=E5=8A=9B=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91OCR=20SDK=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 服务器未安装alibabacloud_ocr_api20210707 SDK导致识别失败。 改为直接将图片base64发给qwen-plus LLM视觉接口识别表格, 跳过OCR步骤,减少依赖链路。 Co-Authored-By: Claude Sonnet 4.6 --- backend/app/services/order_service.py | 62 +++++++++++++++++++++------ 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/backend/app/services/order_service.py b/backend/app/services/order_service.py index 11d0707..32abd7f 100644 --- a/backend/app/services/order_service.py +++ b/backend/app/services/order_service.py @@ -2400,27 +2400,63 @@ class OrderService: def parse_logistics_table_images(self, image_urls, session): - """识别物流表格截图,用 LLM 从 OCR 文本中提取快递单号和收件人。""" + """识别物流表格截图,用 LLM 视觉能力直接从图片中提取快递单号和收件人。""" + import base64 as _b64 + import json as _json import logging as _log - _logger = _log.getLogger(__name__) - from backend.app.services.ai_service import ai_service + from urllib import request as urllib_request + from backend.app.services.ai_service import read_image_bytes from backend.app.core.config import get_settings + + _logger = _log.getLogger(__name__) settings = get_settings() + api_key = settings.llm_parse_api_key or settings.aliyun_ai_access_key_id + + system_prompt = ( + "你是物流信息提取助手。从物流表格截图中提取每一行的快递单号和收件人姓名。\n" + "表格通常包含:运单号、运单状态、件数、体积、计费重量、付款方式、运费、" + "保价费、包装服务费、信息费、代收货款、签收费、声明价值、产品类型、" + "增值服务、服务方式、业务属性、托寄物、寄件人、收件人、目的网点 等列。\n\n" + "严格按以下 JSON 数组格式输出,不要添加任何其他内容:\n" + '[{"tracking_number":"运单号","express_company":"快递公司名或null","recipient_name":"收件人姓名"}]' + ) + results = [] seen_tracking = set() for url in image_urls: try: _logger.info("[parse_logistics_table_images] 处理图片: %s", url) - ctx = ai_service._ocr_and_parse_image(url) - raw_text = ctx.get("raw_text", "") - _logger.info("[parse_logistics_table_images] OCR原始文本长度: %d, 前500字: %s", - len(raw_text), raw_text[:500]) - if not raw_text.strip(): - _logger.warning("[parse_logistics_table_images] OCR未识别到文字") - continue - # 用 LLM 从表格文本中提取快递单号和收件人 - llm_items = self._llm_extract_logistics(raw_text, settings) - _logger.info("[parse_logistics_table_images] LLM提取结果: %d条", len(llm_items)) + image_bytes = read_image_bytes(url) + image_b64 = _b64.b64encode(image_bytes).decode("utf-8") + suffix = url.rsplit(".", 1)[-1].lower() if "." in url else "jpeg" + mime = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "bmp": "image/bmp"}.get(suffix, "image/jpeg") + + payload = _json.dumps({ + "model": settings.llm_parse_model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": [ + {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{image_b64}"}}, + {"type": "text", "text": "请从这个物流表格截图中提取每一行的快递单号和收件人姓名:"}, + ]}, + ], + "temperature": 0.1, + "max_tokens": 4096, + }).encode("utf-8") + + req = urllib_request.Request( + url=settings.llm_parse_api_url, data=payload, + headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}, + method="POST", + ) + with urllib_request.urlopen(req, timeout=60) as resp: + result = _json.loads(resp.read().decode("utf-8")) + + content = result["choices"][0]["message"]["content"] + _logger.info("[parse_logistics_table_images] LLM响应: %s", content[:500]) + llm_items = self._extract_json_array(content) + _logger.info("[parse_logistics_table_images] 提取结果: %d条", len(llm_items)) + for item in llm_items: tn = (item.get("tracking_number") or "").strip() if not tn or len(tn) < 10 or tn in seen_tracking: