schema.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. from concurrent.futures import ThreadPoolExecutor
  2. from langchain_core.output_parsers import StrOutputParser
  3. from langchain_core.prompts import PromptTemplate
  4. import json
  5. class RAGWithQueryRewriting:
  6. """集成查询重写的 RAG 系统"""
  7. def __init__(self, retriever, llm, rewrite_chain):
  8. self.retriever = retriever
  9. self.llm = llm
  10. self.rewrite_chain = rewrite_chain
  11. def invoke(self, question):
  12. # 第一步:重写查询
  13. rewritten_query = self.rewrite_chain.invoke({"query": question})
  14. print(f"原始问题: {question}")
  15. print(f"重写后: {rewritten_query}")
  16. # 第二步:用重写后的查询进行检索
  17. docs = self.retriever.invoke(rewritten_query)
  18. # 第三步:用原始问题 + 检索结果生成答案
  19. context = "\n\n".join([doc.page_content for doc in docs])
  20. answer_prompt = f"""基于以下上下文回答用户问题。如果上下文中没有相关信息,请说明。
  21. 上下文:{context}
  22. 用户问题:{question}
  23. 答案:"""
  24. answer = self.llm.invoke(answer_prompt).content
  25. return {
  26. "original_query": question,
  27. "rewritten_query": rewritten_query,
  28. "retrieved_docs": docs,
  29. "answer": answer
  30. }
  31. class RAGWithDecomposition:
  32. """集成查询分解的 RAG 系统"""
  33. def __init__(self, retriever, llm):
  34. self.retriever = retriever
  35. self.llm = llm
  36. def invoke(self, question):
  37. decompose_prompt = PromptTemplate(
  38. input_variables=["question"],
  39. template="""你是一个问题分解助手。请将用户的复杂问题分解为 2-4 个独立的子问题,
  40. 每个子问题应该能独立检索和回答。
  41. 要求:
  42. 1. 子问题之间互不依赖,可以并行检索
  43. 2. 子问题覆盖原始问题的所有方面
  44. 3. 每个子问题简洁明确
  45. 4. 以 JSON 数组格式输出
  46. 用户问题: {question}
  47. 输出格式示例: ["子问题1", "子问题2", "子问题3"]
  48. 子问题列表:"""
  49. )
  50. decompose_chain = decompose_prompt | self.llm | StrOutputParser()
  51. # 第一步:分解问题
  52. sub_queries = self.decompose_query(question, decompose_chain)
  53. print(f"分解为 {len(sub_queries)} 个子问题")
  54. # 第二步:并行检索并合并
  55. all_docs = self.parallel_retrieve_and_merge(sub_queries, self.retriever)
  56. print(f"合并后共 {len(all_docs)} 个文档片段")
  57. # 第三步:用所有上下文生成综合答案
  58. context = "\n\n".join([doc.page_content for doc in all_docs])
  59. answer_prompt = f"""基于以下上下文,全面回答用户的问题。请综合所有相关信息,给出完整、有条理的答案。上下文:{context}
  60. 用户问题:{question}
  61. 答案:"""
  62. answer = self.llm.invoke(answer_prompt).content
  63. return {
  64. "question": question,
  65. "sub_queries": sub_queries,
  66. "doc_count": len(all_docs),
  67. "answer": answer
  68. }
  69. # 使用 ThreadPoolExecutor 创建线程池,并行执行检索
  70. # 每个子问题独立检索,互不干扰
  71. # 按内容哈希值去重(避免重复文档)
  72. def parallel_retrieve_and_merge(self, sub_queries, retriever, k_per_query=5):
  73. """
  74. 对每个子问题并行检索,然后合并去重
  75. """
  76. all_docs = []
  77. seen_contents = set()
  78. def retrieve_one(query):
  79. return retriever.invoke(query)
  80. # 创建线程池,线程数等于子问题数量
  81. with ThreadPoolExecutor(max_workers=len(sub_queries)) as executor:
  82. # 提交所有检索任务
  83. futures = [executor.submit(retrieve_one, q) for q in sub_queries]
  84. ## 收集结果
  85. for future in futures:
  86. docs = future.result()
  87. for doc in docs:
  88. # 按内容去重
  89. content_hash = hash(doc.page_content)
  90. if content_hash not in seen_contents:
  91. seen_contents.add(content_hash)
  92. all_docs.append(doc)
  93. return all_docs
  94. def decompose_query(self, question: str, decompose_chain) -> list[str]:
  95. """将复杂问题分解为子问题"""
  96. result = decompose_chain.invoke({"question": question})
  97. # 解析 JSON 数组
  98. try:
  99. sub_queries = json.loads(result.strip())
  100. return sub_queries
  101. except json.JSONDecodeError:
  102. # 兜底:按行分割
  103. return [line.strip() for line in result.strip().split("\n") if line.strip()]