1. 问题背景:用户输入不规范带来的挑战
在大宗商品交易、电商搜索、客服系统等实际业务场景中,用户输入的不规范性是一个普遍存在的痛点。以螺纹钢为例,用户可能会使用"钢筋"、"rebar"、"建筑钢材"等多种表达方式。这种表达多样性给系统理解带来了巨大挑战。
我在金融数据平台工作时,曾遇到一个典型案例:某机构客户查询"HRB400价格走势",而我们的标准化商品名是"螺纹钢HRB400"。由于系统只做了简单的关键词匹配,导致查询结果为空。后来通过日志分析发现,用户还会使用"热轧带肋钢筋"、"变形钢筋"等专业术语,以及"工地用的螺纹钢"等口语化表达。
关键问题在于:用户思维是概念驱动的,而传统系统是字符串匹配驱动的。
2. 技术方案对比与选择
2.1 传统方法的局限性
词典映射法是最直接的解决方案。我们曾构建过包含2000+别名的商品词典,初期准确率能达到75%。但维护成本极高——每新增一个商品,都需要人工收集其各种别名。更棘手的是,像"建筑用的那种钢材"这类描述性表达根本无法穷举。
知识图谱确实能更好地表达概念间关系。我们尝试用Neo4j构建了商品关系图谱,将"螺纹钢"作为主节点,"钢筋"、"rebar"等作为别名节点。这种方法在已知关系上表现良好,但对新出现的表达(如网络流行语"螺纹哥")完全无能为力。
Embedding相似度方法我们测试了BERT和Sentence-BERT。虽然"钢筋"和"螺纹钢"的cosine相似度达到0.82,但"螺纹钢"和"不锈钢"也有0.76的相似度,导致大量误判。这种方法更适合作为召回阶段的辅助手段。
2.2 LLM的独特优势
大语言模型的核心价值在于其涌现的语义理解能力。通过设计合适的prompt,LLM可以将模糊描述准确映射到标准概念。例如:
code复制用户输入:"工地用的那种带肋的钢材"
Prompt:请将以下商品描述映射到最接近的标准商品名称:
标准商品列表:螺纹钢,水泥,玻璃...
LLM输出:螺纹钢
我们在实际测试中发现,对于训练数据中从未出现过的表达方式(如"螺纹小钢炮"),GPT-4仍能准确识别为螺纹钢,这展现了强大的泛化能力。
3. 混合架构设计与实现
3.1 分层处理流程
经过多次迭代,我们最终采用了五层处理架构:
- 预处理层:拼写纠正、简繁转换等文本规范化
- 快速匹配层:基于Trie树的词典匹配(覆盖80%高频查询)
- 语义召回层:使用SBERT向量检索Top5候选
- 精排层:LLM基于上下文进行概念判定
- 后处理层:业务规则过滤(如地域限定)
python复制# 示例代码:混合查询处理流程
def normalize_query(query):
# 第一步:文本预处理
cleaned = preprocess(query)
# 第二步:快速词典匹配
std_term = dict_match(cleaned)
if std_term: return std_term
# 第三步:向量召回候选
candidates = vector_search(cleaned, top_k=5)
# 第四步:LLM精排
prompt = build_rerank_prompt(query, candidates)
result = llm_api(prompt)
# 第五步:业务规则验证
return validate(result)
3.2 关键组件实现细节
词典匹配优化:
- 使用双数组Trie树实现,内存占用降低60%
- 支持模糊匹配(拼音首字母、容错1个字符)
- 动态加载机制,支持热更新
向量检索优化:
- 采用FAISS索引,查询耗时<10ms
- 混合使用商品名称、别名、描述生成embedding
- 定期增量更新索引
LLM提示工程:
python复制def build_prompt(user_input, candidates):
return f"""请从以下候选商品中选择最匹配的描述:
用户输入:{user_input}
候选商品:{candidates}
请按格式返回:
思考过程:<分析用户意图与候选商品的关系>
最佳匹配:<商品ID>"""
4. 性能优化与效果评估
4.1 精度与召回率平衡
我们在3个月的真实用户查询数据上测试(约50万条):
| 方法 | 准确率 | 召回率 | 响应时间 |
|---|---|---|---|
| 纯词典 | 92% | 61% | 20ms |
| 纯LLM | 89% | 95% | 1200ms |
| 混合方案 | 91% | 93% | 85ms |
混合方案在保持高准确率的同时,将LLM的调用量减少了78%,整体成本下降显著。
4.2 工程实践中的经验
冷启动问题:
初期缺乏标注数据时,我们采用"反向生成"策略:用LLM为每个标准商品生成100+种可能的表达方式,快速构建初始词典。例如:
code复制为"螺纹钢"生成常见用户表达方式:
1. 工地用的钢筋
2. 建筑钢材
3. 带肋的那种钢...
长尾问题治理:
建立误识别案例库,每周分析Top20错误案例。对于高频误识别模式(如将"钢坯"误认为"螺纹钢"),通过两种方式处理:
- 添加到词典的负样本
- 在prompt中加入特殊规则:"注意:钢坯不是螺纹钢"
5. 典型问题与解决方案
5.1 概念歧义问题
场景:用户查询"钢板价格",可能指:
- 中厚板(标准商品)
- 钢板桩(另一种商品)
解决方案:
- 在prompt中加入商品定义:
code复制
中厚板:厚度>4mm的平板钢材 钢板桩:用于地基支护的型材 - 请求用户澄清(最后手段):
code复制
请问您指的是建筑用中厚板,还是地基用的钢板桩?
5.2 多语言混合输入
场景:用户输入"rebar和螺纹钢哪个贵?"
处理流程:
- 识别出两个实体:rebar和螺纹钢
- 通过知识图谱确认是同一商品
- 返回:"rebar是螺纹钢的英文名称,价格相同"
5.3 时效性要求
对于价格等时效敏感信息,我们在prompt中注入时间上下文:
code复制当前日期:2023-08-20
最新行情:螺纹钢现货价3850元/吨
用户查询:"螺纹钢最近涨价了吗?"
LLM输出:"相比上周上涨约2%..."
6. 进阶优化方向
6.1 动态上下文感知
传统方案对对话场景支持较差。我们正在试验基于对话历史的上下文跟踪:
python复制class ConversationContext:
def __init__(self):
self.history = []
def update(self, query, response):
self.history.append((query, response))
def get_context(self):
return "\n".join([f"用户:{q}\n系统:{r}" for q,r in self.history[-3:]])
6.2 小模型微调方案
为降低LLM API成本,我们正在尝试:
- 用GPT-4生成10万组<表达,标准商品>对
- 在DeBERTa-v3上做微调
- 蒸馏到更小的TinyBERT模型
当前小模型在测试集上达到GPT-4 85%的准确率,但推理速度提升20倍。
6.3 持续学习机制
建立自动化数据飞轮:
- 记录所有LLM判定结果
- 人工复核边界案例
- 定期更新词典和prompt模板
- 重新训练embedding模型
这个过程中最关键的发现是:用户新创的表达方式往往具有传播性。比如某个地区的客户开始用"小螺纹"指代直径较小的螺纹钢,半年后这个称呼被广泛使用。系统需要及时捕捉这种变化。
