_langgraph_integration.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. """
  2. LangChain + LangGraph 集成示例
  3. 数据源:docs/菌群知识库/_chunks.jsonl(22,781 个 chunk)
  4. 两种用法:
  5. 1. RAG 检索增强(推荐 Qdrant/Pinecone)
  6. 2. LangGraph 多步 Agent(带分类路由)
  7. """
  8. # ============================================================
  9. # 方式一:RAG 检索增强(最小路径)
  10. # ============================================================
  11. from langchain_community.document_loaders import JSONLoader
  12. from langchain_text_splitters import RecursiveCharacterTextSplitter
  13. from langchain_community.embeddings import OpenAIEmbeddings # 或 BGE / text-embedding-3-large
  14. from langchain_community.vectorstores import QdrantVectorStore
  15. from langchain_community.vectorstores.utils import DistanceStrategy
  16. from langchain_core.runnables import RunnableParallel, RunnableLambda
  17. # 1. 加载 JSONL
  18. loader = JSONLoader(
  19. file_path=r"C:\code\cfc\docs\菌群知识库\_chunks.jsonl",
  20. jq_schema=".[] | {page_content: .content, metadata: .metadata}",
  21. content_key="page_content",
  22. metadata_key="metadata",
  23. )
  24. docs = loader.load()
  25. # 2. Embedding — 中文推荐 bge-m3 或 text-embedding-3-large
  26. embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
  27. # 3. 向量化存储(Qdrant 本地示例)
  28. vector_store = QdrantVectorStore.from_documents(
  29. documents=docs,
  30. embedding=embeddings,
  31. collection_name="guhejk_kb",
  32. url="http://localhost:6333",
  33. distance=DistanceStrategy.COSINE,
  34. )
  35. # 4. RAG 检索器(支持 metadata 过滤)
  36. retriever = vector_store.as_retriever(
  37. search_type="mmr",
  38. search_kwargs={
  39. "k": 5,
  40. "filter": {
  41. "must": [{"key": "category", "match": {"value": "科普"}}] # 可选:限定分类
  42. }
  43. }
  44. )
  45. # 5. 简单问答链
  46. from langchain_core.prompts import ChatPromptTemplate
  47. from langchain_openai import ChatOpenAI
  48. prompt = ChatPromptTemplate.from_messages([
  49. ("system", "你是菌群健康领域的专家助手。请根据以下资料回答用户问题,引用具体来源。"),
  50. ("human", "问题:{question}\n\n参考资料:{context}"),
  51. ])
  52. llm = ChatOpenAI(model="gpt-4o", temperature=0)
  53. rag_chain = prompt | llm
  54. # 使用
  55. result = rag_chain.invoke({
  56. "question": "肠道菌群和抑郁有什么关系?",
  57. "context": retriever.invoke("肠道菌群 抑郁")
  58. })
  59. print(result.content)
  60. # ============================================================
  61. # 方式二:LangGraph 多步 Agent(带分类路由 + 总结)
  62. # ============================================================
  63. from langgraph.graph import StateGraph, END
  64. from typing import TypedDict, Annotated
  65. from langgraph.checkpoint.memory import MemorySaver
  66. class AgentState(TypedDict):
  67. question: str
  68. category: str # 自动推断的分类
  69. retrieved_chunks: list # 检索到的 chunk
  70. summary: str
  71. final_answer: str
  72. # 节点 1:分类路由
  73. def classify_question(state: AgentState) -> AgentState:
  74. """根据用户问题自动推断应检索哪个分类"""
  75. q = state["question"]
  76. routing = {
  77. ("检测", "报告", "测序"): "技术服务",
  78. ("临床", "患者", "诊断"): "科普",
  79. ("技术", "方法", "测序"): "技术",
  80. ("公告", "新闻"): "公告",
  81. ("报告解读"): "报告解读",
  82. ("视频"): "视频",
  83. }
  84. for kws, cat in routing.items():
  85. if all(kw in q for kw in kws):
  86. return {"category": cat}
  87. return {"category": "科普"} # 默认科普
  88. # 节点 2:检索
  89. def retrieve(state: AgentState) -> AgentState:
  90. cat = state.get("category", "科普")
  91. chunks = retriever.invoke(state["question"])
  92. return {"retrieved_chunks": chunks}
  93. # 节点 3:总结
  94. def summarize(state: AgentState) -> AgentState:
  95. texts = "\n\n".join([c.page_content for c in state["retrieved_chunks"]])
  96. prompt_summary = ChatPromptTemplate.from_messages([
  97. ("system", "你是一个科学文章总结助手。请总结以下内容,突出关键发现和数据。"),
  98. ("human", "{texts}"),
  99. ])
  100. summary = (prompt_summary | ChatOpenAI(model="gpt-4o-mini", temperature=0)).invoke({"texts": texts})
  101. return {"summary": summary.content}
  102. # 节点 4:生成最终回答
  103. def answer(state: AgentState) -> AgentState:
  104. ctx = "\n\n---\n\n".join([c.page_content for c in state["retrieved_chunks"]])
  105. final = rag_chain.invoke({"question": state["question"], "context": ctx})
  106. return {"final_answer": final.content}
  107. # 构建图
  108. graph = StateGraph(AgentState)
  109. graph.add_node("classify", classify_question)
  110. graph.add_node("retrieve", retrieve)
  111. graph.add_node("summarize", summarize)
  112. graph.add_node("answer", answer)
  113. graph.add_edge("classify", "retrieve")
  114. graph.add_edge("retrieve", "summarize")
  115. graph.add_edge("summarize", "answer")
  116. graph.set_entry_point("classify")
  117. graph.set_finish_point("answer")
  118. memory = MemorySaver()
  119. app = graph.compile(checkpointer=memory)
  120. # 使用
  121. config = {"configurable": {"thread_id": "user-001"}}
  122. result = app.invoke(
  123. {"question": "肠道菌群和抑郁有什么关系?宏基因组检测能发现什么?"},
  124. config=config,
  125. )
  126. print(result["final_answer"])
  127. # ============================================================
  128. # 方式三:在 cfc-backend (Java) 中调用 LangGraph API
  129. # ============================================================
  130. """
  131. // 假设 LangGraph 服务运行在 localhost:8123
  132. POST /invoke
  133. {
  134. "question": "肠道菌群与糖尿病的关系",
  135. "category": "科普"
  136. }
  137. Response:
  138. {
  139. "final_answer": "...",
  140. "summary": "...",
  141. "sources": [
  142. {"postId": "1234", "title": "...", "category": "科普"},
  143. ...
  144. ]
  145. }
  146. """
  147. # ============================================================
  148. # 常用搜索示例
  149. # ============================================================
  150. # 按分类过滤检索
  151. retriever_science = vector_store.as_retriever(
  152. search_kwargs={"filter": {"must": [{"key": "category", "match": {"value": "科普"}}]}}
  153. )
  154. # 按主题过滤(topics metadata)
  155. retriever_depression = vector_store.as_retriever(
  156. search_kwargs={"filter": {"must": [{"key": "topics", "match": {"any": ["抑郁", "焦虑"]}}]}}
  157. )
  158. # 多路检索合并
  159. retrievers = RunnableParallel({
  160. "science": retriever_science,
  161. "clinical": vector_store.as_retriever(search_kwargs={"filter": {"must": [{"key": "topics", "match": {"any": ["临床", "诊断"]}}]}}),
  162. })
  163. merged = retrievers | (lambda x: sorted(set([c for chunk_list in x.values() for c in chunk_list]), key=lambda c: c.metadata["pub_date"] or ""))