fix: 智能填单地址解析错误和产品名带型号
Prompt 改进: - 明确 customer_address 只含地理地址,禁止混入产品描述/手机号/订单号 - 明确 product_name 只填材料类型不带型号编号 - specification 必须包含厚度/颜色/尺寸等完整参数 后处理兜底: - 新增 _clean_address 检测地址中混入的非地址内容并重新提取 - 新增 _extract_address_after_phone 从手机号后提取纯地理地址 - 去除地址中的订单号(SYY+5681 等) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d9952cdc25
commit
7351de6a8a
@ -312,21 +312,35 @@ class LLMOrderParser:
|
||||
|
||||
TEXT_SYSTEM_PROMPT = """你是高温布/特氟龙/胶带/输送带行业的订单解析助手。从业务员粘贴的文本中提取订单信息。
|
||||
|
||||
## 输入格式
|
||||
业务员粘贴的文本通常是两列结构,用 tab 或多个空格分隔:
|
||||
## 输入格式(非常重要)
|
||||
业务员粘贴的文本由两列组成,中间用 tab 或多个空格分隔:
|
||||
- 左列:产品描述(厚度 + 材料名 + 尺寸 + 数量 + 工艺要求)
|
||||
- 右列:客户姓名,手机号,地址(备注)
|
||||
|
||||
同一客户可能有多个产品行,每行一个产品。
|
||||
示例:0.13mm 咖色高温布 1*10米 SYY+5681,13711561796,广东省广州市白云区石井街道
|
||||
|
||||
## 产品名称识别
|
||||
从描述中提取材料类型作为产品名称,去除厚度/颜色前缀:
|
||||
- "0.13mm 咖色高温布" → product_name="高温布",specification 包含厚度和颜色
|
||||
必须严格按这个结构拆分,不要把左列的产品信息混入右列的客户地址中。
|
||||
|
||||
## customer_address 规则(非常重要)
|
||||
customer_address 只能包含地理地址信息(省/市/区/县/镇/街道/路/号/楼/室/栋/公司名),绝对不能包含:
|
||||
- 产品描述(厚度、材料名、尺寸等)
|
||||
- 客户姓名和手机号
|
||||
- 订单编号(如 SYY+5681)
|
||||
- 整段原始文本
|
||||
|
||||
## product_name 规则(非常重要)
|
||||
product_name 只填产品材料类型名称,不要带型号编号:
|
||||
- "0.13mm 咖色高温布" → product_name="高温布"
|
||||
- "145g咖色高温布" → product_name="高温布"
|
||||
- "黑色4*4mm网格输送带" → product_name="网格输送带"
|
||||
- "咖色特氟龙胶带" → product_name="特氟龙胶带"
|
||||
- "用凯夫拉布做" → product_name="凯夫拉布"
|
||||
常见产品:高温布、特氟龙胶带、网格输送带、烧烤网格片、凯夫拉布、硅胶布、胶带
|
||||
常见产品名:高温布、特氟龙胶带、网格输送带、烧烤网格片、凯夫拉布、硅胶布、胶带
|
||||
|
||||
## specification 规则
|
||||
specification 必须包含所有产品参数,格式为 "厚度/克重 颜色 标准化尺寸":
|
||||
- "0.13mm 咖色高温布 1*10米" → specification="0.13mm 咖色 1000mm×10000mm"
|
||||
- "145g咖色高温布 25*30厘米 1000张" → specification="145g 咖色 250mm×300mm"
|
||||
|
||||
## 尺寸标准化
|
||||
将所有尺寸统一转换为 "长mm×宽mm" 格式:
|
||||
@ -335,53 +349,43 @@ class LLMOrderParser:
|
||||
- "25*30厘米" → "250mm×300mm"
|
||||
- "长800mm宽250mm" → "800mm×250mm"
|
||||
- "80厘米宽*1米长" → "1000mm×800mm"
|
||||
- "1.25m宽*50m长" → "1250mm×50000mm"
|
||||
|
||||
## 数量单位
|
||||
常用单位:张、个、卷、条、套、片、块
|
||||
|
||||
## 备注提取
|
||||
以下内容应提取到 remark 字段(订单级)或 item.remark(产品级):
|
||||
- 括号中的内容:(不要浅色的)、(单片卷)、(加急安排)
|
||||
- 尾部工艺要求:带回仓库包装、黑膜包边、牛鼻子接头、重叠位置要加固、按图纸生产
|
||||
- 产品级备注放入 items[].remark,订单级备注放入顶层 remark
|
||||
- 括号中的内容:(不要浅色的)、(单片卷)、(加急安排)→ items[].remark
|
||||
- 尾部工艺要求:带回仓库包装、黑膜包边、牛鼻子接头 → items[].remark
|
||||
|
||||
## 严格按以下 JSON 格式输出,不要输出其他内容:
|
||||
## 严格按以下 JSON 格式输出:
|
||||
{{
|
||||
"customer_name": "客户姓名",
|
||||
"customer_mobile": "手机号",
|
||||
"customer_address": "地址",
|
||||
"customer_mobile": "11位手机号",
|
||||
"customer_address": "仅包含地理地址",
|
||||
"order_source": null,
|
||||
"delivery_type": null,
|
||||
"remark": "订单级备注(如配送要求、整体说明)",
|
||||
"remark": "订单级备注",
|
||||
"items": [
|
||||
{{
|
||||
"product_name": "产品名称(材料类型)",
|
||||
"specification": "规格(标准化尺寸 + 厚度/克重等参数)",
|
||||
"product_name": "产品材料类型(不带型号)",
|
||||
"specification": "厚度 颜色 标准化尺寸",
|
||||
"unit": "数量单位",
|
||||
"quantity": 数量数字,
|
||||
"sale_price": null,
|
||||
"remark": "产品级备注(工艺要求、特殊说明)"
|
||||
"remark": "产品级备注"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
## 规则
|
||||
- 手机号必须是 11 位数字,以 1 开头
|
||||
- 数量必须是数字(不是字符串)
|
||||
- 无法识别的字段填 null,不要编造
|
||||
- 如果文本中有多个产品,每个产品一个 items 条目
|
||||
- 同一客户的所有产品共享同一个 customer_name/customer_mobile/customer_address
|
||||
|
||||
## 示例
|
||||
输入: 0.13mm 咖色高温布 1*10米 SYY+5681,13711561796,广东省广州市白云区
|
||||
输出: {{"customer_name":"SYY+5681","customer_mobile":"13711561796","customer_address":"广东省广州市白云区","remark":null,"items":[{{"product_name":"高温布","specification":"0.13mm 咖色 1000mm×10000mm","unit":null,"quantity":null,"sale_price":null,"remark":null}}]}}
|
||||
输入: 0.13mm 咖色高温布 1*10米 SYY+5681,13711561796,广东省 广州市 白云区 石井街道
|
||||
输出: {{"customer_name":"SYY+5681","customer_mobile":"13711561796","customer_address":"广东省广州市白云区石井街道","remark":null,"items":[{{"product_name":"高温布","specification":"0.13mm 咖色 1000mm×10000mm","unit":null,"quantity":null,"sale_price":null,"remark":null}}]}}
|
||||
|
||||
输入: 145g咖色高温布 25*30厘米 1000张 何莹,19932065916,河北省邯郸市大名县
|
||||
输出: {{"customer_name":"何莹","customer_mobile":"19932065916","customer_address":"河北省邯郸市大名县","remark":null,"items":[{{"product_name":"高温布","specification":"145g 咖色 250mm×300mm","unit":"张","quantity":1000,"sale_price":null,"remark":null}}]}}
|
||||
输入: 145g咖色高温布 25*30厘米 1000张 何莹,19932065916,河北省 邯郸市 大名县 园中街瑞盾门业
|
||||
输出: {{"customer_name":"何莹","customer_mobile":"19932065916","customer_address":"河北省邯郸市大名县园中街瑞盾门业","remark":null,"items":[{{"product_name":"高温布","specification":"145g 咖色 250mm×300mm","unit":"张","quantity":1000,"sale_price":null,"remark":null}}]}}
|
||||
|
||||
输入: 用凯夫拉布做,18丝布带。宽度22.8cm,长度56.5,两边重叠6mm 50个(重叠位置要加固) 徐阳飞,15158849480,浙江省杭州市富阳区
|
||||
输出: {{"customer_name":"徐阳飞","customer_mobile":"15158849480","customer_address":"浙江省杭州市富阳区","remark":null,"items":[{{"product_name":"凯夫拉布","specification":"18丝布带 228mm×565mm 重叠6mm","unit":"个","quantity":50,"sale_price":null,"remark":"重叠位置要加固"}}]}}
|
||||
输入: 用凯夫拉布做,18丝布带。宽度22.8cm,长度56.5,两边重叠6mm 50个(重叠位置要加固) 徐阳飞,15158849480,浙江省 杭州市 富阳区 场口镇
|
||||
输出: {{"customer_name":"徐阳飞","customer_mobile":"15158849480","customer_address":"浙江省杭州市富阳区场口镇","remark":null,"items":[{{"product_name":"凯夫拉布","specification":"18丝布带 228mm×565mm 重叠6mm","unit":"个","quantity":50,"sale_price":null,"remark":"重叠位置要加固"}}]}}
|
||||
|
||||
可选的产品库(名称 + 规格):
|
||||
{product_hints}"""
|
||||
@ -1083,6 +1087,16 @@ class AIService:
|
||||
merged = self._build_fallback_result(rule_result)
|
||||
parse_source = "rule"
|
||||
|
||||
# 5.5 地址后处理:清理 LLM 可能混入的非地址内容
|
||||
if merged.get("customer_address"):
|
||||
merged["customer_address"] = self._clean_address(
|
||||
merged["customer_address"], raw_text
|
||||
)
|
||||
if merged.get("customer_mobile") and not merged.get("customer_address"):
|
||||
merged["customer_address"] = self._extract_address_after_phone(
|
||||
raw_text, merged["customer_mobile"]
|
||||
)
|
||||
|
||||
# 6. 客户库预匹配
|
||||
if session is not None and merged.get("customer_mobile"):
|
||||
try:
|
||||
@ -1153,6 +1167,46 @@ class AIService:
|
||||
chunks.append(chunk)
|
||||
return chunks
|
||||
|
||||
_ADDRESS_KEYWORDS = {'省', '市', '区', '县', '镇', '路', '街', '号', '楼', '室', '栋', '村', '巷', '园'}
|
||||
_ORDER_NO_RE = re.compile(r'[A-Z]{2,}[\d\-+]+') # SYY+5681, SO20260604 等订单号
|
||||
|
||||
def _clean_address(self, address: str, raw_text: str) -> str:
|
||||
"""清理 LLM 返回的地址,去除混入的非地址内容。
|
||||
|
||||
如果地址中包含手机号或产品关键词,说明 LLM 没有正确拆分,
|
||||
此时从原始文本中重新提取。
|
||||
"""
|
||||
if not address:
|
||||
return address
|
||||
# 如果地址中包含手机号,说明 LLM 把整段文本塞进了地址
|
||||
if self._PHONE_ANCHOR_RE.search(address):
|
||||
phone_match = self._PHONE_ANCHOR_RE.search(raw_text)
|
||||
if phone_match:
|
||||
return self._extract_address_after_phone(raw_text, phone_match.group(0))
|
||||
return ""
|
||||
# 去除订单号
|
||||
address = self._ORDER_NO_RE.sub('', address).strip()
|
||||
# 去除开头的逗号/空格
|
||||
address = address.lstrip(',,、 \t')
|
||||
# 如果清理后地址关键词密度太低,说明不是有效地址
|
||||
addr_hits = sum(1 for kw in self._ADDRESS_KEYWORDS if kw in address)
|
||||
if addr_hits < 1 and len(address) > 5:
|
||||
# 可能是 LLM 误填,尝试从原始文本提取
|
||||
phone_match = self._PHONE_ANCHOR_RE.search(raw_text)
|
||||
if phone_match:
|
||||
return self._extract_address_after_phone(raw_text, phone_match.group(0))
|
||||
return address
|
||||
|
||||
def _extract_address_after_phone(self, text: str, phone: str) -> str:
|
||||
"""从原始文本中提取手机号之后的地址信息。"""
|
||||
idx = text.find(phone)
|
||||
if idx < 0:
|
||||
return ""
|
||||
after = text[idx + len(phone):].strip()
|
||||
# 去掉开头的逗号/空格/顿号
|
||||
after = after.lstrip(',,、 \t')
|
||||
return after if after else ""
|
||||
|
||||
def _ocr_and_parse_image(self, image_url: str) -> dict:
|
||||
"""调用 OCR 识别图片并进行版面分析。
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user