Agent架构之碎片问句:规则前置过滤+小模型语义补全+JointBERT联合抽取
AI 碎片问句:规则前置过滤、LLM 语义补全与 JointBERT 联合抽取
结论先行:碎片问句处理不是让一个大模型包揽全部工作,而是用一套分层组合方案落地。先用零成本、毫秒级的规则过滤器拦截大部分流量;再把少数存在歧义、信息残缺的短句交给私有化小模型做语义补全和指代消解;最后让 JointBERT 一次推理同步完成意图分类与槽位抽取。
在智能客服、AI 问数、企业知识库问答、投研 AI 助手、AI 报告生成这类业务里,用户输入并不总是结构完整、语句通顺的标准问句,里面会混杂大量半截表述、简写缩写和指代模糊的短句。典型形态包括“它多少钱?”“什么时候发货?”“帮我查”这类内容。
如果直接把碎片文本送入 NLU 模型做识别,经常出现识别失败或结果偏移,下游数据查询、工具调用、报告生成链路会跟着异常。下面把整条链路每个环节的职责、设计考量和线上容易踩到的坑展开说明。
一、碎片问句为什么难
1.1 常见碎片输入类型
碎片文本的形态可以归纳为几类:
- 超短无主体问句:“多少钱?”“产量呢?”,文本里缺少查询对应的目标对象。
- 代词悬空:“它、这个、那个”单独出现,没有前置上下文,无法确定代词绑定的业务实体。
- 半截名词片段:“明天上海到北京”“帮我查下”,句子语义没有收尾,用户诉求表达不完整。
- 缺主体疑问句:“什么时候发车?”,自带疑问诉求,但文本内没有标注询问的目标载体。
- 截断复制文本:末尾附着逗号、顿号、“和”这类连接词,代表输入中途中断,语句本身不闭合。
JointBERT 这类联合建模方案,需要依托完整句子的全局语义,才能稳定完成意图判定和实体边界定位。碎片文本普遍缺失主体信息,容易造成实体漏识别、代词绑定错乱,进而影响意图分类和整条下游链路。
1.2 单一模型方案各自的短板
纯关键词规则只能匹配固定话术模板,用户调整措辞后就无法识别诉求。仅靠 JointBERT 承载意图识别加槽位抽取,在完整问句上指标稳定,但面对碎片输入时 F1 会明显下滑。全链路大模型端到端方案虽然能力更强,但调用成本高、推理等待长,实体边界波动大,结果复现性差。
更合适的工程思路是把能力解耦分工:
这也是行业里比较经典的 BERT + LLM 两级架构:BERT 承接多数常规请求,保障低延迟、低成本;LLM 只处理少量歧义碎片和长尾难例。
二、整体链路:先过滤、后补全、再抽取
整条流水线分为三个阶段:
规则层管控流量规模、压低整体运行成本;LLM 只负责歧义补全这一项工作;JointBERT 稳定输出结构化的意图与槽位结果。三者的边界越清晰,线上问题越容易定位。
三、阶段一:前置规则过滤,第一道成本防线
规则模块本质上是特征打分机制加阈值判定逻辑,不加载任何 AI 模型,只依赖字符串匹配、词表匹配和文本长度统计完成运算,响应时长维持在毫秒级。
它的定位是优先放行不需要补全的正常问句,减少 LLM 调用总量。规则体系无法覆盖全部隐性歧义场景,漏判样本会沉淀为坏案例,后续再依托坏案例持续迭代规则。
3.1 落地可用的判断特征
特征 1:文本长度。
文本长度小于 6 个字为高风险碎片;6 到 15 个字为中风险;大于 15 个字为低风险。例如“多少钱?”只有 4 个字,属于高风险;“明天上海到北京的高铁”共 12 个字,承载的信息相对完整。
特征 2:孤立代词检测。
代词黑名单包括 ["它","这","那","这个","那个","它们","他","她","它的"]。句子里包含代词、同时没有业务实体时,代表上下文缺失,需要送入 LLM 补全。区分示例:
- “它的发车时间?”包含代词、不存在实体,应流转至 LLM。
- “上海到北京的高铁,它的发车时间?”代词搭配实体同步出现,可以直接放行。
特征 3:无核心诉求的半截短句。
维护业务诉求词白名单,例如 ["查","订","买","缴费","转账","查询","生成","介绍","时间","价格"]。短句内不存在诉求词,就判定为碎片样本。
特征 4:疑问词开头、缺少查询主体。
疑问词集合包括 ["为什么","怎么","多少","何时","哪","啥"]。例如“什么时候发车?”以疑问词开头,句子内部没有实体主体,属于碎片样本。
特征 5:业务实体词表匹配。
实体词表来自 NLU 对应的业务 schema,包含车次、地点、账单类型、账户类型等内容。句子命中实体时,上下文信息充足的概率更高;没有命中实体,同时叠加任意一类风险特征,就判定为碎片样本。
词表冷启动阶段可以直接复用 JointBERT 训练集里提取出来的实体;线上持续运行时,再自动扩充词表内容。
特征 6:末尾截断标记。
匹配句子末尾的逗号、顿号、“和”“以及”“还有”,代表输入存在截断。例如“上海到北京和”属于截断类型的碎片样本。
3.2 打分策略与阈值判定
初始分数 score = 0,按下面规则累加:
- 文本长度小于 6:加 3。
- 6 到 15 个字,并且没有命中实体:加 2。
- 包含代词,并且没有实体:加 4。
- 疑问词开头,并且没有实体:加 3。
- 末尾存在截断标记:加 3。
阈值判断为总分大于等于 4 时送入 LLM 补全;低于 4 时直接放行给 JointBERT。
示例推演:
- “它多少钱?”:长度小于 6 加 3,代词无实体加 4,总分 7,流转至 LLM。
- “明天上海到北京的高铁”:命中实体,不存在风险特征,0 分,直接送入 JointBERT。
- “什么时候发车?”:疑问词开头无实体加 3,总分 3,规则放行,属于规则覆盖盲区,后续依靠坏案例迭代优化。
- “上海到北京和”:截断标记加 3,总分 3,分值贴近阈值,可按业务诉求调整。
阈值支持动态调整:想压低 LLM 调用量,可调整到 5 或 6;想减少漏判、接受更高 LLM 开销,可调整到 3。
3.3 参考伪代码
pronouns = {"它","这","那","这个","那个","它们","他","她","它的"}
question_words = {"为什么","怎么","多少","何时","哪","啥"}
cutoff_suffix = {"和","以及","还有",",",","}
entity_vocab = {"上海","北京","高铁","动车","电费","储蓄卡"}
threshold = 4
def need_llm_rewrite(text: str) -> bool:
score = 0
text = text.strip()
l = len(text)
# 长度打分
if l < 6:
score += 3
elif 6 <= l <= 15:
has_entity = any(ent in text for ent in entity_vocab)
if not has_entity:
score += 2
# 代词 + 无实体
has_pronoun = any(p in text for p in pronouns)
has_entity = any(ent in text for ent in entity_vocab)
if has_pronoun and not has_entity:
score += 4
# 疑问词开头无实体
for qw in question_words:
if text.startswith(qw) and not has_entity:
score += 3
break
# 截断后缀
for suf in cutoff_suffix:
if text.endswith(suf):
score += 3
break
return score >= threshold
3.4 规则缺陷与迭代方案
规则体系无法覆盖全部隐性歧义场景,难免出现漏判。拿“什么时候开始?”举例,计算得到的分值可能不满足阈值条件,系统直接放行,JointBERT 识别不到对应实体,整条链路就会失效。
处理方式是埋点采集日志,收集两类样本:
- 规则放行、但 JointBERT 意图识别置信度偏低且槽位为空的样本,也就是规则没有捕获到的歧义句子。
- 判定为碎片并进入 LLM 补全的样本,用于复盘规则触发是否合理。
定期复盘这批样本,补充实体词表、新增判断规则,或直接存入坏案例兜底池。实体词表冷启动时,可以临时下线实体匹配打分项,只靠长度、代词、疑问词判断,代价是 LLM 调用量上升;线上异步采集 JointBERT 识别出的实体后,再自动入库扩充词表。
3.5 可选进阶优化
常规业务场景下,这套打分规则的稳定程度已经足够。若需要进一步增强,可以接入轻量二分类模型,例如 FastText,用几百条标注样本训练,标签分为 0 = 无需补全、1 = 需要补全。这属于非必需优化,不改变主链路结构。
四、阶段二:LLM 语义补全,私有化小模型
只有被规则判定为碎片、存在歧义的输入,才会进入 LLM 语义补全模块。它的职责是依托用户原文补齐上下文、消除指代歧义,输出标准化问句,同时严格禁止编造原文不存在的业务信息。
这是整条链路里唯一的生成式环节,也是幻觉风险集中的位置。Prompt 的约束力度和输出校验逻辑,直接决定补全结果会不会对下游 JointBERT 抽取形成负面干扰。
4.1 Prompt 设计与约束:分域输入 + few-shot 样例
早期 Prompt 更适合大模型零样本调用,直接投喂给 1.5B 到 7B 的私有化小模型时,约束被击穿的概率偏高。主要问题有:
- 没有把对话历史作为独立输入域,模型无法区分“可信上下文”和“禁止脑补的空白信息”。
- 多条否定式约束堆叠,小参数模型对长负向指令的遗忘概率很高。
- 缺少标准正负样例,容易出现过度改写、替换诉求词或凭空生成业务实体。
- 没有强制极简输出,模型偶尔带出解释性文字,导致下游解析失败。
因此,需要把 Prompt 替换为分域输入加 few-shot 约束的版本,并配套低采样参数 temperature = 0.1、top_p = 0.3,再结合后端实体校验形成双层防护。
第一套是单轮版本,默认无历史上下文,绝大多数流量走这一条:
【System Prompt】
你的任务:碎片问句规范化、指代消解,仅做最小修改,禁止新增任何原文不存在的业务实体。
规则(严格按顺序执行):
(1) 只修改悬空代词,不新增地点、车次、金额、账单、时间等实体;没有可信上下文,就保留代词原样,绝不猜测指代对象;
(2) 不改动用户原始查询意图、不替换诉求动词;
(3) 只输出一句规整问句,不加任何解释、备注、引号、markdown;
(4) 如果信息不足无法消解指代,原样返回输入文本。
参考样例:
输入:它多少钱?
输出:它多少钱?
输入:什么时候发车?
输出:什么时候发车?
输入:上海到北京和
输出:上海到北京和
输入:这个订单什么时候发货?
输出:这个订单什么时候发货?
原始问句:{input_text}
输出规范问句:
第二套是多轮上下文版本,只有上游取出对话历史缓存并命中时才启用,分流小部分流量:
【System Prompt】
你的任务:依托提供的可信对话历史,对当前碎片问句做指代消解与规范化。
约束:
(1) 实体只能来自【对话历史】+【当前原始问句】,历史里没有的实体,绝对不能编造;
(2) 只替换悬空代词,最小改动句子,不改变用户诉求;
(3) 历史信息不足以确定指代对象时,原样返回原始问句,禁止脑补;
(4) 仅输出规整后的问句,无额外文字。
参考样例:
对话历史:用户刚才问了上海到北京的高铁
当前输入:它几点发车?
输出:上海到北京的高铁几点发车?
对话历史:无有效业务实体
当前输入:它多少钱?
输出:它多少钱?
【User Prompt】
对话历史(可信上下文):{context_history}
当前原始问句:{input_text}
输出规范问句:
推理参数固定如下,进一步压低幻觉概率:
temperature = 0.1
top_p = 0.3
max_new_tokens = 60
do_sample = False
配套示例:输入“它今天发车时间多少?”,在缺少上下文时,合规输出就是原样返回“它今天发车时间多少?”;如果上游存在缓存上下文可以匹配,才输出“上海到北京的高铁今天发车时间多少?”。
补全完成后必须配套实体校验:拿补全后的句子和原始输入、历史上下文实体集合比对,只要出现原文和历史都不存在的实体,就判定为幻觉,丢弃 LLM 输出,降级使用原始文本送入 JointBERT。
4.2 模型选型策略
这个任务属于短输入、短输出场景,不需要复杂推理和超长上下文,优先选择 1B 到 7B 参数规模模型,搭配 4bit 量化,上限锁定在 7B,不建议上 70B 规模的大模型。
量化优先选择 AWQ、GPTQ 4bit 方案,显存占用可以直接减半。具体选择如下:
| 模型 | 适用场景 |
|---|---|
Qwen2-1.5B-Instruct(4bit) | 中文理解良好,显存占用 1~2GB,适合验证环境 |
Qwen2-7B-Instruct(4bit) | 复杂歧义处理更强,显存占用 4~5GB,适合线上生产 |
Llama3.2-3B、Gemma2-2B | 中文能力偏弱,一般适合英文业务场景 |
T5、Bart(Seq2Seq) | 推理成本更低,但需要针对性微调 |
34B、70B 大模型在短句补全场景里能力过剩,还会失去私有化部署降本的意义。
4.3 硬件与推理优化方案
以 Qwen2-1.5B-Instruct AWQ 4bit 为例:
- 最低验证配置:单卡显存至少 2GB;CPU 只适合低流量测试,不能投入生产环境。
- 线上并发:单张 RTX4090 可以支撑几十路短句补全请求。
- 云服务器选型:优先选择 A10、T4、L4 推理卡,不需要选用昂贵的 A100。
- 推理框架:优先使用 vLLM、SGLang、llama.cpp,不要直接使用原生 Transformers;vLLM 的 PagedAttention 在短生成场景中 QPS 提升明显。
- 参数控制:
max_new_tokens = 40~60,限制输出长度,规避冗余生成。 - 缓存机制:重复碎片问句直接返回缓存结果,减少重复推理。
私有化部署对比商用 API:商用 API 按 token 或调用次数计费;私有化部署是一次性硬件投入,流量规模越大,单条请求分摊成本越低。高流量场景里,两者成本差距可以达到几十倍。
4.4 降本组合策略
- 前置规则过滤保持不变,70% 以上流量可以直接跳过 LLM,这是最关键的降本抓手。
- 严格限制生成 token 长度,降低算力消耗。
- 量化加高性能推理框架组合,拉高单卡吞吐量。
- 开启请求缓存,高频重复问句不再触发模型推理。
- 开启动态批处理,短文本适合攒批推理,提升显卡利用率。
4.5 模型短板与兜底策略
低成本小模型有两个明确短板:
- 幻觉问题:模型容易编造实体。依靠 Prompt 约束加实体校验机制,校验不通过时直接降级返回原始文本。
- 复杂歧义能力上限不足:多层指代、表述绕口的碎片样本,1.5B 模型容易出现补全错误。处理方案是增加质量校验逻辑,异常输出自动降级,并预留少量路由配额,把极少数高难度样本转发 7B 模型兜底。
4.6 分阶段上线路线
- 验证期:
Qwen2-1.5B-Instruct4bit 加llama.cpp或vLLM,低配资源部署,只处理碎片问句,统计补全效果和幻觉率。 - 稳定生产:1.5B 模型效果不满足诉求时,升级
Qwen2-7B-Instruct4bit。 - 长期演进:积累足量“原始碎句 -> 标准问句”平行样本后,微调 T5 或 Bart 替换 LLM,推理成本可以贴近 JointBERT 水平。
这个场景只有少量流量会进入 LLM 补全环节,Qwen2-1.5B-Instruct 4bit 私有化方案性价比更高、硬件门槛偏低;追求更高稳定性时可升级 7B 版本,仍属于低成本私有化路线。
五、阶段三:JointBERT,一次推理完成意图分类与槽位抽取
输入经过放行或 LLM 补全后,语句完整、主体清晰,送入 JointBERT 开展意图识别与槽位抽取。这个模块是整条链路的 NLU 结构化出口,输出意图标签与 BIO 槽位,给下游工具调用、业务路由使用。
选择 JointBERT 而不是独立 BERT-NER 或 LLM 抽取,核心考量是低延迟、低成本以及双任务互相增益,适配高并发线上服务。
5.1 模型选型思路
JointBERT 属于多任务联合建模方案,共享同一个 BERT 或 RoBERTa 编码器,一套基座同时承载两项任务:
- 提取句子向量,完成整句意图分类。
- 提取每个 token 向量,完成 BIO 格式槽位序列标注,并叠加 CRF 模块完成标签语法纠错。
两项任务互相提供监督信号,通过自注意力机制实现语义互通,效果通常优于分开训练的独立模型,同时显存占用和推理延迟可以缩减一半。
模型选择建议:
- 基线版本:
chinese-roberta-wwm-ext加 CRF,全词掩码机制对中文词汇识别更友好。 - 长尾复杂样本:可选用
DeBERTa-v3-base。 - 资源极度受限:可降级为 BiLSTM-CRF。
一般不建议用 LLM 做主链路 NLU,因为推理成本偏高、输出波动大,很难开展稳定指标回归。LLM 只保留作为低置信难例的兜底通道。
5.2 训练阶段要点
训练启动前先对齐业务 NLU schema,稳定意图标签和槽位定义,保障标签互斥、粒度可控。标注统一采用“文本 + 意图标签 + BIO 槽位标签”格式,样本优先取用线上真实日志,混入一部分经过 LLM 补全后的标准问句。
配套银行、订票场景的意图与槽位示例:
| 类型 | 标签 |
|---|---|
| 意图标签 | query_balance、transfer、BookTrain、pay_bill |
| 槽位标签 | amount、payee、depart_time、from_loc、to_loc、bill_type |
训练配套数据增强时,可使用模板生成、同义词替换、大模型辅助扩充样本,但要控制增强带来的噪声。训练使用加权联合损失,搭配梯度裁剪和早停策略。训练完成后留存一套固定测试集,长期保持不变,后续每次迭代都在同一套测试集上运行意图准确率、槽位 P/R/F1,监控模型退化。
5.3 线上推理链路设计
JointBERT 服务独立部署,和 LLM 补全服务解耦,分开扩缩容。预处理采用 FastTokenizer 加 offset 映射,解决子词拆分和原始文本位置对齐问题。模型导出 ONNX 格式,CRF 解码逻辑放到业务层执行,规避 ONNX 动态循环导出的限制;通过 ONNX Runtime 或 Triton 部署,支持动态 batch、FP16 加速,单条推理延迟控制在百毫秒以内。
输入是规整后的问句,输出结构化 JSON。以下面这条补全问句“帮我查下明天上海到北京的高铁”为例:
{
"intent": "BookTrain",
"score": 0.97,
"slots": [
{"text": "明天", "start": 4, "end": 6, "type": "depart_time", "score": 0.97},
{"text": "上海", "start": 6, "end": 8, "type": "from_loc", "score": 0.97},
{"text": "北京", "start": 9, "end": 11, "type": "to_loc", "score": 0.96},
{"text": "高铁", "start": 12, "end": 14, "type": "train_type", "score": 0.96}
]
}
下游拿到意图和槽位后,填充业务槽位,调用订票接口获取数据。
置信度分流策略:
- 高置信度:直接放行。
- 中置信度:触发澄清追问。
- 低置信度:送入 LLM 兜底识别。
5.4 监控与 badcase 回流
监控看板持续观测核心指标:意图识别准确率、槽位抽取空值率、各类槽位召回率、精确率、平均置信度、推理耗时。
识别失败、意图判错、槽位边界切错的样本自动落库作为 badcase。定期批量复盘,补充到标注池,增量微调 JointBERT。链路还需要兜底分支:低置信度样本自动切换到 LLM 识别;关键槽位缺失时返回友好提示,引导用户补充信息,尽量不直接抛异常。
5.5 评测标准
评估不能靠主观感受。固定测试集上重点看四项内容:
- 意图分类是否准确。
- 实体起止位置是否准确。
- 实体分类是否正确。
- 整体意图准确率与槽位 P/R/F1 指标。
六、上线高频坑点与应对方案
| 坑点 | 应对方案 |
|---|---|
| LLM 幻觉编造实体 | Prompt 约束加补全后实体校验,识别新增实体时直接降级 |
| 实体词表冷启动为空 | 初期临时关闭实体匹配打分项,先验证链路;异步采集 JointBERT 识别结果扩充词表 |
| 阈值调优没有参考依据 | 监控 LLM 触发率、槽位空识别占比、规则漏判率,依托线上数据迭代打分权重和阈值 |
| 子词对齐错误 | 使用 BertTokenizerFast 加 return_offsets_mapping,通过字符 offset 完成槽位对齐 |
| CRF 标签非法序列 | CRF 层自动学习标签转移规则,过滤 I 标签前置 O 的非法 BIO 序列 |
| 置信度阈值一刀切 | 按验证集置信度分布划分高、中、低三档,中置信度触发澄清,低置信度流转 LLM 兜底 |
七、方案适用边界
适合场景:
- 用户碎片化输入占比偏高。
- 实体 Schema 固定。
- 对延迟和成本敏感。
- 需要稳定、可观测的抽取链路。
- 典型业务包括 AI 问数、智能客服、知识库问答、订票缴费类对话系统。
不适合场景:
- 业务实体变更频率很高。
- 标注样本数量偏少。
- 用户输入全部是完整问句,甚至可以移除 LLM 补全模块。
- 需要快速验证原型的场景,直接采用 LLM 端到端抽取更省事。
AI 方案不存在绝对标准的答案,选型是否合适,需要依托测试集指标和线上真实数据验证。
结语
碎片问句处理是一套分层组合方案,而不是依靠一个大模型包揽全部工作:
前置规则过滤削减 LLM 调用量,私有化小模型低成本处理歧义补全,JointBERT 稳定输出结构化意图与槽位。三者配合,才能在控制成本的同时,让碎片问句真正进入可运营、可迭代的工程体系。
更多推荐

所有评论(0)