sales-assistant-py-new/app/chat_qa_query/query_nodes/node_rewrite.py

512 lines
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
问题改写
"""
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from app.chat_qa_query.query_qa_state import QueryQAState
from app.llm import llm
from app.core.log import logger
from app.repository.milvus.intent_classification_repository import IntentClassificationRepository
problem_rewriting_prompt1 = """
你是一名智能客服意图分类助手。你的任务是根据用户的历史会话和当前问题,提取用户的核心诉求,并输出标准化的意图标签。
## 核心原则(必须严格遵守)
### 1. 双维度提取原则
每个用户问题通常包含两个维度的意图,必须分别识别:
- **课程/服务主题维度**:用户想咨询的具体课程类型或业务主题
- **诉求/动作维度**:用户想了解的方面(费用、就业、难度等)
这两个维度相互独立,必须同时提取,不得遗漏任何一个。
### 2. 并列结构强制拆分
识别以下并列模式并强制拆分:
- 顿号/逗号分隔:"A、B、C" → [A, B, C]
- "和/与/及/还有"连接:"A和B" → [A, B]
- 疑问词并列:"多少钱、怎么报名、有就业吗" → [多少钱, 怎么报名, 有就业吗]
**规则**:拆分后,每个子项必须独立分析,不得合并。
### 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个时
按P1→P2→P3→P4规则排序。
### 步骤5强制截断
只保留排序后的前2个意图标签。
### 步骤6完整性自检输出前必须执行
- [ ] 每个子诉求是否都经过了置信度评分?
- [ ] 是否有总分≥60分的意图被遗漏
- [ ] 最终输出是否≤2个标签
- [ ] 截断时是否按优先级规则执行?
如果任何一项为"",重新分析。
## 分类示例
### 示例12个意图无需截断
历史会话:(空)
当前问题3D建模培训的课程内容有哪些
分析:
- 主题维度:"3D建模" → 直接匹配 = 60分 ≥ 60分 → 候选池
- 诉求维度:"课程内容" → 直接匹配"课程咨询" = 60分 ≥ 60分 → 候选池
- 候选池共2个无需截断
分类结果:
{{
"intents": ["3D建模", "课程咨询"]
}}
### 示例2并列拆分 + 阈值过滤 + 截断)
历史会话:(空)
当前问题:流程、费用和就业支持
分析:
- 拆分:[流程, 费用, 就业支持]
- "流程" → 直接匹配"课程咨询" = 60分 → 候选池
- "费用" → 直接匹配"课程咨询" = 60分 → 候选池(去重)
- "就业支持" → 直接匹配"就业咨询" = 60分 → 候选池
- 候选池:["课程咨询", "就业咨询"]共2个无需截断
分类结果:
{{
"intents": ["课程咨询", "就业咨询"]
}}
### 示例33个意图强制截断为2个
历史会话:(空)
当前问题:我想学角色,不知道能不能学,好不好学
分析:
- 主题维度:"学角色" → 直接匹配"3D角色建模" = 60分 → 候选池
- 诉求维度1"想学" → 直接匹配"课程咨询" = 60分 → 候选池
- 诉求维度2"不知道能不能学" → 语义关联(40) + 上下文支持(20) = 60分 → 候选池
- 诉求维度3"好不好学" → 语义关联(40) + 上下文支持(20) = 60分 → 候选池(与上同标签,去重)
- 候选池:["3D角色建模", "课程咨询", "学不会怎么办"]共3个需截断
- 排序:课程咨询(P1当前直接) &gt; 学不会怎么办(P1当前直接) &gt; 3D角色建模(P3主题维度)
- 截断后保留前2个
分类结果:
{{
"intents": ["课程咨询", "学不会怎么办"]
}}
### 示例45个意图强制截断为2个
历史会话:(空)
当前问题3D建模费用、就业、住宿、退费
分析:
- "3D建模" → 直接匹配 = 60分 → 候选池
- "费用" → 直接匹配"课程咨询" = 60分 → 候选池
- "就业" → 直接匹配"就业咨询" = 60分 → 候选池
- "住宿" → 直接匹配"住宿咨询" = 60分 → 候选池
- "退费" → 直接匹配"退费咨询" = 60分 → 候选池
- 候选池共5个需截断
- 排序:课程咨询(P1当前直接,出现第2) &gt; 就业咨询(P1当前直接,出现第3) &gt; 住宿咨询 &gt; 退费咨询 &gt; 3D角色建模(P3主题维度)
- 截断后保留前2个
分类结果:
{{
"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": []
}}
---
## 现在请处理以下输入
当前问题:{query}
历史聊天:{history_text}
请直接输出JSON
"""
problem_rewriting_prompt = """
# 智能客服意图分类助手提示词
你是一名智能客服意图分类助手。你的任务是根据用户的历史会话和当前问题,提取用户的核心诉求,并输出标准化的意图标签。
## 参数说明
本提示词需要动态传入以下参数:
- `{query}`:用户当前提问
- `{history_text}`:历史会话记录(可为空)
- `{intent_tags}`:完整的意图标签列表,格式为 JSON 字符串数组,例如:`["3D建模", "2D原画", "课程咨询", "就业咨询", ...]`。**所有输出的意图标签必须严格从该列表中选择,不得自创或修改。**
---
## 核心原则(必须严格遵守)
### 1. 双维度提取原则
每个用户问题通常包含两个维度的意图,必须分别识别:
- **课程/服务主题维度**:用户想咨询的具体课程类型或业务主题
- **诉求/动作维度**:用户想了解的方面(费用、就业、难度等)
这两个维度相互独立,必须同时提取,不得遗漏任何一个。最终输出的标签本身不区分维度,但模型内部需要判断每个标签的维度,以便后续优先级排序(见 P3 规则)。
**维度判定规则(必须按语义本质判断,禁止仅看标签字面后缀):**
- **主题维度**:表示用户想学习的**具体课程类型、专业方向或业务领域**。
例如:`3D建模`、`3D场景建模`、`3D角色建模`、`2D原画`、`UE引擎开发`、`游戏特效`、`地编`、`技术美术`、`游戏动作/分镜`、`二次元风格`、`写实风格` 等。
- **诉求/动作维度**:表示用户**想了解什么、想做什么、担心什么、需要什么服务**。
例如:`课程咨询`(想了解课程信息)、`就业咨询`(想了解就业)、`学不会怎么办`(担心学习效果)、`试听课`(想要试听服务)、`退费咨询`(想退费)、`住宿咨询`(想了解住宿)、`校区分布`(想了解位置)、`分期付款`(需要付款方式)、`开班安排`/`开课安排`/`上课时间`(想了解时间安排)、`报名流程`/`报到流程`(想了解报名动作)、`作品评估`(需要评估服务)、`预约参观`(想要参观)、`购买建议`(需要建议)、`重修政策`/`转班咨询`/`休学咨询`(想了解售后政策)等。
**关键原则**:如果标签表达的是「用户的一个动作、需求、顾虑或服务请求」,即使标签名称中不含「咨询」「怎么办」等词,也必须判定为**诉求/动作维度**。禁止仅凭标签名称是否包含特定后缀来判断维度。
**中心语优先原则(强制)**
在包含""字的偏正短语中(如"3D场景建模的试听课""角色建模的课程内容"**""字后面的成分(中心语)代表用户的核心诉求**""字前面的成分(定语)仅为主题限定。
- 中心语(诉求维度)的优先级 **必须高于** 定语(主题维度)。
- 出现顺序判定以**中心语**在短语中的位置为准,而非字面首个词。
---
### 2. 并列结构强制拆分
识别以下并列模式并强制拆分:
- 顿号/逗号分隔:`"A、B、C"` → `[A, B, C]`
- `"和/与/及/还有"` 连接:`"A和B"` → `[A, B]`
- 疑问词并列:`"多少钱、怎么报名、有就业吗"` → `[多少钱, 怎么报名, 有就业吗]`
**规则**:拆分后,每个子项必须独立分析,不得合并。
---
### 3. 意图识别置信度评分体系
每个意图标签在输出前必须经过置信度评分,**只有总分 ≥ 60 分的标签才会被输出**。
#### 评分维度
| 评分维度 | 分值 | 触发条件 | 说明 |
|---------|------|---------|------|
| **直接匹配** | **60分** | 用户问题中直接出现标签关键词或同义词扩展词见第5节 | 关键词完全匹配或同义词匹配,直接给 60 分,超过阈值 |
| **语义关联** | **40分** | 用户表达与标签语义高度相关,通过语义场推断 | 语义场匹配给 40 分,需配合上下文支持达到阈值 |
| **上下文支持** | **20分** | 历史会话或当前上下文支持该意图推断 | 与语义关联配合40 + 20 = 60 分,刚好达到阈值 |
| **历史补全** | **10分** | 意图完全依赖历史会话补全 | 仅作为辅助加分,不单独使用 |
#### 阈值判定规则
- **总分 ≥ 60 分** → 进入候选池
- **总分 &lt; 60 分** → 直接过滤,不进入候选池
- 无任何意图 ≥ 60 分 → 输出 `{{"intents": []}}`
---
### 4. 强制截断规则
**无论识别到多少个意图,最终输出最多只返回 2 个意图标签。**
当候选池中超过 2 个意图时,按以下优先级排序,只保留前 2 个:
| 优先级 | 排序规则 | 说明 |
|-------|---------|------|
| **P1最高** | 当前问题直接明确表达的 &gt; 历史会话补全的 | 用户当前明确说出的诉求优先 |
| **P2** | 总分高的 &gt; 总分低的 | 置信度更高的优先 |
| **P3** | 诉求维度 &gt; 主题维度 | 用户更关心"做什么"而非"是什么" |
| **P4** | 核心诉求出现顺序靠前的 &gt; 靠后的 | 同一子诉求内,中心语(诉求维度)优先于定语(主题维度);跨子诉求时,按子诉求在原文中的先后顺序 |
**P4 执行细则(强制)**
排序时必须严格遵循 P1 → P2 → P3 → P4 的层级顺序,**严禁跨层级比较**。即:仅当 P1、P2、P3 均相同或无法区分时,才启用 P4。
"主题+诉求"的复合表达(如"X的Y"Y诉求维度视为该子诉求的核心其 P4 顺序位始终优先于 X主题维度
**截断规则**:排序后,只保留前 2 个,其余全部丢弃。
---
### 5. 语义同义词扩展匹配
匹配时不仅使用标签字面本身,还必须覆盖常见的同义词扩展。以下为常见标签的同义词映射表(**当且仅当 `{intent_tags}` 中包含与下列标准标签完全同名的标签时,才可使用该扩展**;若传入的标签不在该表中,则根据标签字面及通用语义进行匹配,禁止编造扩展词):
- **就业咨询**:就业、找工作、工作推荐、就业支持、就业服务、就业保障、就业率、包就业、毕业安排、工作分配、推荐工作、就业方向、职业发展、入职、岗位推荐、能就业吗、好找工作吗、就业情况、就业数据、薪资、工资待遇、收入水平
- **学不会怎么办**:学不会、跟不上、没基础、怕学不会、能不能学、能学会吗、学得会吗、适合我学吗、零基础能学吗、没经验能学吗、纯小白能学吗、好不好学、难不难、难吗、简单吗、容易学吗、好学吗、有难度吗、门槛高吗、有门槛吗、担心自己学不会、不知道能不能学会、怕跟不上、怕太难、上手难、学不进、听不懂、理解不了
- **课程咨询**:多少钱、费用、价格、学费、学什么、内容、课程安排、怎么收费、贵不贵、收费标准、分期付款、优惠、折扣、教什么、学什么内容、课程体系、教学大纲、课程表、怎么报名、报名流程、入学流程、报名需要什么、报名条件、上课时间、上课周期、学多久、多长时间、几个月、课时、开班时间、什么时候开课、有班级吗、还能报名吗
- **退费咨询**:退费、退款、退钱、退款政策、不学了能退吗、中途退学、退课、退费规则、退费流程、能退多少、退款比例、退费条件、不想学了怎么办
- **住宿咨询**:住宿、宿舍、包住、住哪、住宿条件、有宿舍吗、包住宿吗、住宿环境、住宿费、房租、住校、外宿、租房、住宿安排、住宿费用
- **学历咨询**:学历、文凭、证书、学位、毕业证、大专、本科、学历认证、学历证书、学位认证
- **校区分布**:校区、地址、在哪、位置、分布、哪个城市、怎么去、交通、附近
- **优势/竞争力**:优势、竞争力、为什么选你们、和其他机构比、口碑、排名、实力、师资、教学质量
(若传入的标签列表包含上述标签以外的标签,同义词匹配将仅基于标签文字及通用语义,不得臆造扩展词。)
---
### 6. 信息补全与语义保持
- 如果当前问题缺少主语,从历史会话补全
- 不得改变用户原始意图,不得引入历史会话中不存在的新信息
- 不得遗漏当前问题中的任何关键诉求
---
## 输出格式
- 仅输出 JSON不要任何解释性文字
- **最多只返回 2 个意图标签**,超出部分强制截断
- 所有标签必须来自传入的 `{intent_tags}` 列表
- 输出格式示例:`{{"intents": ["标签1", "标签2"]}}`(若候选为空则 `{{"intents": []}}`
---
## 分析流程(必须按顺序执行)
### 步骤 1并列拆分
将当前问题拆分为独立的子诉求。
### 步骤 2双维度扫描 + 置信度评分
对每个子诉求,扫描 `{intent_tags}` 中的标签,按照第 3 节和第 5 节计算总分。每个意图独立评分。
### 步骤 3阈值过滤
只保留总分 ≥ 60 分的意图,进入候选池。
### 步骤 4优先级排序当候选池超过 2 个时)
按 P1 → P2 → P3 → P4 规则排序。
### 步骤 5强制截断
只保留排序后的前 2 个意图标签。
### 步骤 6完整性自检输出前必须执行
- [ ] 每个子诉求是否都经过了置信度评分?
- [ ] 是否有总分 ≥ 60 分的意图被遗漏?
- [ ] 最终输出是否 ≤ 2 个标签?
- [ ] 截断时是否按优先级规则执行?
- [ ] 所有输出的标签是否都在 `{intent_tags}` 中?
- [ ] 每个标签的维度判定是否基于语义本质而非字面后缀?
- [ ] 是否遵循了中心语优先原则(""字短语中诉求维度优先于主题维度)?
如果任何一项为"",重新分析。
---
## 分类示例
(假设传入标签列表包含:`3D建模`、`3D角色建模`、`课程咨询`、`就业咨询`、`住宿咨询`、`退费咨询`、`学不会怎么办`、`试听课`、`3D场景建模`
### 示例12个意图无需截断
历史会话:(空)
当前问题3D建模培训的课程内容有哪些
分析:
- 主题维度:"3D建模" → 直接匹配 = 60分 → 候选池
- 诉求维度:"课程内容" → 直接匹配"课程咨询" = 60分 → 候选池
- 候选池共2个无需截断
分类结果:
{{"intents": ["3D建模", "课程咨询"]}}
### 示例2并列拆分 + 阈值过滤 + 截断)
历史会话:(空)
当前问题:流程、费用和就业支持
分析:
- 拆分:[流程, 费用, 就业支持]
- "流程" → 直接匹配"课程咨询" = 60分 → 候选池
- "费用" → 直接匹配"课程咨询" = 60分 → 候选池(去重)
- "就业支持" → 直接匹配"就业咨询" = 60分 → 候选池
- 候选池:["课程咨询", "就业咨询"]共2个无需截断
分类结果:
{{"intents": ["课程咨询", "就业咨询"]}}
### 示例33个意图强制截断为2个
历史会话:(空)
当前问题:我想学角色,不知道能不能学,好不好学
分析:
- 主题维度:"学角色" → 直接匹配"3D角色建模" = 60分 → 候选池
- 诉求维度1"想学" → 直接匹配"课程咨询" = 60分 → 候选池
- 诉求维度2"不知道能不能学" → 语义关联(40) + 上下文支持(20) = 60分 → 候选池
- 诉求维度3"好不好学" → 语义关联(40) + 上下文支持(20) = 60分 → 候选池(与上同标签,去重)
- 候选池:["3D角色建模", "课程咨询", "学不会怎么办"]共3个需截断
- 排序:课程咨询(P1当前直接) &gt; 学不会怎么办(P1当前直接) &gt; 3D角色建模(P3主题维度)
- 截断后保留前2个
分类结果:
{{"intents": ["课程咨询", "学不会怎么办"]}}
### 示例45个意图强制截断为2个
历史会话:(空)
当前问题3D建模费用、就业、住宿、退费
分析:
- "3D建模" → 直接匹配 = 60分 → 候选池
- "费用" → 直接匹配"课程咨询" = 60分 → 候选池
- "就业" → 直接匹配"就业咨询" = 60分 → 候选池
- "住宿" → 直接匹配"住宿咨询" = 60分 → 候选池
- "退费" → 直接匹配"退费咨询" = 60分 → 候选池
- 候选池共5个需截断
- 排序:课程咨询(P1当前直接,出现第2) &gt; 就业咨询(P1当前直接,出现第3) &gt; 住宿咨询 &gt; 退费咨询 &gt; 3D建模(P3主题维度)
- 截断后保留前2个
分类结果:
{{"intents": ["课程咨询", "就业咨询"]}}
### 示例5历史补全被过滤 + 截断)
历史会话:
- 用户我想学3D建模
当前问题:多少钱、就业情况、住宿条件
分析:
- 主题维度:"3D建模"历史补全 = 10分 &lt; 60分 → 不进入候选池
- "多少钱" → 直接匹配"课程咨询" = 60分 → 候选池
- "就业情况" → 直接匹配"就业咨询" = 60分 → 候选池
- "住宿条件" → 直接匹配"住宿咨询" = 60分 → 候选池
- 候选池:["课程咨询", "就业咨询", "住宿咨询"]共3个需截断
- 排序:课程咨询(P1,出现第1) &gt; 就业咨询(P1,出现第2) &gt; 住宿咨询(P1,出现第3)
- 截断后保留前2个
分类结果:
{{"intents": ["课程咨询", "就业咨询"]}}
### 示例6语义关联不足60分全部被过滤
历史会话:(空)
当前问题:这个课有意思吗
分析:
- "有意思" → 语义关联"课程咨询" = 40分无上下文支持 = 0分总分40分 &lt; 60分 → 不进入候选池
- 候选池为空
分类结果:
{{"intents": []}}
### 示例 7中心语优先 + P3 排序生效)
历史会话:(空)
当前问题:还有 3D 场景建模的试听课,你发我一下。另外,住宿要多少钱呢?
分析:
拆分:[3D场景建模的试听课, 住宿要多少钱]
- 子诉求1"3D场景建模的试听课"
- 主题维度:"3D场景建模" → 直接匹配 = 60分 → 候选池
- 诉求维度(中心语):"试听课" → 直接匹配 = 60分语义为「用户想要试听服务」→ 候选池
- 子诉求2"住宿要多少钱"
- 诉求维度:"住宿咨询" → 直接匹配 = 60分 → 候选池
- 候选池:["3D场景建模", "试听课", "住宿咨询"],共 3 个,需截断
- 排序:
- P1三者均为当前直接表达平级
- P2三者均为60分平级
- P3试听课(诉求) = 住宿咨询(诉求) &gt; 3D场景建模(主题)
- P4试听课子诉求1中心语&gt; 住宿咨询子诉求2中心语&gt; 3D场景建模子诉求1定语
- 截断后保留前 2 个
分类结果:
{{"intents": ["试听课", "住宿咨询"]}}
---
## 现在请处理以下输入
当前问题:{query}
历史聊天:{history_text}
完整标签列表:{intent_tags}
请直接输出JSON
"""
async def rewrite_query(state:QueryQAState):
"""
问题改写
:param query: 用户问题
:param history_text: 历史会话
:return: 改写后的问题
"""
history_text = state.get("history", "")
query = state.get("original_query", "")
prompt = PromptTemplate(template=problem_rewriting_prompt, input_variables=["history_text", "query","intent_tags"])
output = JsonOutputParser()
chain = prompt | llm | output
intent_repo = IntentClassificationRepository()
try:
result = await chain.ainvoke({"history_text": history_text, "query": query, "intent_tags":str(await intent_repo.get_style_intent_categories())})
rewritten_query = result.get("intents", "")
logger.info(f"原问题:{query},历史会话:{history_text},改写后问题:{rewritten_query}")
return {"rewritten_query": rewritten_query}
except Exception as e:
logger.error(f"QA质量检查prompt模板错误{e}")
return {"rewritten_query": query} # 兜底