rag_chat_agent.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import asyncio
  2. from typing import List
  3. from applications.config import Chunk
  4. from applications.api import fetch_deepseek_completion
  5. class RAGChatAgent:
  6. @staticmethod
  7. def generate_summary_prompt(query, search_results):
  8. """
  9. 生成总结的prompt。交给AI根据搜索结果判断内容是否对回答问题有帮助,
  10. 并结合内容生成总结和判断是否能回答问题。
  11. :param query: 问题
  12. :param search_results: 搜索结果列表,每个元素包含 'content', 'contentSummary'
  13. :return: 生成的总结prompt
  14. """
  15. prompt = f"问题: {query}\n\n请结合以下搜索结果,生成一个总结:\n"
  16. weighted_summaries = []
  17. weighted_contents = []
  18. # 将所有搜索结果的摘要和内容按相似度排序
  19. for result in search_results:
  20. content = result["content"]
  21. content_summary = result["contentSummary"]
  22. weighted_summaries.append(content_summary)
  23. weighted_contents.append(content)
  24. # 拼接加权摘要和内容
  25. prompt += "\n-- 内容摘要 --\n"
  26. for summary in weighted_summaries:
  27. prompt += f"摘要: {summary}\n"
  28. prompt += "\n-- 内容 --\n"
  29. for content in weighted_contents:
  30. prompt += f"内容: {content}\n"
  31. # 约束 AI 输出 JSON
  32. prompt += """
  33. 请根据上述内容判断能否回答问题,并生成一个总结,返回 JSON 格式,结构如下:
  34. {
  35. "query": "<原始问题>",
  36. "summary": "<简洁总结>",
  37. "relevance_score": <0到1之间的小数,表示总结与问题的相关度>,
  38. "status": <判断能否回答这个问题,0代表不能回答,1代表可以回答>
  39. }
  40. 注意:
  41. - 只输出 JSON,不要额外解释。
  42. - relevance_score 数字越大,表示总结和问题越相关。
  43. - 请根据问题和给定的搜索结果内容,判断是否能回答该问题。返回一个 0 或 1 的 status,表示能否回答问题。
  44. """
  45. return prompt
  46. async def chat_with_deepseek(self, query, search_results):
  47. prompt = self.generate_summary_prompt(query, search_results)
  48. response = await fetch_deepseek_completion(
  49. model="DeepSeek-V3", prompt=prompt, output_type="json"
  50. )
  51. return response
  52. @staticmethod
  53. def create_query_prompt(question: str) -> str:
  54. """
  55. 封装生成查询的 prompt,用于发送给 AI 模型。
  56. 参数:
  57. - question: 需要查询的问题(字符串)
  58. 返回:
  59. - 返回一个格式化的 prompt 字符串,用于向 AI 提问
  60. """
  61. prompt = f"""
  62. 你是一个智能助手,能够帮助我查询任何问题并返回答案。你的任务是接收到我给定的问题后,通过网络查询相关的信息,并以 JSON 格式返回查询结果。
  63. 问题:{question}
  64. 请查询并返回该问题的答案。返回的 JSON 应该包含以下字段:
  65. - "question": 问题的原始文本。
  66. - "answer": 问题的答案内容。
  67. - "source": 如果有来源,提供信息来源链接或描述。
  68. - "status": 表示查询是否成功,"1代表成功,0代表失败"。
  69. 如果无法找到答案,返回 "status": "failure",并且 "answer" 字段应为 "No answer found"。
  70. 例子:
  71. {{
  72. "question": "什么是量子计算?",
  73. "answer": "量子计算是一种基于量子力学原理的计算模型。",
  74. "source": "https://example.com/quantum-computing",
  75. "status": 1
  76. }}
  77. """
  78. return prompt
  79. async def llm_search(self, query):
  80. prompt = self.create_query_prompt(query)
  81. response = await fetch_deepseek_completion(
  82. model="DeepSeek-V3", prompt=prompt, output_type="json"
  83. )
  84. return response
  85. @staticmethod
  86. def make_decision_prompt(chat_res, search_res):
  87. # 创建一个 prompt 请求给大模型
  88. prompt = f"""
  89. 这是一个问题的回答任务,以下是两个来源的结果:
  90. 1. **RAG 搜索回答**:
  91. 问题: {chat_res["query"]}
  92. 总结: {chat_res["summary"]}
  93. 相关度评分: {chat_res["relevance_score"]}
  94. 状态: {"可以回答" if chat_res["status"] == 1 else "无法回答"}
  95. 2. **AI 搜索结果**:
  96. 问题: {search_res["question"]}
  97. 答案: {search_res["answer"]}
  98. 来源: {search_res["source"]}
  99. 状态: {"可以回答" if search_res["status"] == 1 else "无法回答"}
  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, chat_res, search_res):
  112. prompt = self.make_decision_prompt(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