rag_service.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. """
  2. RAG服务工具类 (基于LangChain实现)
  3. 使用LangChain + Milvus实现RAG服务
  4. 支持从Milvus向量数据库检索相关文档并通过大模型生成回答
  5. 作者:开发工程师
  6. 日期:2025年01月
  7. """
  8. import logging
  9. from typing import List, Optional, Dict, Any
  10. from pydantic import BaseModel
  11. from langchain_milvus import Milvus
  12. from langchain_openai import ChatOpenAI
  13. from langchain_community.callbacks.manager import get_openai_callback
  14. from langfuse.langchain import CallbackHandler as LangfuseCallbackHandler
  15. from config import Config
  16. from rerank_service import get_rerank_service
  17. # 配置日志
  18. logger = logging.getLogger(__name__)
  19. langfuse_handler = LangfuseCallbackHandler()
  20. class SourceDocument(BaseModel):
  21. """源文档信息模型"""
  22. doc_name: str # 文档名称
  23. doc_path_name: str # 文档完整路径
  24. doc_type: str # 文档类型
  25. content_preview: str # 内容预览(前200字符)
  26. similarity_score: Optional[float] = None # 相似度分数
  27. class TokenUsage(BaseModel):
  28. """Token使用情况模型"""
  29. prompt_tokens: int # 输入token数
  30. completion_tokens: int # 输出token数
  31. total_tokens: int # 总token数
  32. class RAGResponse(BaseModel):
  33. """RAG服务响应模型"""
  34. content: str # 大模型回复给用户的文本内容
  35. source_documents: List[SourceDocument] # 检索使用的源文档列表
  36. token_usage: Optional[TokenUsage] = None # token使用情况
  37. success: bool = True # 查询是否成功
  38. error_message: Optional[str] = None # 错误信息
  39. scene_info: Optional[Dict[str, Any]] = None # 场景检测信息
  40. class RAGService:
  41. """RAG服务类 (基于LangChain实现)
  42. 使用LangChain + Milvus向量数据库实现检索增强生成服务
  43. 支持OpenAI兼容的大模型和嵌入模型
  44. """
  45. # 场景检测提示词模板
  46. SCENE_DETECTION_TEMPLATE = """请分析用户问题属于以下哪种客服场景类型,只返回对应的数字:
  47. 1. 产品咨询类 - 询问产品功能、规格、价格等基本信息
  48. 2. 售后服务类 - 退换货、维修、质量问题等售后相关
  49. 3. 账户相关类 - 登录、注册、密码、个人信息等账户问题
  50. 4. 投诉建议类 - 对服务或产品的投诉、意见、建议
  51. 5. 技术支持类 - 使用方法、故障排除、技术配置等
  52. 6. 其他咨询类 - 不属于以上分类的一般性咨询
  53. 用户问题: {question}
  54. 请只返回场景类型对应的数字(1-6):"""
  55. # 统一的提示词模板 - 专业客服版本
  56. PROMPT_TEMPLATE = """你是一位专业的客服人员,请根据提供的知识库内容来回答用户的问题。
  57. 知识库内容:
  58. {context}
  59. 用户问题: {question}
  60. 回答要求:
  61. 1. 【角色定位】你是一位专业、耐心、友善的客服代表
  62. 2. 【回答原则】严格基于知识库内容回答,不得编造或推测信息
  63. 3. 【准确性要求】
  64. - 如果知识库中有明确答案,请准确完整地回答
  65. - 如果知识库中信息不完整,说明现有信息并提示用户可联系人工客服获取更详细信息
  66. - 如果知识库中完全没有相关信息,请礼貌地说明无法找到相关资料,建议用户转接人工客服
  67. 4. 【回答格式】
  68. - 使用纯文本格式,不使用markdown格式
  69. - 语言简洁明了,适合微信对话环境
  70. - 使用礼貌、专业的语调
  71. - 如需列举,使用数字序号或简单的分行
  72. 5. 【转人工提示】当遇到以下情况时,主动建议用户转接人工客服:
  73. - 复杂的售后问题
  74. - 需要个人账户信息查询的问题
  75. - 投诉或纠纷相关问题
  76. - 知识库无法覆盖的专业技术问题
  77. 请基于以上要求,为用户提供专业的客服回答:"""
  78. def __init__(self):
  79. """初始化RAG服务"""
  80. self.vector_store = None
  81. self.embeddings = None
  82. self.llm = None
  83. self.rerank_service = None # 重排序服务
  84. self._initialize_components()
  85. def _initialize_components(self):
  86. """初始化所有组件"""
  87. try:
  88. # 初始化嵌入模型
  89. self._initialize_embeddings()
  90. # 初始化大语言模型
  91. self._initialize_llm()
  92. # 初始化向量存储
  93. self._initialize_vector_store()
  94. # 初始化重排序服务
  95. self._initialize_rerank_service()
  96. logger.info("RAG服务初始化完成")
  97. except Exception as e:
  98. logger.error(f"RAG服务初始化失败: {e}")
  99. raise
  100. def _initialize_embeddings(self):
  101. """初始化嵌入模型"""
  102. try:
  103. # 使用自定义嵌入类,兼容OpenAI API
  104. from openai import OpenAI
  105. class CustomEmbeddings:
  106. def __init__(self, model_name: str, api_key: str, api_base: str, dimensions: int):
  107. self.model_name = model_name
  108. self.client = OpenAI(api_key=api_key, base_url=api_base)
  109. self.dimensions = dimensions
  110. def embed_query(self, text: str) -> List[float]:
  111. """嵌入单个查询"""
  112. return self.embed_documents([text])[0]
  113. def embed_documents(self, texts: List[str]) -> List[List[float]]:
  114. """嵌入多个文档"""
  115. try:
  116. response = self.client.embeddings.create(
  117. model=self.model_name,
  118. input=texts,
  119. dimensions=self.dimensions,
  120. encoding_format="float"
  121. )
  122. return [data.embedding for data in response.data]
  123. except Exception as e:
  124. logger.error(f"嵌入生成失败: {e}")
  125. raise
  126. self.embeddings = CustomEmbeddings(
  127. model_name=Config.LLM_EMBEDDING_MODEL_NAME,
  128. api_key=Config.LLM_EMBEDDING_API_KEY,
  129. api_base=Config.LLM_EMBEDDING_BASE_URL,
  130. dimensions=Config.MILVUS_VECTOR_DIM
  131. )
  132. # 测试嵌入模型
  133. test_embedding = self.embeddings.embed_query("测试")
  134. actual_dim = len(test_embedding)
  135. logger.info(f"嵌入模型初始化成功: {Config.LLM_EMBEDDING_MODEL_NAME}")
  136. logger.info(f"向量维度: {actual_dim}")
  137. if actual_dim != Config.MILVUS_VECTOR_DIM:
  138. logger.warning(f"向量维度不匹配! 实际({actual_dim}) != 期望({Config.MILVUS_VECTOR_DIM})")
  139. except Exception as e:
  140. logger.error(f"嵌入模型初始化失败: {e}")
  141. raise
  142. def _initialize_llm(self):
  143. """初始化大语言模型"""
  144. try:
  145. self.llm = ChatOpenAI(
  146. model=Config.LLM_MODEL_NAME,
  147. openai_api_key=Config.LLM_API_KEY,
  148. openai_api_base=Config.LLM_BASE_URL,
  149. temperature=0.1,
  150. max_tokens=800 # 调整为800,平衡详细度和简洁性
  151. )
  152. logger.info(f"大语言模型初始化成功: {Config.LLM_MODEL_NAME}")
  153. except Exception as e:
  154. logger.error(f"大语言模型初始化失败: {e}")
  155. raise
  156. def _initialize_vector_store(self):
  157. """初始化向量存储"""
  158. try:
  159. # 构建Milvus连接参数
  160. connection_args = {
  161. "host": Config.MILVUS_HOST,
  162. "port": Config.MILVUS_PORT,
  163. "db_name": Config.MILVUS_DATABASE
  164. }
  165. # 如果有用户名和密码,添加到连接参数中
  166. if Config.MILVUS_USER:
  167. connection_args["user"] = Config.MILVUS_USER
  168. if Config.MILVUS_PASSWORD:
  169. connection_args["password"] = Config.MILVUS_PASSWORD
  170. # 配置搜索参数,针对IVF_FLAT索引优化
  171. search_params = {
  172. "metric_type": "COSINE", # 与索引保持一致
  173. "params": {
  174. "nprobe": 64 # 建议设置为nlist的6.25% (64/1024),平衡性能和召回率
  175. }
  176. }
  177. # 初始化Milvus向量存储
  178. self.vector_store = Milvus(
  179. embedding_function=self.embeddings,
  180. collection_name=Config.MILVUS_COLLECTION_NAME,
  181. connection_args=connection_args,
  182. text_field="content", # 文本内容字段
  183. vector_field="content_vector", # 向量字段
  184. auto_id=True,
  185. search_params=search_params # 添加搜索参数
  186. )
  187. logger.info(f"Milvus向量存储初始化成功: {Config.MILVUS_COLLECTION_NAME}")
  188. logger.info(f"搜索参数: nprobe={search_params['params']['nprobe']}")
  189. except Exception as e:
  190. logger.error(f"Milvus向量存储初始化失败: {e}")
  191. raise
  192. def _initialize_rerank_service(self):
  193. """初始化重排序服务"""
  194. try:
  195. self.rerank_service = get_rerank_service()
  196. if self.rerank_service:
  197. logger.info("重排序服务初始化成功")
  198. else:
  199. logger.info("重排序服务未配置,将跳过rerank步骤")
  200. except Exception as e:
  201. logger.warning(f"重排序服务初始化失败: {e}")
  202. self.rerank_service = None
  203. def query_service(self, query: str, use_rerank: bool = True) -> RAGResponse:
  204. """核心查询服务方法
  205. Args:
  206. query: 用户输入的查询内容
  207. use_rerank: 是否使用重排序功能
  208. Returns:
  209. RAGResponse: 包含回答内容、源文档和token使用情况的响应对象
  210. """
  211. try:
  212. logger.info(f"🔍 开始处理查询(rerank={use_rerank}): {query}")
  213. if not query or not query.strip():
  214. return RAGResponse(
  215. content="请输入有效的查询内容",
  216. source_documents=[],
  217. success=False,
  218. error_message="查询内容为空"
  219. )
  220. # 第0步:场景检测(可选)
  221. # scene_info = self.detect_user_scene(query)
  222. scene_info = None # 暂时不使用场景检测
  223. # 第一步:向量检索获取候选文档
  224. initial_k = 10 if use_rerank and self.rerank_service else 3
  225. retriever = self.vector_store.as_retriever(
  226. search_type="similarity",
  227. search_kwargs={"k": initial_k}
  228. )
  229. # 获取候选文档
  230. candidate_docs = retriever.invoke(query)
  231. logger.info(f"📄 初始检索到 {len(candidate_docs)} 个候选文档")
  232. # 第二步:重排序(如果启用)
  233. final_docs = candidate_docs
  234. if use_rerank and self.rerank_service and len(candidate_docs) > 1:
  235. # 提取文档内容用于重排序
  236. doc_contents = [doc.page_content for doc in candidate_docs]
  237. # 增加重排序的top_n数量,确保不会过滤掉高相关度文档
  238. rerank_top_n = min(5, len(candidate_docs)) # 从3增加到5,但不超过候选文档数量
  239. # 执行重排序
  240. rerank_response = self.rerank_service.rerank_documents(
  241. query=query,
  242. documents=doc_contents,
  243. top_n=rerank_top_n
  244. )
  245. if rerank_response.success:
  246. # 根据重排序结果重新排列文档
  247. reranked_docs = []
  248. for rerank_doc in rerank_response.documents:
  249. if 0 <= rerank_doc.index < len(candidate_docs):
  250. original_doc = candidate_docs[rerank_doc.index]
  251. # 将相关性分数添加到元数据中
  252. if hasattr(original_doc, 'metadata'):
  253. original_doc.metadata['rerank_score'] = rerank_doc.relevance_score
  254. reranked_docs.append(original_doc)
  255. # 安全检查:确保原始最高相似度文档不会被完全过滤掉
  256. # 如果原始第1个文档不在重排序结果中,将其添加到结果中
  257. if candidate_docs and len(reranked_docs) > 0:
  258. first_doc = candidate_docs[0]
  259. first_doc_in_rerank = any(
  260. hasattr(doc, 'metadata') and
  261. doc.metadata.get('doc_name') == first_doc.metadata.get('doc_name') and
  262. doc.page_content == first_doc.page_content
  263. for doc in reranked_docs
  264. )
  265. if not first_doc_in_rerank:
  266. # 将原始最高相似度文档添加到重排序结果的开头
  267. if hasattr(first_doc, 'metadata'):
  268. first_doc.metadata['rerank_score'] = 1.0 # 给予最高分数
  269. reranked_docs.insert(0, first_doc)
  270. logger.info(f"🔒 安全检查:将原始最高相似度文档添加到重排序结果中")
  271. final_docs = reranked_docs[:3] # 最终仍然只取前3个
  272. logger.info(f"🔄 重排序完成,选择了 {len(final_docs)} 个文档")
  273. else:
  274. logger.warning(f"重排序失败,使用原始检索结果: {rerank_response.error_message}")
  275. # 第三步:使用选定的文档生成回答
  276. with get_openai_callback() as cb: ## 在上下文中获取 OpenAI 回调处理器,方便地公开令牌和成本信息
  277. # 构建上下文
  278. context = "\n\n".join([doc.page_content for doc in final_docs])
  279. # 使用统一的提示模板生成回答
  280. prompt = self.PROMPT_TEMPLATE.format(context=context, question=query)
  281. if (Config.LANGFUSE_ENABLE):
  282. answer = self.llm.invoke(prompt, config={"callbacks":[langfuse_handler]}).content
  283. else:
  284. answer = self.llm.invoke(prompt).content
  285. # 更新文档引用为最终选择的文档
  286. source_documents = final_docs
  287. logger.info(f"✅ 查询完成,检索到 {len(source_documents)} 个相关文档")
  288. logger.info(f"📄 答案长度: {len(answer)} 字符")
  289. logger.info(f"💰 Token使用: 输入{cb.prompt_tokens}, 输出{cb.completion_tokens}, 总计{cb.total_tokens}")
  290. # 处理源文档信息
  291. processed_source_docs = []
  292. for i, doc in enumerate(source_documents):
  293. try:
  294. # 提取文档元数据
  295. metadata = doc.metadata if hasattr(doc, 'metadata') else {}
  296. doc_name = metadata.get("doc_name", f"文档{i+1}")
  297. doc_path_name = metadata.get("doc_path_name", "")
  298. doc_type = metadata.get("doc_type", "unknown")
  299. rerank_score = metadata.get("rerank_score")
  300. # 获取内容预览
  301. content_preview = doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content
  302. source_doc = SourceDocument(
  303. doc_name=doc_name,
  304. doc_path_name=doc_path_name,
  305. doc_type=doc_type,
  306. content_preview=content_preview,
  307. similarity_score=rerank_score # 使用rerank分数
  308. )
  309. processed_source_docs.append(source_doc)
  310. score_info = f" (rerank: {rerank_score:.3f})" if rerank_score else ""
  311. logger.info(f"📖 文档{i+1}: {doc_name}{score_info} - {content_preview[:50]}...")
  312. except Exception as e:
  313. logger.warning(f"处理源文档{i+1}时出错: {e}")
  314. # 添加默认文档信息
  315. processed_source_docs.append(SourceDocument(
  316. doc_name=f"文档{i+1}",
  317. doc_path_name="",
  318. doc_type="unknown",
  319. content_preview="无法获取文档信息"
  320. ))
  321. # 构建token使用情况
  322. token_usage = TokenUsage(
  323. prompt_tokens=cb.prompt_tokens,
  324. completion_tokens=cb.completion_tokens,
  325. total_tokens=cb.total_tokens
  326. )
  327. return RAGResponse(
  328. content=answer if answer else "抱歉,我无法根据现有信息回答您的问题。",
  329. source_documents=processed_source_docs,
  330. token_usage=token_usage,
  331. success=True,
  332. scene_info=scene_info
  333. )
  334. except Exception as e:
  335. logger.error(f"❌ 查询服务失败: {e}")
  336. return RAGResponse(
  337. content="",
  338. source_documents=[],
  339. success=False,
  340. error_message=f"查询过程中发生错误:{str(e)}",
  341. scene_info=None
  342. )
  343. def get_similar_documents(self, query: str, top_k: int = 3) -> List[Dict[str, Any]]:
  344. """获取相似文档(用于调试和分析)
  345. Args:
  346. query: 查询内容
  347. top_k: 返回文档数量
  348. Returns:
  349. List[Dict]: 相似文档列表
  350. """
  351. try:
  352. logger.info(f"🔍 搜索相似文档: {query} (top_k={top_k})")
  353. # 使用向量存储进行相似性搜索
  354. docs = self.vector_store.similarity_search_with_score(query, k=top_k)
  355. results = []
  356. for doc, score in docs:
  357. result = {
  358. "content": doc.page_content,
  359. "metadata": doc.metadata,
  360. "similarity_score": float(score),
  361. "doc_name": doc.metadata.get("doc_name", "未知文档"),
  362. "doc_path_name": doc.metadata.get("doc_path_name", ""),
  363. "doc_type": doc.metadata.get("doc_type", "unknown")
  364. }
  365. results.append(result)
  366. logger.info(f"✅ 找到 {len(results)} 个相似文档")
  367. return results
  368. except Exception as e:
  369. logger.error(f"❌ 获取相似文档失败: {e}")
  370. return []
  371. def health_check(self) -> Dict[str, Any]:
  372. """健康检查
  373. Returns:
  374. Dict: 健康状态信息
  375. """
  376. status = {
  377. "service": "RAGService",
  378. "status": "healthy",
  379. "components": {},
  380. "timestamp": None
  381. }
  382. try:
  383. from datetime import datetime
  384. status["timestamp"] = datetime.now().isoformat()
  385. # 检查嵌入模型
  386. try:
  387. test_embedding = self.embeddings.embed_query("健康检查")
  388. status["components"]["embeddings"] = {
  389. "status": "healthy",
  390. "model": Config.LLM_EMBEDDING_MODEL_NAME,
  391. "dimension": len(test_embedding)
  392. }
  393. except Exception as e:
  394. status["components"]["embeddings"] = {
  395. "status": "error",
  396. "error": str(e)
  397. }
  398. status["status"] = "degraded"
  399. # 检查大语言模型
  400. try:
  401. with get_openai_callback() as cb:
  402. test_response = self.llm.invoke("你好").content
  403. status["components"]["llm"] = {
  404. "status": "healthy",
  405. "model": Config.LLM_MODEL_NAME,
  406. "response_length": len(test_response) if test_response else 0,
  407. "test_tokens": cb.total_tokens
  408. }
  409. except Exception as e:
  410. status["components"]["llm"] = {
  411. "status": "error",
  412. "error": str(e)
  413. }
  414. status["status"] = "degraded"
  415. # 检查向量存储
  416. try:
  417. test_docs = self.vector_store.similarity_search("测试", k=1)
  418. status["components"]["vector_store"] = {
  419. "status": "healthy",
  420. "collection": Config.MILVUS_COLLECTION_NAME,
  421. "test_results": len(test_docs)
  422. }
  423. except Exception as e:
  424. status["components"]["vector_store"] = {
  425. "status": "error",
  426. "error": str(e)
  427. }
  428. status["status"] = "degraded"
  429. # 检查重排序服务
  430. try:
  431. if self.rerank_service:
  432. rerank_health = self.rerank_service.health_check()
  433. status["components"]["rerank_service"] = rerank_health
  434. else:
  435. status["components"]["rerank_service"] = {
  436. "status": "not_configured",
  437. "message": "重排序服务未配置"
  438. }
  439. except Exception as e:
  440. status["components"]["rerank_service"] = {
  441. "status": "error",
  442. "error": str(e)
  443. }
  444. status["status"] = "degraded"
  445. except Exception as e:
  446. status["status"] = "error"
  447. status["error"] = str(e)
  448. return status
  449. def detect_user_scene(self, query: str) -> Dict[str, Any]:
  450. """检测用户问题的场景类型
  451. Args:
  452. query: 用户问题
  453. Returns:
  454. Dict: 包含场景类型和建议的字典
  455. """
  456. try:
  457. # 使用LLM进行场景检测
  458. prompt = self.SCENE_DETECTION_TEMPLATE.format(question=query)
  459. response = self.llm.invoke(prompt).content.strip()
  460. # 解析场景类型
  461. scene_mapping = {
  462. "1": {"type": "产品咨询类", "priority": "normal", "suggest_human": False},
  463. "2": {"type": "售后服务类", "priority": "high", "suggest_human": True},
  464. "3": {"type": "账户相关类", "priority": "high", "suggest_human": True},
  465. "4": {"type": "投诉建议类", "priority": "urgent", "suggest_human": True},
  466. "5": {"type": "技术支持类", "priority": "normal", "suggest_human": False},
  467. "6": {"type": "其他咨询类", "priority": "normal", "suggest_human": False}
  468. }
  469. scene_info = scene_mapping.get(response, scene_mapping["6"])
  470. scene_info["detected_number"] = response
  471. logger.info(f"🎯 场景检测结果: {query} -> {scene_info['type']} (优先级: {scene_info['priority']})")
  472. return scene_info
  473. except Exception as e:
  474. logger.warning(f"场景检测失败: {e}")
  475. return {"type": "其他咨询类", "priority": "normal", "suggest_human": False, "detected_number": "6"}
  476. # 全局RAG服务实例
  477. _rag_service_instance = None
  478. def get_rag_service() -> Optional[RAGService]:
  479. """获取RAG服务实例(单例模式)"""
  480. global _rag_service_instance
  481. if _rag_service_instance is None:
  482. try:
  483. _rag_service_instance = RAGService()
  484. logger.info("RAG服务实例创建成功")
  485. except Exception as e:
  486. logger.error(f"RAG服务实例创建失败: {e}")
  487. return None
  488. return _rag_service_instance
  489. # 便捷函数
  490. def query_question(question: str) -> RAGResponse:
  491. """查询问题的便捷函数
  492. Args:
  493. question: 用户问题
  494. Returns:
  495. RAGResponse: 查询响应
  496. """
  497. rag_service = get_rag_service()
  498. if rag_service is None:
  499. return RAGResponse(
  500. content="",
  501. source_documents=[],
  502. success=False,
  503. error_message="RAG服务初始化失败"
  504. )
  505. return rag_service.query_service(question)
  506. if __name__ == "__main__":
  507. # 测试代码 - 专业客服RAG系统
  508. rag = get_rag_service()
  509. # 检测健康状态
  510. health = rag.health_check()
  511. print(f"健康状态: {health}")
  512. # 测试不同场景的客服问题
  513. test_cases = [
  514. "帮我介绍一下盗窃罪", # 其他咨询类
  515. "我要退货,怎么办理?", # 售后服务类
  516. "忘记密码了,如何重置?", # 账户相关类
  517. "你们的服务太差了!", # 投诉建议类
  518. "产品无法连接WiFi", # 技术支持类
  519. ]
  520. print(100 * "=")
  521. for i, question in enumerate(test_cases, 1):
  522. print(100 * "*")
  523. print(f"\n=== 测试案例 {i}: {question} ===")
  524. # 测试场景检测
  525. scene_info = rag.detect_user_scene(question)
  526. print(f"🎯 场景类型: {scene_info['type']}")
  527. print(f"🚨 优先级: {scene_info['priority']}")
  528. print(f"👤 建议转人工: {'是' if scene_info['suggest_human'] else '否'}")
  529. # 测试完整查询
  530. response = rag.query_service(question, use_rerank=True)
  531. print(f"💬 客服回答: {response.content}")
  532. if response.scene_info:
  533. print(f"📊 场景信息: {response.scene_info['type']}")
  534. if response.token_usage:
  535. print(f"💰 Token使用: {response.token_usage.total_tokens}")
  536. print(f"📚 参考文档数: {len(response.source_documents)}")
  537. print(100 * "=")
  538. print("\n=== 测试经典产品咨询 ===")
  539. response = rag.query_service("介绍一下老人与海这本书", use_rerank=True)
  540. print(f"回答: {response.content}")
  541. print(f"场景信息: {response.scene_info}")
  542. print(f"源文档数量: {len(response.source_documents)}")
  543. if response.token_usage:
  544. print(f"Token使用: {response.token_usage.total_tokens}")
  545. for doc in response.source_documents:
  546. print("--------------------------------")
  547. print(f"源文档: {doc.doc_name}")
  548. print(f"相似度分数: {doc.similarity_score}")
  549. print(f"内容预览: {doc.content_preview[:50]}...")