fix: 物流表格截图识别改用LLM视觉能力,移除阿里云OCR SDK依赖

服务器未安装alibabacloud_ocr_api20210707 SDK导致识别失败。
改为直接将图片base64发给qwen-plus LLM视觉接口识别表格,
跳过OCR步骤,减少依赖链路。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wsb1224 2026-07-04 10:12:36 +08:00
parent a7382188b7
commit 94e1b66ec8

View File

@ -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: