1. 关键词匹配撑不起智能客服的意图识别,问题到底出在哪
如果你正在用 Claude Code 搭智能客服系统,大概率会经历这样一个阶段:知识库能检索、FAQ 能命中,但一到「我要退款,太不满意了」这种一句话里带两种情绪的输入,意图识别就开始乱。要么被 COMPLAINT 规则先吃掉,要么直接掉进 UNKNOWN 兜底,回复驴唇不对马嘴。
我试过照抄网上那套knowledge_engine.py的关键词 + TF-IDF 方案,IntentClassifier里INTENT_RULES给关键词 0.3、正则 0.5 的权重,单看规则没问题,但「退款」和「投诉」的触发词高度重叠,max(scores, key=scores.get)取最高分时谁先命中谁赢,结果就是 refund 被 complaint 抢走。很多人第一反应是「代码写错了」,其实规则本身没错,是权重和词表边界没拆干净。
更隐蔽的坑在通道层。Claude Code 默认走官方端点,如果你没在~/.claude/settings.json里把ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN配通,它连模型都调不动,你改半天规则也看不到真实推理结果,只能靠本地uv run python硬跑,效率极低。这篇就按「先通通道、再调规则、最后验证」的顺序,把智能客服的意图识别从「关键词匹配」拉到「能真正理解语义」的水平。适合正在做知识库 + 意图识别 + 多轮对话的开发者,尤其是被 UNKNOWN 兜底折磨过的人。
2. 前置:把 Claude Code 的 Token 通道配通
在动knowledge_engine.py之前,先把消耗 Token 的 Claude Code 通道弄通。这一步不做,后面所有「让 Claude Code 帮我核对规则」的操作都是空谈。
打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册账号,进控制台创建一个 API Key。拿到 Key 之后,编辑~/.claude/settings.json,在env字段里填两个变量:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的Key" } }这里有两个细节必须注意。第一,ANTHROPIC_BASE_URL结尾不要加/v1,也不要带任何 UTM 参数,就填https://taotoken.net/api,多一个字符都会导致 404。第二,ANTHROPIC_AUTH_TOKEN填的是刚创建的那把 Key,不是账号密码。
配完之后回到smart-support项目目录,跑一次claude命令,如果能看到正常的对话响应,说明通道通了。这一步的验证很关键,因为后面让 Claude Code 逐条核对INTENT_RULES的权重、拆分「投诉」和「退款」规则,全靠这条通道。
如果你还想在浏览器里直接验证模型输出,可以走模型对话入口 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite ,把同一句话丢进去看模型怎么理解意图,和本地IntentClassifier的结果做对照。长期做编码和 Agent 的话,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 会更划算,Token 同样走这把 Key。
3. 可复制配置:拆开「投诉」和「退款」的规则边界
通道通了之后,回到knowledge_engine.py。原文的INTENT_RULES里,COMPLAINT 的关键词是["投诉", "差评", "不满意", "太差", "骗人", "吃土", "问题"],REFUND 是["退款", "退货", "退钱", "取消订单", "不要了", "退回"]。问题出在「不满意」「太差」这类词既像投诉又像退款前置情绪,而「退款」本身在投诉语境里也高频出现。
让 Claude Code 对着原文INTENT_RULES逐条核对,重点做三件事:
第一,把纯情绪词从 COMPLAINT 里剥离出来,单独作为情感信号,不参与意图打分。SentimentAnalyzer已经在做这件事,IntentClassifier不该再抢它的活。
第二,给 REFUND 加一条高优先级正则,明确「退款」动作词优先于情绪词:
Intent.REFUND: { "keywords": ["退款", "退货", "退钱", "取消订单", "不要了", "退回"], "patterns": [ r"(退款|退货|退钱|取消订单)", r"(我要|我想|申请).{0,4}(退款|退货|退钱)" ] },第三,把 COMPLAINT 的权重从 0.3 / 0.5 调整为情绪词 0.2、明确投诉动作词 0.5,避免情绪词压过退款动作:
Intent.COMPLAINT: { "keywords": ["投诉", "差评", "骗人", "吃土"], "patterns": [r"(投诉|差评|骗人|吃土)"] },改完之后,classify里的max(scores, key=scores.get)逻辑不用动,但因为 REFUND 的正则命中会拿到 0.5,而 COMPLAINT 的情绪词只拿 0.2,我要退款,太不满意了就会正确落到 refund。
这里有个参数对照表,方便你核对改动前后的差异:
| 意图 | 改动前关键词权重 | 改动后关键词权重 | 改动前正则权重 | 改动后正则权重 |
|---|---|---|---|---|
| REFUND | 0.3 | 0.3 | 0.5 | 0.5(新增动作正则) |
| COMPLAINT | 0.3 | 0.2 | 0.5 | 0.5(仅动作词) |
| UNKNOWN | 兜底 | 兜底 | - | - |
注意:不要为了追求「退款」永远赢,就把 COMPLAINT 权重压到 0。真实投诉场景里「我要投诉你们退款太慢」应该走 complaint 转人工,而不是 refund。权重调整的目标是让动作词决定意图,情绪词只做辅助。
4. 验证请求:重跑三行测试,确认意图落回 refund
改完规则,重跑原文那段三行测试:
from knowledge_engine import IntentClassifier intent, conf = IntentClassifier.classify('我要退款,太不满意了') print(f'意图: {intent.value} (置信度: {conf:.2f})')预期输出应该是:
意图: refund (置信度: 0.50)如果还是 complaint 或者 unknown,按下面顺序排查。先确认INTENT_RULES里 REFUND 的正则有没有写错,r"(退款|退货|退钱|取消订单)"里的竖线是正则或,不是字符串。再确认classify里re.search(pattern, text)用的是search不是match,match只从开头匹配,我要退款开头是「我」,会漏掉。
置信度 0.50 是因为只命中了正则的 0.5,没命中关键词。如果你希望置信度更高,可以在 REFUND 关键词里补上「退款」——但注意「退款」已经在关键词列表里了,if keyword in text会命中,所以实际置信度应该是 0.3 + 0.5 = 0.8。如果跑出来只有 0.5,检查关键词列表是不是被误删了。
确认意图识别正确后,再让 Claude Code 生成dialogue_manager.py的多轮对话与转人工逻辑。原文的SLOT_REQUIREMENTS里 refund 需要收集order_no,转人工判断是negative_count >= 3或intent == COMPLAINT and confidence > 0.7。这部分 Token 同样走~/.claude/settings.json里那把 Key,不用额外配置。
跑通对话管理器后,按原文继续做槽位收集和 Web 聊天界面。app.py里的/api/chat接口会调用dm.process_message,整条链路就串起来了。
5. 本篇常见错排查
报错一:ANTHROPIC_BASE_URL配了但 Claude Code 仍报 401。检查 Key 是不是复制时带了空格,或者settings.json的 JSON 格式有误(比如多了逗号)。用cat ~/.claude/settings.json | python -m json.tool验证格式。
报错二:意图识别跑出来还是 UNKNOWN。先打印scores字典看有没有命中任何规则。如果scores为空,说明关键词和正则都没匹配上,检查text是不是被strip()处理过、有没有全角半角混用。
报错三:IntentClassifier.classify返回的置信度超过 1.0。原文有min(score, 1.0)兜底,如果你改代码时删了这行,多个关键词叠加会超过 1.0。加回去即可。
报错四:dialogue_manager.py里negative_count不累加。检查process_message里if sentiment["sentiment"] == "negative"的判断,SentimentAnalyzer.analyze返回的sentiment是字符串"negative",不是枚举。
报错五:Web 界面发消息没反应。打开浏览器控制台看/api/chat请求的响应,大概率是session_id为空。/api/session是异步创建的,确保sendMessage在sessionId赋值之后再调用。
提示:排障时优先看 Claude Code 的原始输出,不要只看本地
knowledge_engine.py和dialogue_manager.py,它能帮你定位到具体哪一行规则冲突。
6. 接入文档与后续
通道配通、规则拆干净、意图落回 refund 之后,剩下的槽位收集、多轮对话、Web 界面都是顺水推舟。如果你在接入过程中遇到 Key 配置或端点报错,直接看接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有settings.json的完整字段说明和常见错误码。需要管理多把 Key 或查看用量,走 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
智能客服的意图识别不是靠堆关键词堆出来的,而是靠「动作词定意图、情绪词做辅助、通道保证推理真实」这三层配合。把 Claude Code 的通道先弄通,再回头调规则,你会发现 UNKNOWN 兜底少了一大半。