107 lines
5.7 KiB
Python
107 lines
5.7 KiB
Python
import json
|
||
from typing import List
|
||
|
||
from langchain_core.output_parsers import StrOutputParser
|
||
from langchain_core.prompts import PromptTemplate
|
||
|
||
from app.llm import llm
|
||
from app.repository.milvus.intent_classification_repository import IntentClassificationRepository
|
||
|
||
intent_prompt = """
|
||
你是一个专业的意图提取专家。请分析以下文本内容,并结合当前已有的意图标签,判断是否符合;若不符合则生成一个新意图,符合则直接返回原意图。
|
||
|
||
## 输入说明
|
||
- **文本内容**:`{text}`
|
||
- **目前的意图**:`{intent}`(可能为空)
|
||
|
||
## 核心规则
|
||
|
||
### 1. 意图提取与对比规则
|
||
- 首先从文本中提取1个核心意图(提取方法见下方“意图提取规则”)。
|
||
- 将提取出的意图与给定的`{{intent}}`进行对比:
|
||
- 若两者**完全相同**(字符串完全一致),则最终输出意图为原意图`{{intent}}`。
|
||
- 若两者不同,或`{{intent}}`为空,则最终输出意图为新提取的意图。
|
||
- 无论采用原意图还是新意图,都必须来自或可扩充自“意图分类体系”。
|
||
|
||
### 2. 意图提取规则(用于从文本中提取核心意图)
|
||
- 若文本包含用户明确的问题,提取其核心诉求对应的意图。
|
||
- 若文本为销售介绍/陈述内容,**聚焦于其最想传达的核心功能或核心信息**,提取总结性意图。**不得将补充说明信息(如附带提到的地点、时间等)误判为核心意图。**
|
||
- 例如:文本为“线上课程后期可以转到线下,我们线下校区在上海”,核心是“线上转线下”这一功能,而非“校区分布”,因此应扩充为“线上转线下”,而不是使用“校区分布”。
|
||
- 意图必须基于实际内容,不编造。
|
||
- 仅提取1个意图标签。
|
||
|
||
### 3. 单意图选择规则(关键)
|
||
**只能输出一个意图标签,不能以顿号、逗号或“和”分隔多个意图。**
|
||
|
||
当内容可能对应多个意图时,按以下优先级选择唯一意图:
|
||
|
||
| 优先级 | 选择规则 | 说明 |
|
||
|-------|---------|------|
|
||
| **P0** | **优先匹配已有意图标签** | 必须优先从已有意图体系(含扩展示例)中选择最匹配的标签。**若已有标签中无准确对应,则按扩充规则创建新标签,严禁使用仅部分相关或语义模糊的已有标签。** 当销售单方面介绍课程内容(无用户明确提问)时,直接使用对应的主题标签(如“3D场景建模”),若主题为已有标签未覆盖的功能/服务(如“线上转线下”),则应扩充新标签,不得强行归类为“校区分布”等。 |
|
||
| **P1** | **核心诉求优先** | 选择最能代表文本核心诉求的意图,**忽略次要的补充信息**。例如:文本“线上课程可转线下,校区在上海” → 核心是“线上转线下”,而非“校区分布”;报名流程说明 → “报名咨询”,而非“线上直播教学”。 |
|
||
| **P2** | **主题维度优先于动作维度** | 当主题和动作诉求冲突时,优先选择主题维度。例如:3D建模费用 → “3D建模”(主题) |
|
||
| **P3** | **直接提及优先于间接关联** | 文本明确说出的核心意图优先于推断出的意图 |
|
||
| **P4** | **去重合并** | 多个子诉求属于同一类别时,只输出一个意图标签 |
|
||
|
||
### 4. 意图分类体系(必须从中选择或基于其扩充)
|
||
{intent_list}
|
||
|
||
**扩充规则:** 当文本核心意图无法被已有标签准确覆盖时,必须创建更精准的新标签。创建时:
|
||
- 新标签应简洁体现核心语义(如“线上转线下”)。
|
||
- 严格对比已有标签,确保无重复语义。
|
||
- **示例**:文本“线上课程后期可以转到线下” → 已有标签无对应,扩充为“线上转线下”;文本“原画好就业吗” → 扩充为“原画就业”。
|
||
|
||
### 5. 多意图处理
|
||
若同一段文本明确涉及多个不重叠的核心意图,仅选择最能代表核心内容的1个意图。
|
||
|
||
## 输出格式要求
|
||
- 输出纯JSON,不要任何解释、不要markdown标记、不要注释
|
||
- 确保所有字符串用双引号包裹
|
||
- 输出格式固定为:
|
||
{{
|
||
"intent": "最终的意图标签(原意图或新意图,只能有一个)"
|
||
}}
|
||
"""
|
||
from app.core.log import logger
|
||
class TextIntent:
|
||
|
||
async def intent_message(self, text:str, intent:str, intent_list:list[str]) -> str:
|
||
|
||
prompt_template = PromptTemplate(template=intent_prompt, input_variables=["text","intent","intent_list"])
|
||
chain = prompt_template | llm
|
||
result = await chain.ainvoke({"text": text,"intent":intent,"intent_list": str(intent_list)})
|
||
res_new = str(result.content) if hasattr(
|
||
result, 'content') else str(result)
|
||
|
||
intent:str=""
|
||
try:
|
||
data = json.loads(res_new)
|
||
intent = data.get("intent", "")
|
||
except (json.JSONDecodeError, AttributeError):
|
||
logger.info(f"无法解析JSON:{res_new}")
|
||
return intent
|
||
|
||
|
||
def _extract_jsons(self, content: str) -> List[str]:
|
||
results = []
|
||
start = 0
|
||
while start < len(content):
|
||
brace_start = content.find("{", start)
|
||
if brace_start == -1:
|
||
break
|
||
depth = 0
|
||
end = brace_start
|
||
for i in range(brace_start, len(content)):
|
||
if content[i] == "{":
|
||
depth += 1
|
||
elif content[i] == "}":
|
||
depth -= 1
|
||
if depth == 0:
|
||
end = i + 1
|
||
break
|
||
if end > brace_start:
|
||
results.append(content[brace_start:end])
|
||
start = end
|
||
else:
|
||
start = brace_start + 1
|
||
return results |