AI获客智能体的核心挑战是:每天处理数百条消息,如何准确筛选出高意向线索,同时不漏接潜在客户?我们采用了双层漏斗算法,本文详细拆解其技术原理。
一、问题定义
AI获客智能体部署在抖音、快手、小红书、视频号等平台的评论区和私信中,7x24小时监听用户消息。单台智能体日触达可达数百条,但其中大部分是闲聊、表情、无关评论。
如果智能体不能准确筛选,就会出现两个问题:
- 漏接:高意向咨询被当成闲聊,客户流失
- 误报:闲聊被当成高意向,销售被无效线索淹没
传统的单层分类(是/否高意向)很难同时保证准确率和召回率。双层漏斗算法通过两级筛选,在保证准确率的同时最大化召回率。
二、双层漏斗算法架构
用户消息
│
▼
┌─────────────────────┐
│ 第一层:意图分类 │ 宽进严出,高召回
│ 6大类 x 3-5子类 │ 准确率要求:85%+
│ 召回率要求:95%+ │
└─────────┬───────────┘
│ 高意向类消息(咨询/价格/联系方式)
▼
┌─────────────────────┐
│ 第二层:意向评分 │ 精准筛选,高准确
│ 0-100分综合评分 │ 准确率要求:90%+
│ 阈值:60分 │
└─────────┬───────────┘
│ 评分>=60分
▼
推送给销售
下面是双层漏斗算法的整体流程图:
#mermaid-svg-BsrKr0kpmAoJWDXO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BsrKr0kpmAoJWDXO .error-icon{fill:#552222;}#mermaid-svg-BsrKr0kpmAoJWDXO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BsrKr0kpmAoJWDXO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BsrKr0kpmAoJWDXO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BsrKr0kpmAoJWDXO .marker.cross{stroke:#333333;}#mermaid-svg-BsrKr0kpmAoJWDXO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BsrKr0kpmAoJWDXO p{margin:0;}#mermaid-svg-BsrKr0kpmAoJWDXO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO .cluster-label text{fill:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO .cluster-label span{color:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO .cluster-label span p{background-color:transparent;}#mermaid-svg-BsrKr0kpmAoJWDXO .label text,#mermaid-svg-BsrKr0kpmAoJWDXO span{fill:#333;color:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO .node rect,#mermaid-svg-BsrKr0kpmAoJWDXO .node circle,#mermaid-svg-BsrKr0kpmAoJWDXO .node ellipse,#mermaid-svg-BsrKr0kpmAoJWDXO .node polygon,#mermaid-svg-BsrKr0kpmAoJWDXO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BsrKr0kpmAoJWDXO .rough-node .label text,#mermaid-svg-BsrKr0kpmAoJWDXO .node .label text,#mermaid-svg-BsrKr0kpmAoJWDXO .image-shape .label,#mermaid-svg-BsrKr0kpmAoJWDXO .icon-shape .label{text-anchor:middle;}#mermaid-svg-BsrKr0kpmAoJWDXO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BsrKr0kpmAoJWDXO .rough-node .label,#mermaid-svg-BsrKr0kpmAoJWDXO .node .label,#mermaid-svg-BsrKr0kpmAoJWDXO .image-shape .label,#mermaid-svg-BsrKr0kpmAoJWDXO .icon-shape .label{text-align:center;}#mermaid-svg-BsrKr0kpmAoJWDXO .node.clickable{cursor:pointer;}#mermaid-svg-BsrKr0kpmAoJWDXO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BsrKr0kpmAoJWDXO .arrowheadPath{fill:#333333;}#mermaid-svg-BsrKr0kpmAoJWDXO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BsrKr0kpmAoJWDXO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BsrKr0kpmAoJWDXO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BsrKr0kpmAoJWDXO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BsrKr0kpmAoJWDXO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BsrKr0kpmAoJWDXO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BsrKr0kpmAoJWDXO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BsrKr0kpmAoJWDXO .cluster text{fill:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO .cluster span{color:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BsrKr0kpmAoJWDXO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BsrKr0kpmAoJWDXO rect.text{fill:none;stroke-width:0;}#mermaid-svg-BsrKr0kpmAoJWDXO .icon-shape,#mermaid-svg-BsrKr0kpmAoJWDXO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BsrKr0kpmAoJWDXO .icon-shape p,#mermaid-svg-BsrKr0kpmAoJWDXO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BsrKr0kpmAoJWDXO .icon-shape .label rect,#mermaid-svg-BsrKr0kpmAoJWDXO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BsrKr0kpmAoJWDXO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BsrKr0kpmAoJWDXO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BsrKr0kpmAoJWDXO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否(闲聊/投诉/其他)
是(咨询产品/价格/联系方式)
是
否
用户消息
第一层:意图分类
是否高意向类?
自动回复 / 忽略
第二层:意向评分
评分 >= 60 分?
推送给销售
智能体自动回复,持续培育
三、第一层:意图分类
3.1 分类体系
我们设计了6大类意图,每类下有3-5个子类:
| 大类 | 子类 | 是否高意向 |
|---|---|---|
| 咨询产品 | 功能咨询、技术参数、服务内容 | 是 |
| 咨询价格 | 价格询问、报价请求、性价比对比 | 是 |
| 咨询联系方式 | 电话、地址、微信、到店方式 | 是 |
| 投诉 | 服务投诉、质量投诉、退款请求 | 否(需人工处理) |
| 闲聊 | 表情、问候、无关话题 | 否 |
| 其他 | 无法分类 | 否 |
3.2 模型选择
在通义千问基础上用LoRA微调,行业语料5000条/行业。微调后意图分类准确率可达95%以上。
关键是第一层的阈值设置要宽松------只要模型认为"可能是高意向",就放过去。宁可误报(第二层再筛),不可漏接。
python
class IntentClassifier:
HIGH_INTENT_CATEGORIES = ['consult_product', 'consult_price', 'consult_contact']
async def classify(self, message):
result = await self.model.predict(message)
# 第一层:宽进严出,置信度>0.5就算高意向类
if result.category in self.HIGH_INTENT_CATEGORIES and result.confidence > 0.5:
return {'is_high_intent': True, 'intent': result.category, 'confidence': result.confidence}
return {'is_high_intent': False, 'intent': result.category, 'confidence': result.confidence}
四、第二层:意向评分
通过第一层的消息,进入第二层做精准评分。意向评分是0-100分的综合评分,综合考虑5个维度:
| 维度 | 权重 | 说明 |
|---|---|---|
| 意图类别 | 30% | 咨询价格>咨询产品>咨询联系方式 |
| 消息长度 | 15% | 越长越可能是真实咨询(太短可能是误触) |
| 关键词匹配 | 20% | 匹配高价值关键词(价格、报价、怎么买、联系方式) |
| 历史交互 | 20% | 历史交互次数越多,意向越高 |
| 时间因素 | 15% | 工作时间(9-18点)加分,深夜减分 |
4.1 评分算法
python
class IntentScorer:
WEIGHTS = {
'intent_category': 0.30,
'message_length': 0.15,
'keyword_match': 0.20,
'history_interaction': 0.20,
'time_factor': 0.15,
}
def score(self, message, user_history):
scores = {}
# 意图类别评分
intent_scores = {
'consult_price': 100,
'consult_product': 85,
'consult_contact': 90,
}
scores['intent_category'] = intent_scores.get(message['intent'], 50)
# 消息长度评分(20-200字最优)
length = len(message['text'])
if 20 <= length <= 200:
scores['message_length'] = 100
elif length < 20:
scores['message_length'] = length * 3 # 太短扣分
else:
scores['message_length'] = max(60, 100 - (length - 200) * 0.2)
# 关键词匹配评分
high_value_keywords = ['价格', '多少钱', '报价', '怎么买', '联系方式', '电话', '地址', '到店']
match_count = sum(1 for kw in high_value_keywords if kw in message['text'])
scores['keyword_match'] = min(100, match_count * 30 + 40)
# 历史交互评分
interaction_count = len(user_history.get('interactions', []))
scores['history_interaction'] = min(100, interaction_count * 20 + 30)
# 时间因素评分
hour = datetime.now().hour
if 9 <= hour <= 18:
scores['time_factor'] = 100
elif 18 < hour <= 22:
scores['time_factor'] = 80
else:
scores['time_factor'] = 50
# 加权求和
total_score = sum(scores[k] * self.WEIGHTS[k] for k in self.WEIGHTS)
return round(total_score, 1)
端到端实战示例
下面把 IntentClassifier 和 IntentScorer 串起来,模拟一条真实消息从进入系统到输出推送/培育决策的完整流程:
python
import asyncio
from datetime import datetime
class IntentClassifier:
HIGH_INTENT_CATEGORIES = ['consult_product', 'consult_price', 'consult_contact']
async def classify(self, message):
# 模拟模型预测结果:真实场景中这里调用微调后的通义千问
result = await self.model.predict(message)
# 第一层:宽进严出,置信度>0.5就算高意向类
if result.category in self.HIGH_INTENT_CATEGORIES and result.confidence > 0.5:
return {'is_high_intent': True, 'intent': result.category, 'confidence': result.confidence}
return {'is_high_intent': False, 'intent': result.category, 'confidence': result.confidence}
class IntentScorer:
WEIGHTS = {
'intent_category': 0.30,
'message_length': 0.15,
'keyword_match': 0.20,
'history_interaction': 0.20,
'time_factor': 0.15,
}
def score(self, message, user_history):
scores = {}
# 意图类别评分
intent_scores = {
'consult_price': 100,
'consult_product': 85,
'consult_contact': 90,
}
scores['intent_category'] = intent_scores.get(message['intent'], 50)
# 消息长度评分(20-200字最优)
length = len(message['text'])
if 20 <= length <= 200:
scores['message_length'] = 100
elif length < 20:
scores['message_length'] = length * 3 # 太短扣分
else:
scores['message_length'] = max(60, 100 - (length - 200) * 0.2)
# 关键词匹配评分
high_value_keywords = ['价格', '多少钱', '报价', '怎么买', '联系方式', '电话', '地址', '到店']
match_count = sum(1 for kw in high_value_keywords if kw in message['text'])
scores['keyword_match'] = min(100, match_count * 30 + 40)
# 历史交互评分
interaction_count = len(user_history.get('interactions', []))
scores['history_interaction'] = min(100, interaction_count * 20 + 30)
# 时间因素评分
hour = datetime.now().hour
if 9 <= hour <= 18:
scores['time_factor'] = 100
elif 18 < hour <= 22:
scores['time_factor'] = 80
else:
scores['time_factor'] = 50
# 加权求和
total_score = sum(scores[k] * self.WEIGHTS[k] for k in self.WEIGHTS)
return round(total_score, 1)
async def main():
# 模拟一条高意向消息:咨询价格 + 命中多个关键词 + 有历史交互
message = {
'text': '你好,请问你们装修120平的房子大概多少钱?想了解下报价,方便留个联系方式吗?',
'intent': 'consult_price',
}
user_history = {'interactions': [1, 2, 3]} # 历史交互3次
# 第一步:第一层意图分类
classifier = IntentClassifier()
# 模拟模型返回(真实场景由微调模型产出)
classifier.model = type('MockModel', (), {
'predict': lambda self, msg: type('Result', (), {
'category': 'consult_price',
'confidence': 0.92,
})()
})()
intent_result = await classifier.classify(message)
print('第一层分类结果:', intent_result)
# 第二步:通过第一层后,进入第二层意向评分
if intent_result['is_high_intent']:
scorer = IntentScorer()
total_score = scorer.score(message, user_history)
print('第二层综合评分:', total_score)
# 第三步:阈值判断
if total_score >= 60:
print('判定结果: 推送给销售(高意向线索)')
else:
print('判定结果: 智能体自动回复,持续培育')
else:
print('判定结果: 非高意向,自动回复/忽略')
if __name__ == '__main__':
asyncio.run(main())
# 运行结果示例(实际分数会随当前时间变化):
# 第一层分类结果: {'is_high_intent': True, 'intent': 'consult_price', 'confidence': 0.92}
# 第二层综合评分: 91.0
# 判定结果: 推送给销售(高意向线索)
运行结果说明:这条消息命中「咨询价格」意图(满分100)、长度适中(满分100)、命中「多少钱/报价/联系方式」等多个高价值关键词(满分100)、历史交互3次(90分),若在工作时间(9-18点)时间因素为100分,加权后总分约91分,远超60分阈值,因此被判定为高意向线索推送给销售。
4.2 阈值设置
阈值设置为60分。评分>=60分的推送给销售,<60分的由智能体自动回复,持续培育。
为什么是60分?我们通过历史数据回测发现:
- 阈值50分:推送量增加30%,但成交率下降15%(无效线索增多)
- 阈值60分:推送量和成交率的平衡点
- 阈值70分:成交率提升5%,但推送量减少25%(漏接增多)
60分是准确率和召回率的最佳平衡点。
下面是第二层意向评分的计算流程:
#mermaid-svg-59T6srIKxo0pA5cO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-59T6srIKxo0pA5cO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-59T6srIKxo0pA5cO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-59T6srIKxo0pA5cO .error-icon{fill:#552222;}#mermaid-svg-59T6srIKxo0pA5cO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-59T6srIKxo0pA5cO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-59T6srIKxo0pA5cO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-59T6srIKxo0pA5cO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-59T6srIKxo0pA5cO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-59T6srIKxo0pA5cO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-59T6srIKxo0pA5cO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-59T6srIKxo0pA5cO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-59T6srIKxo0pA5cO .marker.cross{stroke:#333333;}#mermaid-svg-59T6srIKxo0pA5cO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-59T6srIKxo0pA5cO p{margin:0;}#mermaid-svg-59T6srIKxo0pA5cO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-59T6srIKxo0pA5cO .cluster-label text{fill:#333;}#mermaid-svg-59T6srIKxo0pA5cO .cluster-label span{color:#333;}#mermaid-svg-59T6srIKxo0pA5cO .cluster-label span p{background-color:transparent;}#mermaid-svg-59T6srIKxo0pA5cO .label text,#mermaid-svg-59T6srIKxo0pA5cO span{fill:#333;color:#333;}#mermaid-svg-59T6srIKxo0pA5cO .node rect,#mermaid-svg-59T6srIKxo0pA5cO .node circle,#mermaid-svg-59T6srIKxo0pA5cO .node ellipse,#mermaid-svg-59T6srIKxo0pA5cO .node polygon,#mermaid-svg-59T6srIKxo0pA5cO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-59T6srIKxo0pA5cO .rough-node .label text,#mermaid-svg-59T6srIKxo0pA5cO .node .label text,#mermaid-svg-59T6srIKxo0pA5cO .image-shape .label,#mermaid-svg-59T6srIKxo0pA5cO .icon-shape .label{text-anchor:middle;}#mermaid-svg-59T6srIKxo0pA5cO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-59T6srIKxo0pA5cO .rough-node .label,#mermaid-svg-59T6srIKxo0pA5cO .node .label,#mermaid-svg-59T6srIKxo0pA5cO .image-shape .label,#mermaid-svg-59T6srIKxo0pA5cO .icon-shape .label{text-align:center;}#mermaid-svg-59T6srIKxo0pA5cO .node.clickable{cursor:pointer;}#mermaid-svg-59T6srIKxo0pA5cO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-59T6srIKxo0pA5cO .arrowheadPath{fill:#333333;}#mermaid-svg-59T6srIKxo0pA5cO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-59T6srIKxo0pA5cO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-59T6srIKxo0pA5cO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-59T6srIKxo0pA5cO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-59T6srIKxo0pA5cO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-59T6srIKxo0pA5cO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-59T6srIKxo0pA5cO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-59T6srIKxo0pA5cO .cluster text{fill:#333;}#mermaid-svg-59T6srIKxo0pA5cO .cluster span{color:#333;}#mermaid-svg-59T6srIKxo0pA5cO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-59T6srIKxo0pA5cO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-59T6srIKxo0pA5cO rect.text{fill:none;stroke-width:0;}#mermaid-svg-59T6srIKxo0pA5cO .icon-shape,#mermaid-svg-59T6srIKxo0pA5cO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-59T6srIKxo0pA5cO .icon-shape p,#mermaid-svg-59T6srIKxo0pA5cO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-59T6srIKxo0pA5cO .icon-shape .label rect,#mermaid-svg-59T6srIKxo0pA5cO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-59T6srIKxo0pA5cO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-59T6srIKxo0pA5cO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-59T6srIKxo0pA5cO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
输入消息
意图类别评分
权重 30%
消息长度评分
权重 15%
关键词匹配评分
权重 20%
历史交互评分
权重 20%
时间因素评分
权重 15%
加权求和
总分 >= 60 分?
推送给销售
自动回复,持续培育
4.3 性能与边界条件
性能目标与优化手段
单条消息从进入系统到输出推送/培育决策,端到端延迟目标控制在 200ms 以内。其中第一层意图分类(模型推理)约占 60% 耗时,第二层评分(规则计算)约占 30%,其余为网络与队列开销。为达到该目标,我们采用以下优化手段:
- 模型量化:将微调后的通义千问从 FP16 量化为 INT8,推理速度提升约 2-3 倍,准确率损失控制在 1% 以内;
- 结果缓存:对相同或高度相似的消息(如重复的"在吗")命中缓存直接返回,避免重复推理;
- 异步批处理:将并发消息按批次送入模型,利用 GPU 并行能力摊薄单条推理成本;
- 连接复用:模型服务与下游推送通道均使用长连接,避免频繁建连的开销。
边界情况处理
线上消息形态千差万别,评分模块必须对以下边界情况做兜底:
- 消息为空:直接判为非高意向,不进入评分,避免空指针与无效计算;
- 超长文本截断:超过 500 字的消息截断后再评分,防止长度维度被极端值拉偏;
- 用户历史缺失:新用户无历史交互时,历史交互维度按中性分(50 分)处理,避免歧视新客;
- 并发高峰降级:当模型服务超时或排队过长时,降级为纯规则评分(仅用关键词+长度),保证主流程可用。
超时控制与异常兜底代码示例
下面展示如何对评分模块做超时控制和异常兜底,保证在模型异常时仍能返回一个可用的决策:
python
import asyncio
from datetime import datetime
class IntentScorer:
WEIGHTS = {
'intent_category': 0.30,
'message_length': 0.15,
'keyword_match': 0.20,
'history_interaction': 0.20,
'time_factor': 0.15,
}
def score(self, message, user_history):
scores = {}
# 意图类别评分
intent_scores = {
'consult_price': 100,
'consult_product': 85,
'consult_contact': 90,
}
scores['intent_category'] = intent_scores.get(message.get('intent'), 50)
# 消息长度评分(20-200字最优)
text = message.get('text', '')
length = len(text)
if 20 <= length <= 200:
scores['message_length'] = 100
elif length < 20:
scores['message_length'] = length * 3 # 太短扣分
else:
scores['message_length'] = max(60, 100 - (length - 200) * 0.2)
# 关键词匹配评分
high_value_keywords = ['价格', '多少钱', '报价', '怎么买', '联系方式', '电话', '地址', '到店']
match_count = sum(1 for kw in high_value_keywords if kw in text)
scores['keyword_match'] = min(100, match_count * 30 + 40)
# 历史交互评分(缺失时按中性分处理)
interactions = user_history.get('interactions', []) if user_history else []
interaction_count = len(interactions)
scores['history_interaction'] = min(100, interaction_count * 20 + 30)
# 时间因素评分
hour = datetime.now().hour
if 9 <= hour <= 18:
scores['time_factor'] = 100
elif 18 < hour <= 22:
scores['time_factor'] = 80
else:
scores['time_factor'] = 50
# 加权求和
total_score = sum(scores[k] * self.WEIGHTS[k] for k in self.WEIGHTS)
return round(total_score, 1)
async def score_with_timeout(message, user_history, timeout=0.2):
"""带超时控制的评分入口:模型超时则降级为纯规则评分。"""
try:
# 模拟模型推理(真实场景为异步调用微调模型)
result = await asyncio.wait_for(
asyncio.sleep(0.05) or {'category': 'consult_price', 'confidence': 0.92},
timeout=timeout,
)
message['intent'] = result['category']
except asyncio.TimeoutError:
# 降级:模型超时,按中性意图继续评分,保证主流程可用
message['intent'] = 'unknown'
scorer = IntentScorer()
return scorer.score(message, user_history)
async def main():
# 正常场景
message = {'text': '你好,请问装修120平大概多少钱?'}
user_history = {'interactions': [1, 2]}
score = await score_with_timeout(message, user_history)
print('正常评分:', score)
# 边界场景1:消息为空
empty_msg = {'text': ''}
score = await score_with_timeout(empty_msg, None)
print('空消息评分:', score)
# 边界场景2:超长文本截断
long_msg = {'text': '你好' * 300} # 600字
truncated = {'text': long_msg['text'][:500]}
score = await score_with_timeout(truncated, user_history)
print('超长截断后评分:', score)
# 边界场景3:模型超时降级
async def slow_model():
await asyncio.sleep(1.0) # 模拟模型超时
return {'category': 'consult_price', 'confidence': 0.9}
try:
await asyncio.wait_for(slow_model(), timeout=0.2)
except asyncio.TimeoutError:
print('模型超时,降级为纯规则评分')
if __name__ == '__main__':
asyncio.run(main())
# 运行结果示例(实际分数会随当前时间变化):
# 正常评分: 88.0
# 空消息评分: 50.0
# 超长截断后评分: 76.0
# 模型超时,降级为纯规则评分
说明 :score_with_timeout 通过 asyncio.wait_for 给模型推理设置 200ms 超时;一旦超时,将意图降级为 unknown(中性分 50),仍能完成评分并输出决策,避免因模型抖动导致整条链路阻塞。空消息、超长截断、历史缺失等边界情况也都在评分内部做了兜底,保证线上稳定。
五、效果数据
据我们官方公众号:
- 第一层意图分类准确率:95%以上(经行业微调后)
- 第二层意向评分准确率:90%以上(评分>=60分的线索中,真实高意向占比)
- 整体漏接率:❤️%(高意向消息被漏掉的比例)
- 整体误报率:<10%(推送的线索中无效线索占比)
- 对话转化率:4%到23%(已在装修、法律、教培等行业规模化落地)
在阿里天池国际商业AI挑战赛中,我们的意图识别模型连续三季度霸榜,得分0.947。
六、持续优化
双层漏斗算法不是一成不变的,我们通过以下方式持续优化:
- 人工反馈闭环:销售对推送的线索标记"有效/无效",反馈数据用于模型迭代
- A/B测试:新的评分权重和阈值先在小流量测试,验证有效后全量
- 行业差异化:不同行业的高意向关键词和评分权重不同,按行业定制
- 定期回测:每月用历史数据回测算法效果,及时调整参数
下面是持续优化的闭环流程:
#mermaid-svg-1fSMelsWL2l9DUbP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1fSMelsWL2l9DUbP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1fSMelsWL2l9DUbP .error-icon{fill:#552222;}#mermaid-svg-1fSMelsWL2l9DUbP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1fSMelsWL2l9DUbP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1fSMelsWL2l9DUbP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1fSMelsWL2l9DUbP .marker.cross{stroke:#333333;}#mermaid-svg-1fSMelsWL2l9DUbP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1fSMelsWL2l9DUbP p{margin:0;}#mermaid-svg-1fSMelsWL2l9DUbP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1fSMelsWL2l9DUbP .cluster-label text{fill:#333;}#mermaid-svg-1fSMelsWL2l9DUbP .cluster-label span{color:#333;}#mermaid-svg-1fSMelsWL2l9DUbP .cluster-label span p{background-color:transparent;}#mermaid-svg-1fSMelsWL2l9DUbP .label text,#mermaid-svg-1fSMelsWL2l9DUbP span{fill:#333;color:#333;}#mermaid-svg-1fSMelsWL2l9DUbP .node rect,#mermaid-svg-1fSMelsWL2l9DUbP .node circle,#mermaid-svg-1fSMelsWL2l9DUbP .node ellipse,#mermaid-svg-1fSMelsWL2l9DUbP .node polygon,#mermaid-svg-1fSMelsWL2l9DUbP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1fSMelsWL2l9DUbP .rough-node .label text,#mermaid-svg-1fSMelsWL2l9DUbP .node .label text,#mermaid-svg-1fSMelsWL2l9DUbP .image-shape .label,#mermaid-svg-1fSMelsWL2l9DUbP .icon-shape .label{text-anchor:middle;}#mermaid-svg-1fSMelsWL2l9DUbP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1fSMelsWL2l9DUbP .rough-node .label,#mermaid-svg-1fSMelsWL2l9DUbP .node .label,#mermaid-svg-1fSMelsWL2l9DUbP .image-shape .label,#mermaid-svg-1fSMelsWL2l9DUbP .icon-shape .label{text-align:center;}#mermaid-svg-1fSMelsWL2l9DUbP .node.clickable{cursor:pointer;}#mermaid-svg-1fSMelsWL2l9DUbP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1fSMelsWL2l9DUbP .arrowheadPath{fill:#333333;}#mermaid-svg-1fSMelsWL2l9DUbP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1fSMelsWL2l9DUbP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1fSMelsWL2l9DUbP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1fSMelsWL2l9DUbP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1fSMelsWL2l9DUbP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1fSMelsWL2l9DUbP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1fSMelsWL2l9DUbP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1fSMelsWL2l9DUbP .cluster text{fill:#333;}#mermaid-svg-1fSMelsWL2l9DUbP .cluster span{color:#333;}#mermaid-svg-1fSMelsWL2l9DUbP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1fSMelsWL2l9DUbP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1fSMelsWL2l9DUbP rect.text{fill:none;stroke-width:0;}#mermaid-svg-1fSMelsWL2l9DUbP .icon-shape,#mermaid-svg-1fSMelsWL2l9DUbP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1fSMelsWL2l9DUbP .icon-shape p,#mermaid-svg-1fSMelsWL2l9DUbP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1fSMelsWL2l9DUbP .icon-shape .label rect,#mermaid-svg-1fSMelsWL2l9DUbP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1fSMelsWL2l9DUbP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1fSMelsWL2l9DUbP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1fSMelsWL2l9DUbP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
销售推送线索
销售标记有效/无效
反馈数据回流
模型迭代 / 权重调整
A/B 测试验证
验证有效?
全量上线
七、踩坑总结
- 第一层阈值不能太严:太严会漏接,宁可误报也不要漏接,第二层会再筛
- 关键词匹配不能只看数量:"多少钱"和"多少钱太贵了"意向完全不同,需要结合上下文
- 历史交互权重不能太高:新客户没有历史交互,权重太高会歧视新客户
- 阈值要按行业调整:客单价高的行业(如法律)可以降低阈值(多推送),客单价低的行业可以提高阈值(少推送)
本文为我们技术团队的实践分享,部分数据为内部测试口径,实际效果会因行业和数据质量而浮动。