rag_chat_agent.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import asyncio
  2. import json
  3. from typing import List
  4. from applications.config import Chunk
  5. from applications.api import fetch_deepseek_completion
  6. class RAGChatAgent:
  7. @staticmethod
  8. def generate_summary_prompt(query, search_results):
  9. """
  10. 生成总结的prompt。交给AI根据搜索结果判断内容是否对回答问题有帮助,
  11. 并结合内容生成总结和判断是否能回答问题。
  12. :param query: 问题
  13. :param search_results: 搜索结果列表,每个元素包含 'content', 'contentSummary'
  14. :return: 生成的总结prompt
  15. """
  16. prompt = f"问题: {query}\n\n请结合以下搜索结果,生成一个总结:\n"
  17. weighted_summaries = []
  18. weighted_contents = []
  19. # 将所有搜索结果的摘要和内容按相似度排序
  20. for result in search_results:
  21. content = result["content"]
  22. content_summary = result["contentSummary"]
  23. weighted_summaries.append(content_summary)
  24. weighted_contents.append(content)
  25. # 拼接加权摘要和内容
  26. prompt += "\n-- 内容摘要 --\n"
  27. for summary in weighted_summaries:
  28. prompt += f"摘要: {summary}\n"
  29. prompt += "\n-- 内容 --\n"
  30. for content in weighted_contents:
  31. prompt += f"内容: {content}\n"
  32. # 约束 AI 输出 JSON
  33. prompt += """
  34. 请根据上述内容判断能否回答问题,并生成一个总结,返回 JSON 格式,结构如下:
  35. {
  36. "query": "<原始问题>",
  37. "summary": "<简洁总结>",
  38. "relevance_score": <0到1之间的小数,表示总结与问题的相关度>,
  39. "status": <判断能否回答这个问题,0代表不能回答,1代表可以回答>
  40. }
  41. 注意:
  42. - 只输出 JSON,不要额外解释。
  43. - relevance_score 数字越大,表示总结和问题越相关。
  44. - 请根据问题和给定的搜索结果内容,判断是否能回答该问题。返回一个 0 或 1 的 status,表示能否回答问题。
  45. """
  46. return prompt
  47. async def chat_with_deepseek(self, query, search_results):
  48. prompt = self.generate_summary_prompt(query, search_results)
  49. response = await fetch_deepseek_completion(
  50. model="DeepSeek-V3", prompt=prompt, output_type="json"
  51. )
  52. return response
  53. @staticmethod
  54. def create_query_prompt(question: str) -> str:
  55. """
  56. 封装生成查询的 prompt,用于发送给 AI 模型。
  57. 参数:
  58. - question: 需要查询的问题(字符串)
  59. 返回:
  60. - 返回一个格式化的 prompt 字符串,用于向 AI 提问
  61. """
  62. prompt = f"""
  63. 你是一个智能助手,能够帮助我查询任何问题并返回答案。你的任务是接收到我给定的问题后,通过网络查询相关的信息,并以 JSON 格式返回查询结果。
  64. 问题:{question}
  65. 请查询并返回该问题的答案。返回的 JSON 应该包含以下字段:
  66. - "question": 问题的原始文本。
  67. - "answer": 问题的答案内容。
  68. - "source": 如果有来源,提供信息来源链接或描述。
  69. - "status": 表示查询是否成功,"1代表成功,0代表失败"。
  70. 如果无法找到答案,返回 "status": "failure",并且 "answer" 字段应为 "No answer found"。
  71. 例子:
  72. {{
  73. "question": "什么是量子计算?",
  74. "answer": "量子计算是一种基于量子力学原理的计算模型。",
  75. "source": "https://example.com/quantum-computing",
  76. "status": 1
  77. }}
  78. """
  79. return prompt
  80. async def llm_search(self, query):
  81. prompt = self.create_query_prompt(query)
  82. response = await fetch_deepseek_completion(
  83. model="DeepSeek-V3", prompt=prompt, output_type="json"
  84. )
  85. return response
  86. @staticmethod
  87. def make_decision_prompt(query, chat_res, search_res):
  88. # 创建一个 prompt 请求给大模型
  89. prompt = f"""
  90. 这是一个问题的回答任务,以下是两个来源的结果:
  91. 1. **RAG 搜索回答**:
  92. 问题: {query}
  93. 总结: {chat_res["summary"]}
  94. 相关度评分: {chat_res["relevance_score"]}
  95. 状态: {"可以回答" if chat_res["status"] == 1 else "无法回答"}
  96. 2. **AI 搜索结果**:
  97. 问题: {query}
  98. 回复内容: {search_res["content"]}
  99. 搜索结果: {json.dumps(search_res["search_results"], ensure_ascii=False)}
  100. 基于这两个结果,请你综合判断并生成一个更好的答案,如果可能的话。你可以选择结合 `chat_res` 和 `search_res`,或者选择其中更合适的一个进行回答。如果没有足够的信息可以回答,请用你自己已有的知识回答"。
  101. 基于回答的结果,总结回答的答案中使用的工具,名称以及用途,如果没有涉及到工具的使用,则不需要总结
  102. 请返回以下格式的 JSON 结果:
  103. {{
  104. "result": "<最终的答案,输出markdown格式>",
  105. "relevance_score": <0到1之间的小数,表示总结与问题的相关度>,
  106. "status": <1代表回答的好,0代表回答的不好>,
  107. "used_tools": ["工具名1: 用途描述", "工具名2: 用途描述"]
  108. }}
  109. """
  110. return prompt
  111. async def make_decision(self, query, chat_res, search_res):
  112. prompt = self.make_decision_prompt(query, chat_res, search_res)
  113. response = await fetch_deepseek_completion(
  114. model="DeepSeek-R1", prompt=prompt, output_type="json"
  115. )
  116. return response
  117. @staticmethod
  118. def split_query_prompt(query):
  119. prompt = f"""
  120. 你是一个信息检索助理,负责把用户的问题拆解为“更宽泛但仍同类、且彼此不重叠”的子问题,用于召回多样证据。
  121. 【目标】
  122. - 生成 1–3 个“更宽泛”的子问题(broad questions),它们应与原问题同一类别/主题,但从不同角度扩展;避免把原问题切得更细(avoid over-specific)。
  123. - 子问题之间尽量覆盖不同维度(例如:背景/原理、影响/应用、比较/趋势、方法/评估 等),减少语义重叠(≤20% 相似度)。
  124. 【必须遵守】
  125. 1) 与原问题同类:如果原问题是技术/科普/对比/流程类,子问题也应保持同类语气与目标。
  126. 2) 更宽泛:去掉过细的限制(具体数值/版本/人名/时间点/实现细节),但保留主题核心。
  127. 3) 去重与互补:不要改写成近义句;每个子问题关注的面不同(角度、层面或受众不同)。
  128. 4) 可检索性:避免抽象空话;每个子问题都应是可用于检索/召回的良好查询。
  129. 5) 数量自适应:若无法合理扩展到 3 个,就输出 1–2 个;不要为了凑数而重复。
  130. 6) 语言一致:与原问题同语言输出(中文入→中文出;英文入→英文出)。
  131. 7) 仅输出 JSON,严格符合下述 schema;不要输出额外文字或注释。
  132. 原始问题:
  133. {query}
  134. 请按照以下JSON格式返回结果:
  135. {{
  136. "original_question": "原始问题",
  137. "split_questions": [
  138. "第一个宽泛问题", "第二个宽泛问题", "第三个宽泛问题"
  139. ]
  140. }}
  141. 请确保返回的内容是纯JSON格式,不要包含其他任何文字。
  142. """
  143. return prompt
  144. async def split_query(self, query):
  145. prompt = self.split_query_prompt(query)
  146. response = await fetch_deepseek_completion(
  147. model="DeepSeek-V3", prompt=prompt, output_type="json"
  148. )
  149. return response