# -*- coding: utf-8 -*- """提示词集中管理模块。 设计目的:把"模型该怎么判断"和"怎么调用模型"分开——本文件只关心提示词内容, llm_client.py 只负责拿着提示词去调接口。以后要调整分类边界、增删细分类目, 只需要改这一个文件,不涉及调用逻辑。 两种模式共用同一套背景知识(什么是产品本体、资料/配件/耗材的定义),只是 模式为"fine"时,额外要求模型在第三行给出细分类目,且 few-shot 的回复也带 上第三行。 细分类目枚举以布莱迪真实历史数据(新参数字段,约2.4万条)为基础人工归纳, 并与业务方逐项确认边界后定稿: - 螺栓/螺母单列"紧固件"一类(不并入法兰隔膜等附属部件)。 - 垫片单列第三大类"耗材/密封件",不算在"配件"里,避免大量小垫片稀释"配件" 标签的信息量(一张订单如果只要了一片垫片,和要了一整套阀组,不该被算作 同等权重的"携带配件")。 - 阻尼帽比照阻尼钉,视为工艺处理,不算配件、不进枚举。 - 合格证明确不计入判断:既不算资料,也不算任何其他附件类型。无论参数文本 里是否提到合格证,一律忽略——不影响"是否携带附件"的判断结果,也不会 出现在细分类目里。这是与此前版本的主要差异之一,few-shot 里同步给出了 "仅有合格证"的反例(否则模型容易在缺少监督信号时把合格证当成资料的一种)。 "其他"兜底类目(可选,默认关闭): 遇到确实是随货交付的附件、但不属于以上任何一个具体细分类目的情况, 如果调用方传入 enable_other=True,模型可以把这类附件归为第四大类"其他", 细分类目也固定填"其他"(不允许模型自己编造具体名称,保持枚举严格校验的 风格不变)。enable_other=False(默认)时,"其他"完全不出现在提示词和 校验枚举里,行为与不支持"其他"之前完全一致——这个参数必须和 parser.parse_llm_output 使用的 enable_other 保持一致,否则会出现 "提示词允许但校验拒绝"或反过来的不一致;classifier.py 负责保证两边一致。 """ from __future__ import annotations # ============================================================ # 细分类目枚举(fine 模式专用,供 parser.py 做严格校验) # ============================================================ DOC_FINE_TYPES: list[str] = [ "出厂检测检验报告", "材质证明类", "说明书", "检验记录过程性", "图纸类", "标定校验证书", "质量证明书", "原产地证明", "检验合格证书综合", "其他交工文件", "营业执照", "型式检验报告", ] PART_FINE_TYPES: list[str] = [ "针型阀", "球阀", "截止阀", "旋塞阀", "角阀", "阀组", "冷凝圈", "冷凝管", "冷凝弯", "虹吸管", "表弯管", "缓冲管缓冲弯", "法兰隔膜", "隔离器", "过压保护器", "散热器散热片", "铅封", "转换接头", "焊接接头短节短管", "卡箍抱箍", "紧固件", "活接头", "接线盒", "电缆插头", "变送器", ] CONSUMABLE_FINE_TYPES: list[str] = [ "垫片", ] # "其他"大类专用:整个大类只有这一个细分值,且值本身就是"其他"——不允许 # 模型编造具体名称,兜底之外仍保持和三大类同样严格的枚举校验风格。 OTHER_CATEGORY = "其他" OTHER_FINE_TYPE = "其他" # 三大类固定存在;"其他"是否作为第四类生效,完全由调用方传入的 enable_other # 决定,默认不生效。 CATEGORY_NAMES = ("资料", "配件", "耗材") # 三大类下所有细分类目的全集(不含"其他") ALL_FINE_TYPES: set[str] = set(DOC_FINE_TYPES) | set(PART_FINE_TYPES) | set(CONSUMABLE_FINE_TYPES) # 细分类目 -> 所属大类(不含"其他") FINE_TYPE_TO_CATEGORY: dict[str, str] = { **{t: "资料" for t in DOC_FINE_TYPES}, **{t: "配件" for t in PART_FINE_TYPES}, **{t: "耗材" for t in CONSUMABLE_FINE_TYPES}, } def valid_categories(enable_other: bool = False) -> set[str]: """返回当前生效的大类合法值集合,enable_other=True 时多一个"其他"。""" return set(CATEGORY_NAMES) | ({OTHER_CATEGORY} if enable_other else set()) def all_fine_types(enable_other: bool = False) -> set[str]: """返回当前生效的细分类目全集,enable_other=True 时多一个"其他"。""" return ALL_FINE_TYPES | ({OTHER_FINE_TYPE} if enable_other else set()) def fine_type_to_category(enable_other: bool = False) -> dict[str, str]: """返回当前生效的"细分类目 -> 大类"映射,enable_other=True 时多"其他"->"其他"。""" if not enable_other: return dict(FINE_TYPE_TO_CATEGORY) return {**FINE_TYPE_TO_CATEGORY, OTHER_FINE_TYPE: OTHER_CATEGORY} # ============================================================ # 背景知识(两种模式共用) # ============================================================ _BACKGROUND = """你是布莱迪压力表公司的订单参数审核助手。你的任务是判断一条订单的"技术参数"文本中,除了压力表产品本体之外,是否还要求交付"附件"。 【背景】公司生产压力表(用于化工行业)。参数文本里大部分内容是描述产品本体的规格,这些都不是附件。 【什么是产品本体(不算附件)】 表壳直径/材质、精度等级、防护等级(IP)、过压保护、弹性元件材质、接头材质、接头螺纹、安装方式、表盘材质/刻度/颜色、量程、充油、玻璃、指针、调零装置、缓冲钉/阻尼钉/阻尼帽(均属工艺处理,不算配件)、结构代号(如513/531)、卡盘尺寸、以及各种打标/示值/红线标识要求。 【附件 = 产品本体之外,随货交付的东西,分三大类】 一、资料类(交付给客户的技术文件): 检测报告、检验报告、出厂报告、材质证明/材质单/材质报告、说明书(使用/安装/维护)、标定证书/校验证书、制造记录/检验记录、图纸、含技术信息的二维码/标签、营业执照、原产地证明、型式检验报告。 ※ 注意:装箱单、送货单、贴箱单、发货章、包装/箱体标注等,属于物流包装指令,不算资料。合格证也不算资料——无论参数文本里是否提到合格证,一律忽略,既不影响"是否携带附件"的判断,也绝不能出现在第三行的具体分类里。 二、配件类(随货的独立实物零件,不含垫片): 针型阀、球阀、截止阀、旋塞阀、角阀、阀组(二/三/五阀组等)、冷凝管/冷凝圈/冷凝弯、表弯管/缓冲弯、虹吸管、过压保护器、散热器/散热片、焊接接头/短节/短管、法兰隔膜、隔离器、铅封、转换接头、卡箍/抱箍、紧固件(螺栓/螺母)、活接头、接线盒、电缆/插头、变送器等,凡是随表一起出货的独立零件都算。 ※ 注意:位号牌、铭牌、标牌、号牌属于标识件,不算配件。缓冲钉/阻尼钉/阻尼帽都是工艺处理,不算配件。 三、耗材/密封件类: 垫片(四氟垫片、紫铜垫片、缠绕垫、密封圈、O型圈等各种材质的垫片),单独列为耗材类,不算在配件里。 【判断步骤】 1. 逐项扫描文本,剔除所有"产品本体"描述,并剔除合格证(合格证不计入判断,见上方资料类说明)。 2. 看剩下的内容里是否出现资料类、配件类或耗材类的东西。 3. 只要出现任一类,第一行就是"是";三类都没有就是"否"。""" _OTHER_BACKGROUND_SUFFIX = """ 四、其他类(兜底类目,仅在本次调用启用该选项时可用): 如果确认订单确实随货交付了某个附件,但它明显不属于以上资料/配件/耗材已列出的任何一个具体细分类目(包括各大类里已有的"其他XX"兜底项,如"其他交工文件""检验合格证书综合"),也无法合理归入其中任何一类,才可以归为"其他"。"其他"是最后的兜底选项——凡是能归到前三类具体细分类目的,都必须归到那里,不要图省事就往"其他"里放。""" def _background(enable_other: bool) -> str: return _BACKGROUND + (_OTHER_BACKGROUND_SUFFIX if enable_other else "") # ============================================================ # coarse 模式:只输出大类 # ============================================================ def _coarse_system_prompt(enable_other: bool) -> str: ordered_categories = list(CATEGORY_NAMES) + ([OTHER_CATEGORY] if enable_other else []) categories_str = "、".join(f'"{c}"' for c in ordered_categories) return ( _background(enable_other) + f""" 【输出格式 —— 必须严格按此格式,只输出这两行,不要多余文字、不要标点符号、不要解释】 是否携带附件: 是 附件类型: 资料,配件 【格式规则】 - 第一行只能是"是否携带附件: 是"或"是否携带附件: 否"。 - 第二行"附件类型: "后面,如果第一行是"否",留空;如果是"是",写{categories_str}中的一个或多个,用英文逗号分隔(不要空格)。 - 不要输出 JSON、不要输出 markdown 代码块、不要输出任何解释性文字。""" ) FEWSHOT_COARSE: list[tuple[str, str]] = [ ( "表壳直径:100mm 表壳材质:304SS 精度等级:1.6级 接头螺纹:M20×1.5 带缓冲钉 513结构", "是否携带附件: 否\n附件类型: ", ), ( "表壳直径:63mm 精度等级:2.5级 带不锈钢位号牌", "是否携带附件: 否\n附件类型: ", ), ( "表壳直径:100mm 张贴好装箱单并注明订单号 送货单盖发货章", "是否携带附件: 否\n附件类型: ", ), ( "接头螺纹:ZG1/4 带阻尼帽 不要阻尼钉", "是否携带附件: 否\n附件类型: ", ), ( "表壳直径:100mm 表壳材质:304SS 合格证随货", "是否携带附件: 否\n附件类型: ", ), ( "表壳直径:100mm 提供出厂检测报告 合格证随货", "是否携带附件: 是\n附件类型: 资料", ), ( "表壳直径:100mm 针型阀材质:304SS 配316L表弯管", "是否携带附件: 是\n附件类型: 配件", ), ( "配四氟垫片 法兰专用四氟垫片", "是否携带附件: 是\n附件类型: 耗材", ), ( "出厂检验报告 仪表使用说明书 配冷凝圈 过压保护器 配四氟垫片", "是否携带附件: 是\n附件类型: 资料,配件,耗材", ), ] # 仅在 enable_other=True 时追加到 FEWSHOT_COARSE 后面 FEWSHOT_COARSE_OTHER: list[tuple[str, str]] = [ ( "表壳直径:100mm 随货附带一个定制不锈钢防雨罩", "是否携带附件: 是\n附件类型: 其他", ), ] # ============================================================ # fine 模式:额外输出第三行细分类目 # ============================================================ def _fine_system_prompt(enable_other: bool) -> str: ordered_categories = list(CATEGORY_NAMES) + ([OTHER_CATEGORY] if enable_other else []) categories_str = "、".join(f'"{c}"' for c in ordered_categories) other_line = ( f"\n其他类细分:{OTHER_FINE_TYPE}(仅当确认是随货附件、但资料/配件/耗材下所有具体" f"细分类目都无法覆盖时才使用,不需要也不允许编造更具体的名称)" if enable_other else "" ) return ( _background(enable_other) + f""" 【细分类目枚举 —— 第三行必须只从下面这些词里选,不能自己编造新词】 资料类细分:{"、".join(DOC_FINE_TYPES)} 配件类细分:{"、".join(PART_FINE_TYPES)} 耗材类细分:{"、".join(CONSUMABLE_FINE_TYPES)}{other_line} 【输出格式 —— 必须严格按此格式,只输出这三行,不要多余文字、不要标点符号、不要解释】 是否携带附件: 是 附件类型: 资料,配件 具体分类: 出厂检测检验报告,针型阀 【格式规则】 - 第一行只能是"是否携带附件: 是"或"是否携带附件: 否"。 - 第二行"附件类型: "后面,如果第一行是"否",留空;如果是"是",写{categories_str}中的一个或多个,用英文逗号分隔(不要空格)。 - 第三行"具体分类: "后面,如果第一行是"否",留空;如果是"是",从上面枚举的细分类目中选出所有识别到的,用英文逗号分隔(不要空格),不能选枚举之外的词,也不能只写大类名称(如"资料")。 - 第三行选出的每个细分类目,都必须能对应到第二行里的某个大类(例如选了"针型阀",第二行就必须包含"配件")。 - 不要输出 JSON、不要输出 markdown 代码块、不要输出任何解释性文字。""" ) FEWSHOT_FINE: list[tuple[str, str]] = [ ( "表壳直径:100mm 表壳材质:304SS 精度等级:1.6级 接头螺纹:M20×1.5 带缓冲钉 513结构", "是否携带附件: 否\n附件类型: \n具体分类: ", ), ( "表壳直径:63mm 精度等级:2.5级 带不锈钢位号牌", "是否携带附件: 否\n附件类型: \n具体分类: ", ), ( "表壳直径:100mm 张贴好装箱单并注明订单号 送货单盖发货章", "是否携带附件: 否\n附件类型: \n具体分类: ", ), ( "接头螺纹:ZG1/4 带阻尼帽 不要阻尼钉", "是否携带附件: 否\n附件类型: \n具体分类: ", ), ( "表壳直径:100mm 表壳材质:304SS 合格证随货", "是否携带附件: 否\n附件类型: \n具体分类: ", ), ( "表壳直径:100mm 提供出厂检测报告 合格证随货", "是否携带附件: 是\n附件类型: 资料\n具体分类: 出厂检测检验报告", ), ( "表壳直径:100mm 针型阀材质:304SS 配316L表弯管", "是否携带附件: 是\n附件类型: 配件\n具体分类: 针型阀,表弯管", ), ( "配四氟垫片 法兰专用四氟垫片", "是否携带附件: 是\n附件类型: 耗材\n具体分类: 垫片", ), ( "带不锈钢螺栓螺母固定 配304SS法兰隔膜", "是否携带附件: 是\n附件类型: 配件\n具体分类: 紧固件,法兰隔膜", ), ( "出厂检验报告 仪表使用说明书 配冷凝圈 过压保护器 配四氟垫片", "是否携带附件: 是\n附件类型: 资料,配件,耗材\n具体分类: 出厂检测检验报告,说明书,冷凝圈,过压保护器,垫片", ), ] # 仅在 enable_other=True 时追加到 FEWSHOT_FINE 后面 FEWSHOT_FINE_OTHER: list[tuple[str, str]] = [ ( "表壳直径:100mm 随货附带一个定制不锈钢防雨罩", "是否携带附件: 是\n附件类型: 其他\n具体分类: 其他", ), ] def get_prompt(mode: str, enable_other: bool = False) -> tuple[str, list[tuple[str, str]]]: """按模式(以及是否启用"其他"兜底类目)返回 (system_prompt, few_shot_examples)。 mode 只接受 "coarse" 或 "fine",其他值直接抛 ValueError——这里不做 宽松兼容,避免拼写错误的 mode 静默退化成某个默认行为。 enable_other 默认 False:不传该参数时,"其他"完全不出现在提示词里, 模型和之后的 parser 校验都不知道这个选项的存在,行为与不支持"其他" 之前完全一致。调用方(llm_client.py)必须把这里用的 enable_other 原样 传给 parser.parse_llm_output,两边必须一致,否则会出现"提示词允许但 校验拒绝"或反过来的不一致。 """ if mode == "coarse": prompt = _coarse_system_prompt(enable_other) fewshot = FEWSHOT_COARSE + (FEWSHOT_COARSE_OTHER if enable_other else []) return prompt, fewshot if mode == "fine": prompt = _fine_system_prompt(enable_other) fewshot = FEWSHOT_FINE + (FEWSHOT_FINE_OTHER if enable_other else []) return prompt, fewshot raise ValueError(f"不支持的分类模式: {mode!r},只能是 'coarse' 或 'fine'")