huoyan-enterprise/backend/services/rag_intent_service.py

213 lines
6.6 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.

"""
RAG 意图判断服务
基于 server 实现的智能路由策略
"""
import json
from typing import List, Dict, Optional
from pydantic import BaseModel, Field
from langchain_core.prompts import PromptTemplate
from core.llm_catalog import build_chat_model
from logger.logging import get_logger
logger = get_logger(__name__)
class FileIntent(BaseModel):
"""单个文件的意图判断结果"""
file_name: str = Field(description="文件名")
file_id: int = Field(description="文件ID")
question_type: str = Field(
description="问题类型: summary(需要全文), search(向量检索), excel_analysis(表格分析)",
default="search"
)
class RagIntentResult(BaseModel):
"""RAG 意图判断结果"""
result: List[FileIntent] = Field(description="涉及的文件及其处理方式", default=[])
# 意图判断的 Prompt参考 server 实现)
INTENT_JUDGE_PROMPT = """
你是一个 RAG 问答系统的意图分类器。请根据用户的问题和文件摘要,判断:
1. 哪些文件与问题相关
2. 每个文件需要什么类型的处理
## 任务 1过滤文件列表
- 从候选文件中选出与用户问题相关的文件
- 按关联度从高到低排序
- 若问题中有"本文""这个文件"等指代词,结合上下文判断
- 若无法判断相关文件,返回空数组
## 任务 2问题类型判断
对每个文件,判断用户需要什么类型的处理:
### "summary" - 需要完整文件内容
适用于以下情况:
- 需要文件的全部内容才能回答(如:总结、概括、归纳、分析)
- 基于文件的整体内容问答
- **简单的事实查询**(如"XX是多少""XX排名第几""XX是什么""谁夺冠"等)
- 文件内容的改写、润色、翻译
- 图片内容的具体描述
- 问题中提到"文件""文档""文章""图片"等词语
- **🔑 重要:当不确定时,优先选择 summary**
**示例**
- "总结一下这个文档的主要内容"
- "詹姆斯得了多少分"(简单事实查询 → summary
- "南京在苏超的排名是第几"(简单事实查询 → summary
- "成年人的修养是什么"(需要完整文档内容 → summary
- "翻译这篇文章"
### "search" - 只需部分内容(向量检索)
适用于以下情况:
- 只需要在文件中定位、查找或提取特定的、局部的内容
- 基于关键词的搜索
- 问题明确指向某个具体片段
**示例**
- "文件中哪里提到了xxx"
- "找出关于xxx的段落"
- "第三章讲了什么"
### "excel_analysis" - 表格数据分析
适用于以下情况:
- 文件类型必须为 xlsx、xls、csv
- 基于表格的数据问答、筛选、排序、汇总、统计分析
- 查询单元格、行、列数据
**示例**
- "A1单元格是什么"
- "第二行第三列的值是多少"
- "计算平均值"
## 输入信息
候选文件列表(按上传时间排序,最后一个为最新):
{{ file_list }}
文件摘要信息:
{{ file_summaries }}
用户问题:
{{ query }}
## 输出格式
严格按照以下 JSON 格式输出,不要输出其他内容:
```json
{
"result": [
{
"file_name": "文件名",
"file_id": 123,
"question_type": "summary"
}
]
}
```
如果没有相关文件,返回:
```json
{
"result": []
}
```
"""
class RagIntentService:
"""RAG 意图判断服务"""
def __init__(self):
self.model = build_chat_model(
provider="tongyi",
api_model="qwen-plus-latest",
streaming=False,
temperature=0.1, # 降低温度,让判断更稳定
)
async def judge_intent(
self,
query: str,
file_list: List[Dict[str, any]],
chat_history: Optional[List[str]] = None
) -> List[FileIntent]:
"""
判断用户问题的 RAG 意图
Args:
query: 用户问题
file_list: 文件列表 [{"file_id": 1, "file_name": "test.docx", "summary": "..."}]
chat_history: 聊天历史(可选)
Returns:
List[FileIntent]: 涉及的文件及其处理方式
"""
try:
# 构建文件列表字符串
file_names = [f["file_name"] for f in file_list]
file_list_str = ", ".join(file_names)
# 构建文件摘要字符串
file_summaries_str = ""
for f in file_list:
file_summaries_str += f"{f['file_name']}ID: {f['file_id']}:\n"
summary = f.get('summary', '无摘要')
# 截取摘要前 500 字符(避免过长)
if len(summary) > 500:
summary = summary[:500] + "..."
file_summaries_str += f"{summary}\n\n"
# 构建完整输入
full_query = query
if chat_history:
history_str = "\n".join(chat_history[-3:]) # 最近3轮对话
full_query = f"【聊天历史】\n{history_str}\n\n【当前问题】\n{query}"
# 创建 Prompt
prompt_template = PromptTemplate(
template=INTENT_JUDGE_PROMPT,
input_variables=["file_list", "file_summaries", "query"],
template_format="jinja2"
)
# 调用 LLM 判断意图(使用 Pydantic schema
chain = prompt_template | self.model.with_structured_output(
schema=RagIntentResult
)
result = await chain.ainvoke({
"file_list": file_list_str,
"file_summaries": file_summaries_str,
"query": full_query
})
# 解析结果with_structured_output 直接返回 Pydantic 对象)
if isinstance(result, RagIntentResult):
intents = result.result
logger.info(f"意图判断完成: {len(intents)} 个文件")
for intent in intents:
logger.info(f" - {intent.file_name} ({intent.file_id}): {intent.question_type}")
return intents
else:
logger.warning(f"意图判断返回格式异常: {type(result)}")
return []
except Exception as e:
logger.error(f"意图判断失败: {e}")
return []
# 全局实例(单例模式)
_intent_service = None
async def get_rag_intent_service() -> RagIntentService:
"""获取 RAG 意图服务实例(单例)"""
global _intent_service
if _intent_service is None:
_intent_service = RagIntentService()
return _intent_service