feat: 意图识别优化

This commit is contained in:
qinyong@9artedu.com 2026-07-15 16:00:58 +08:00
parent 20c7d1a25f
commit 2a469c6663
3 changed files with 156 additions and 52 deletions

View File

@ -45,7 +45,7 @@ if __name__ == '__main__':
archive_messages_mysql_repository = ArchiveMessagesRepository(db_session)
qa_milvus_repository = QARepository()
state: QueryQAState = QueryQAState(original_query="老师你好我想咨询一下3d建模费用和就业情况", history=['2026-07-10T15:42:13 EXTERNAL 老师这个课程多少钱啊\n', '2026-07-10T15:40:34 EXTERNAL 老师 我想报班\n'])
state: QueryQAState = QueryQAState(original_query="老师现在建模课多少钱呢?", history=['2026-07-10T15:42:13 EXTERNAL 老师现在建模课多少钱呢?\n'])
context = QueryQAContext(meta_mysql_repository=archive_messages_mysql_repository,
qa_milvus_repository=qa_milvus_repository
)

View File

@ -15,95 +15,195 @@ problem_rewriting_prompt = """
### 1. 双维度提取原则
每个用户问题通常包含两个维度的意图必须分别识别
- **课程/服务主题维度**用户想咨询的具体课程类型或业务主题如3D建模2D原画UE引擎开发等
- **诉求/动作维度**用户想了解的方面如课程咨询就业咨询校区分布
- **课程/服务主题维度**用户想咨询的具体课程类型或业务主题
- **诉求/动作维度**用户想了解的方面费用就业难度
这两个维度相互独立必须同时提取不得遗漏任何一个
### 2. 主题维度提取规则
- 如果用户明确提到了具体课程名称或类型"3D建模""2D原画""UE引擎开发"必须作为独立意图标签输出
- 如果当前问题使用代词"这个"""结合历史会话进行指代消解还原具体课程名称
- 如果历史会话中也没有明确课程类型则不输出主题维度标签
### 2. 并列结构强制拆分
识别以下并列模式并强制拆分
- 顿号/逗号分隔"A、B、C" [A, B, C]
- "和/与/及/还有"连接"A和B" [A, B]
- 疑问词并列"多少钱、怎么报名、有就业吗" [多少钱, 怎么报名, 有就业吗]
### 3. 诉求维度提取规则
- 从当前问题中提取用户明确表达的动作诉求费用就业退费住宿等
- 将动作诉求映射到标准诉求类型标签
**规则**拆分后每个子项必须独立分析不得合并
### 4. 信息补全与语义保持
### 3. 意图识别置信度评分体系
每个意图标签在输出前必须经过置信度评分**只有总分60分的标签才会被输出**
#### 评分维度:
| 评分维度 | 分值 | 触发条件 | 说明 |
|---------|------|---------|------|
| **直接匹配** | **60** | 用户问题中直接出现标签关键词或同义词扩展词 | 关键词完全匹配或同义词匹配直接给60分超过阈值 |
| **语义关联** | **40** | 用户表达与标签语义高度相关通过语义场推断 | 语义场匹配给40分需配合上下文支持达到阈值 |
| **上下文支持** | **20** | 历史会话或当前上下文支持该意图推断 | 与语义关联配合40+20=60刚好达到阈值 |
| **历史补全** | **10** | 意图完全依赖历史会话补全 | 仅作为辅助加分不单独使用 |
#### 阈值判定规则:
- **总分 60** 进入候选池
- **总分 < 60** 直接过滤不进入候选池
- 无任何意图60 输出 `{{"intents": []}}`
### 4. 强制截断规则(新增)
**无论识别到多少个意图最终输出最多只返回2个意图标签**
当候选池中超过2个意图时按以下优先级排序只保留前2个
| 优先级 | 排序规则 | 说明 |
|-------|---------|------|
| **P1最高** | 当前问题直接明确表达的 &gt; 历史会话补全的 | 用户当前明确说出的诉求优先 |
| **P2** | 总分高的 &gt; 总分低的 | 置信度更高的优先 |
| **P3** | 诉求维度 &gt; 主题维度 | 用户更关心"做什么"而非"是什么" |
| **P4** | 出现顺序靠前的 &gt; 出现顺序靠后的 | 用户先说的一般更关心 |
**截断规则**排序后只保留前2个其余全部丢弃
### 5. 语义同义词扩展匹配
匹配时不仅使用标准词还必须覆盖扩展词
**就业咨询** = 就业找工作工作推荐就业支持就业服务就业保障就业率包就业毕业安排工作分配推荐工作就业方向职业发展入职岗位推荐能就业吗好找工作吗就业情况就业数据薪资工资待遇收入水平
**学不会怎么办** = 学不会跟不上没基础怕学不会能不能学能学会吗学得会吗适合我学吗零基础能学吗没经验能学吗纯小白能学吗好不好学难不难难吗简单吗容易学吗好学吗有难度吗门槛高吗有门槛吗担心自己学不会不知道能不能学会怕跟不上怕太难上手难学不进听不懂理解不了
**课程咨询** = 多少钱费用价格学费学什么内容课程安排怎么收费贵不贵收费标准分期付款优惠折扣教什么学什么内容课程体系教学大纲课程表怎么报名报名流程入学流程报名需要什么报名条件上课时间上课周期学多久多长时间几个月课时开班时间什么时候开课有班级吗还能报名吗
**退费咨询** = 退费退款退钱退款政策不学了能退吗中途退学退课退费规则退费流程能退多少退款比例退费条件不想学了怎么办
**住宿咨询** = 住宿宿舍包住住哪住宿条件有宿舍吗包住宿吗住宿环境住宿费房租住校外宿租房住宿安排住宿费用
**学历咨询** = 学历文凭证书学位毕业证大专本科学历认证学历证书学位认证
**校区分布** = 校区地址在哪位置分布哪个城市怎么去交通附近
**优势/竞争力** = 优势竞争力为什么选你们和其他机构比口碑排名实力师资教学质量
### 6. 信息补全与语义保持
- 如果当前问题缺少主语从历史会话补全
- 不得改变用户原始意图不得引入历史会话中不存在的新信息
- 不得遗漏当前问题中的任何关键诉求
## 输出格式
- 仅输出JSON不要任何解释性文字
- 多诉求时用数组形式输出每个意图标签独立
- **最多只返回2个意图标签**超出部分强制截断
- 标签从以下列表中选择不得自创
**课程/服务主题标签**公司介绍3D建模3D场景建模3D角色建模2D原画二次元风格写实风格动作/分镜游戏动作/分镜开发UE引擎开发3D大师课GGAC绘殓课程造型力大学生扶持计划兴趣/提升班学习模式线下面授教学线上直播教学寒暑假教学提供服务优势/竞争力校区分布
**诉求/动作标签**课程咨询退费咨询住宿咨询学不会怎么办就业咨询学历咨询
## 分析流程(必须按顺序执行)
### 步骤1并列拆分
将当前问题拆分为独立的子诉求
### 步骤2双维度扫描 + 置信度评分
为每个识别到的意图计算总分
### 步骤3阈值过滤
只保留总分60分的意图进入候选池
### 步骤4优先级排序当候选池超过2个时
按P1P2P3P4规则排序
### 步骤5强制截断
只保留排序后的前2个意图标签
### 步骤6完整性自检输出前必须执行
- [ ] 每个子诉求是否都经过了置信度评分
- [ ] 是否有总分60分的意图被遗漏
- [ ] 最终输出是否2个标签
- [ ] 截断时是否按优先级规则执行
如果任何一项为""重新分析
## 分类示例
### 示例1主题+诉求双维度)
### 示例12个意图无需截断
历史会话
当前问题3D建模培训的课程内容有哪些
分析主题="3D建模"诉求="课程内容"课程咨询
分析
- 主题维度"3D建模" 直接匹配 = 60 60 候选池
- 诉求维度"课程内容" 直接匹配"课程咨询" = 60 60 候选池
- 候选池共2个无需截断
分类结果
{{
"intents": ["3D建模", "课程咨询"]
}}
### 示例2主题+诉求双维度,信息补全)
历史会话
- 用户我想了解游戏特效培训
- 助手好的请问您想了解哪方面
当前问题培训的费用
分析主题从历史会话补全="游戏特效培训"诉求="费用"课程咨询
分类结果
{{
"intents": ["课程咨询"]
}}
### 示例3主题+诉求双维度,指代消解)
历史会话
- 用户你们有Python课程吗
- 助手有的我们有Python基础班和进阶班
当前问题这个课程的时长是多久
分析主题="Python课程"诉求="课程时长"课程咨询
分类结果
{{
"intents": ["课程咨询"]
}}
### 示例4多诉求提取
历史会话
- 用户我想报名数据分析培训
### 示例2并列拆分 + 阈值过滤 + 截断)
历史会话
当前问题流程费用和就业支持
分析主题="数据分析培训"诉求="流程/费用"课程咨询"就业支持"就业咨询
分析
- 拆分[流程, 费用, 就业支持]
- "流程" 直接匹配"课程咨询" = 60 候选池
- "费用" 直接匹配"课程咨询" = 60 候选池去重
- "就业支持" 直接匹配"就业咨询" = 60 候选池
- 候选池["课程咨询", "就业咨询"]共2个无需截断
分类结果
{{
"intents": ["课程咨询", "就业咨询"]
}}
### 示例5主题+诉求双维度
### 示例33个意图强制截断为2个
历史会话
当前问题老师3D大师课课程多少钱啊老师我想报班
分析主题="3D大师课"诉求="多少钱/报班"课程咨询
当前问题我想学角色不知道能不能学好不好学
分析
- 主题维度"学角色" 直接匹配"3D角色建模" = 60 候选池
- 诉求维度1"想学" 直接匹配"课程咨询" = 60 候选池
- 诉求维度2"不知道能不能学" 语义关联(40) + 上下文支持(20) = 60 候选池
- 诉求维度3"好不好学" 语义关联(40) + 上下文支持(20) = 60 候选池与上同标签去重
- 候选池["3D角色建模", "课程咨询", "学不会怎么办"]共3个需截断
- 排序课程咨询(P1当前直接) &gt; 学不会怎么办(P1当前直接) &gt; 3D角色建模(P3主题维度)
- 截断后保留前2个
分类结果
{{
"intents": ["3D大师课", "课程咨询"]
"intents": ["程咨询", "学不会怎么办"]
}}
### 示例6关键示例主题必须独立提取
历史会话
- 用户老师 我想报班
- 用户老师这个课程多少钱啊
当前问题老师我想咨询3D建模费用问题
分析主题="3D建模"当前问题明确提及诉求="费用问题"课程咨询
### 示例45个意图强制截断为2个
历史会话
当前问题3D建模费用就业住宿退费
分析
- "3D建模" 直接匹配 = 60 候选池
- "费用" 直接匹配"课程咨询" = 60 候选池
- "就业" 直接匹配"就业咨询" = 60 候选池
- "住宿" 直接匹配"住宿咨询" = 60 候选池
- "退费" 直接匹配"退费咨询" = 60 候选池
- 候选池共5个需截断
- 排序课程咨询(P1当前直接,出现第2) &gt; 就业咨询(P1当前直接,出现第3) &gt; 住宿咨询 &gt; 退费咨询 &gt; 3D角色建模(P3主题维度)
- 截断后保留前2个
分类结果
{{
"intents": ["3D建模", "课程咨询"]
"intents": ["课程咨询", "就业咨询"]
}}
### 示例5历史补全被过滤 + 截断)
历史会话
- 用户我想学3D建模
当前问题多少钱就业情况住宿条件
分析
- 主题维度"3D建模"历史补全 = 10 < 60 **不进入候选池**
- "多少钱" 直接匹配"课程咨询" = 60 候选池
- "就业情况" 直接匹配"就业咨询" = 60 候选池
- "住宿条件" 直接匹配"住宿咨询" = 60 候选池
- 候选池["课程咨询", "就业咨询", "住宿咨询"]共3个需截断
- 排序课程咨询(P1,出现第1) &gt; 就业咨询(P1,出现第2) &gt; 住宿咨询(P1,出现第3)
- 截断后保留前2个
分类结果
{{
"intents": ["课程咨询", "就业咨询"]
}}
### 示例6语义关联不足60分全部被过滤
历史会话
当前问题这个课有意思吗
分析
- "有意思" 语义关联"课程咨询" = 40无上下文支持 = 0总分40分 < 60 **不进入候选池**
- 候选池为空
分类结果
{{
"intents": []
}}
---

View File

@ -123,3 +123,7 @@ class IntentClassificationRepository:
)
categories = list(set([item.get("intent_category", "") for item in result]))
return categories
async def get_style_intent_categories(self) -> List[str]:
all_categories = await self.get_all_intent_categories()
return [c for c in all_categories if "风格" in c]