1. LangChain 1.0到底是个什么东西
LangChain 1.0更接近于一个"数据流/工作流"
它解决的问题不是“怎么和模型聊天”,而是:一句用户输入,如何可靠地经过多个步骤,产出一个结果。
2. LangChain 1.0的四个核心概念
首先来快速对这四个概念进行简要介绍,后续通过实际例子来详细探讨这些概念的作用和实操方法。
2.1 Runnable
Runnable是LangChain 1.0中对“输入如何变成输出”的统一抽象,是一切可组合能力的最小单元。
2.2 管道符 |(组合的语法)
管道符|用来声明数据流向,把多个Runnable按输入输出关系串成一条可执行的数据流。
2.3 Prompt(不是字符串)
Prompt在1.0里是一个结构化模板:
from langchain_core.prompts import ChatPromptTemplateprompt = ChatPromptTemplate.from_template( "回答问题:{question}")
prompt不是字符串,其真实类型是:ChatPromptTemplate
它是一个Runnable,它接收一个输入dict,输出ChatMessage(结构化消息)。
2.4 Parser
把LLM输出变成“程序能用的东西”:
from langchain_core.output_parsers import StrOutputParserparser = StrOutputParser()
3. 第一个LangChain1.0程序
不涉及RAG、不涉及Agent,只是“干净的数据流”。
from langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParser# from langchain_community.llms import Ollama# 旧时代写法,已经废弃from langchain_ollama import OllamaLLMprompt = ChatPromptTemplate.from_template( "用一句话解释:{concept}")llm = OllamaLLM( model="qwen2.5-coder:7b", temperature=1)chain = prompt | llm | StrOutputParser()print(chain.invoke({"concept": "LangChain 1.0是什么"}))
输出:
LangChain 1.0 是一个用于构建语言模型应用的开源框架。
在执行chain.invoke({"concept": "LangChain 1.0是什么"}时,调用的是prompt = ChatPromptTemplate.from_template("用一句话解释:{concept}"),它会执行模板填充 ,于是用户的输入就变成了用一句话解释:LangChain 1.0是什么,并将其包装成聊天消息HumanMessage(content="用一句话解释:LangChain 1.0")。
接下来看看chain = prompt | llm | StrOutputParser()的数据流向。
首先,将prompt模板生成的HumanMessage输入llm = OllamaLLM(...)这个Runnable,经过llm这个Runnable,得到llm输出的AIMessage,比如AIMessage(content="LangChain 1.0 是一个用于构建 LLM 应用的数据流框架。"),最后将AIMessage通过StrOutputParser()这个Runnable,将llm输出的内容content提取出来。
4. 一个chain可以设计多条并行分支
from langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.runnables import RunnablePassthrough, RunnableParallelfrom langchain_ollama import OllamaLLMprompt = ChatPromptTemplate.from_template( "请用非常非常非常简短的一句话总结下面的内容:{text}。")llm = OllamaLLM( model="qwen2.5-coder:7b", temperature=0.0)chain = RunnableParallel( original=RunnablePassthrough(), summary=prompt | llm | StrOutputParser())input_text = "Python是一种高级编程语言,由Guido van Rossum于1991年首次发布。它是一种解释型语言,具有简单易学、代码可读性高、功能强大等特点。Python广泛应用于Web开发、数据分析、人工智能、科学计算等领域。"result = chain.invoke({"text": input_text})print(result)
借助RunnableParallel,对于同一个输入,可以同时执行多条输出路线,上述第一条输出路线是RunnablePassthrough(),这意味着直接把输入进行输出,不做任任何处理,而第二条输出路线则执行ChatPromptTemplate定义的操作。
输出结果是一个字典:
{'original': {'text': 'Python是一种高级编程语言,由Guido van Rossum于1991年首次发布。它是一种解释型语言,具有简单易学、代码可读性高、功能强大等特点。Python广泛应用于Web开发、数据分析、人工智能、科学计算等领域。'}, 'summary': 'Python是一种高级、解释型编程语言,由Guido van Rossum在1991年创建,以其简洁语法和广泛的应用领域而闻名。'}
二、LangChain 1.0里的RAG
1. 搭建普通RAG
在LangChain 1.0里,RAG =一个 Retriever Runnable + 一个 Prompt + 一个 LLM,没错,Retriever也是一个Runnable。
没有Agent,没有循环,没有工具。
现在来基于FAISS搭建一个retriever:
from langchain_community.document_loaders import TextLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_community.vectorstores import FAISSfrom langchain_ollama import OllamaEmbeddings# 加载文档loader = TextLoader(r"D:\MyFile\github\AgenticRag\all2markdown\不存在的知识.md", encoding="utf-8")docs = loader.load()# 切分splitter = RecursiveCharacterTextSplitter( chunk_size=200, chunk_overlap=50)splits = splitter.split_documents(docs)# 向量化embeddings = OllamaEmbeddings(model="nomic-embed-text")vectorstore = FAISS.from_documents(splits, embeddings)# 关键:Retriever 是 Runnableretriever = vectorstore.as_retriever(search_kwargs={"k": 2})# topk=2
到这里为止,还没LangChain的“链”,只是得到了一个:
retriever.invoke("什么是 Runnable?")# → [Document, Document]
把两个Document打印出来:
[Document(id='086d3d39-dc15-4f12-b881-a1a3ba4f0c82', metadata={'source': '不存在的知识.md'}, page_content='逆向熵稳定区是一个假想的系统状态区间,最早出现在 **“非对称热动力学草 案”** 中。\n\n在该区间内,系统表现出以下反常性质:\n\n1. 局部结构复杂度随时间上升 \n2. 外部观测到的整体熵值保 持不变 \n3. 微观扰动反而提升宏观稳定性 \n\nIESZ 被认为只会在以下条件同时满足时短暂出现:'), Document(id='710437ea-86e3-43ec-a458-0c2af164992f', metadata={'source': '不存在的知识.md'}, page_content='拟因果残影是指在系统中,**因果关系被移除后仍持续存在的解释结构**。\n\n这一概念最早出现于虚构论文《因果消退后的结构记忆》中。\n\n其主要表现包括:\n\n- 系统仍能生成“看似合理”的因果叙述 \n- 不同输入 会被映射到相同的解释路径 \n- 解释结果对真实因果变化不再敏感 \n\n拟因果残影被认为是解释系统中最危险的状态之一 ,因为它会:')]
现在来把Retriever真正接进Runnable数据流:
from langchain_core.prompts import ChatPromptTemplateprompt = ChatPromptTemplate.from_template( """你是一个严谨的技术助理,只能基于给定资料回答。 资料: {context} 问题: {q} """)from langchain_core.runnables import RunnablePassthroughfrom langchain_core.output_parsers import StrOutputParserfrom langchain_ollama import OllamaLLMllm = OllamaLLM( model="qwen2.5-coder:7b", temperature=0.0)rag_chain = ( { "context": retriever, # question → context "q": RunnablePassthrough() # question → question } | prompt | llm | StrOutputParser())result=rag_chain.invoke("什么是逆向熵稳定区?")print(result)
这是一次真正的RAG操作,输出:
逆向熵稳定区是一个假想的系统状态区间,最早出现在“非对称热动力学草案”中。在该区间内,系统表现出以下反常性质:1. 局部结构复杂度随时间上升2. 外部观测到的整体熵值保持不变3. 微观扰动反而提升宏观稳定性
调用rag_chain.invoke("什么是逆向熵稳定区?")时,LangChain内部做的是:
输入: "什么是逆向熵稳定区?"┌───────────────┐│ context │ ← retriever("什么是逆向熵稳定区?")│ question │ ← 原样传递└───────────────┘↓ 组装 prompt↓ LLM↓ 字符串输出
为什么只是写了"context": retriever,就“自动完成了查询”?
因为retriever本身就是一个Runnable,它接收一个字符串输入str,直接开始查询,然后输出对应的查询结果。
查询结果作为"context"的value,RunnablePassthrough()直接把输入的str原样返回,作为"q"的value,然后进入到chain的下一环节:prompt。
上述操作没有黑箱,没有agent,没有循环。
2. RAG的回答靠不靠谱?加一个检验的逻辑
不引入Agent,只在上述普通RAG基础上,加一条分支,让RAG在回答的同时,对“自己靠不靠谱”给出一个判断。
注意三点:不让它解释,不让它发挥,输出空间被硬限制,构建可控系统。
confidence_prompt = ChatPromptTemplate.from_template( """ 你是一个严格的检索结果评估器。 问题: {question} 检索到的内容: {context} 请判断:这些内容是否足以回答该问题? 只输出以下三个标签之一: - HIGH - LOW - NONE """)from langchain_core.runnables import RunnableParallelrag_with_confidence = RunnableParallel( context=retriever, question=RunnablePassthrough(), confidence=( { "context": retriever, "question": RunnablePassthrough(), } | confidence_prompt | llm | StrOutputParser() ))result = rag_with_confidence.invoke("什么是逆向熵稳定区?")for k, v in result.items(): print(f"{k}: {v}")
现在这个rag_with_confidence,在一次.invoke(question)时,同时并行计算三条语义不同的路径。
- context分支:输入是问题字符串"什么是逆向熵稳定区?",执行RAG检索后输出一个“检索结果”字符串。
- question分支:输入是问题字符串"什么是逆向熵稳定区?",原样输出。
- confidence分支:又嵌套了一层,又检索了一次(因为retriever是“纯函数节点”,重用是合法的,语义是清晰的。),基于confidence_prompt调用llm作答。
由于整体上是3条不同的执行路线,因此对应3个输出,如下:
context: [ Document(id='e4c09030-6f07-47d8-a62f-80cb8e7834de', metadata={'source': '不存在的知识.md'}, page_content='逆向熵稳定区是一个假想的系统状态区间,最早出现在 **“非对称热动力学草案”** 中。\n\n在该区间内,系统表现出以下反常性质:\n\n1. 局部结构复杂度随时间上升 \n2. 外部观测到的整 体熵值保持不变 \n3. 微观扰动反而提升宏观稳定性 \n\nIESZ 被认为只会在以下条件同时满足时短暂出现:'), Document(id='5102440c-84e1-430e-97b8-58ca10ca144d', metadata={'source': '不存在的知识.md'}, page_content='拟因果残影是指在系统中,**因果关系被移除后仍持续存在的解释结构**。\n\n这一概念最早出现于虚构论文《因果消退后的结构记忆》中。\n\n其主要表现包括:\n\n- 系统仍能生成“看似合理”的因果叙述 \n- 不同输入会被映射到相同的解释路径 \n- 解释结果对真实因果变化不再敏感 \n\n拟因果残影被认为是解释系统中最危险的 状态之一,因为它会:'), ...]question: 什么是逆向熵稳定区?confidence: LOW
3. 根据检验结果,决定是否基于知识库进行回答
有些问题检索不到答案,或者答案与问题本身相关性不大,此时RAG要学会拒绝回答或者提示风险。
基于上一节的confidence输出分支,设计如下规则:
HIGH → 允许回答LOW → 谨慎回答 / 提示不确定NONE → 直接拒答
为了实现这一目的,需要引入RunnableBranch:
from langchain_core.runnables import RunnableBranch
它的作用是根据输入内容,选择一条Runnable执行,这个不是prompt判断,是代码级if/else。
完整代码:
from langchain_core.runnables import RunnableBranchfrom langchain_core.runnables import RunnableLambdaconfidence_chain = ( { "context": retriever, "question": RunnablePassthrough(), } | confidence_prompt | llm | StrOutputParser())answer_chain = ( prompt# 之前定义好的prompt,直接拿来用 | llm | StrOutputParser())refuse_chain = RunnableLambda( lambda _: "检索结果不足,无法可靠回答该问题。")controlled_rag = ( { "context": retriever, "q": RunnablePassthrough(), "confidence": confidence_chain } | RunnableBranch( ( lambda x: x["confidence"] == "HIGH", answer_chain ), refuse_chain ))result = controlled_rag.invoke("什么是逆向熵稳定区?")print(result)
输出的confidence是HIGH,因此选择answe_chain,执行,得到如下回答:
逆向熵稳定区(Inverse Entropy Stability Zone, IESZ)是一个假想的系统状态区间,最早出现在 **“非对称热动力学草案”** 中。在该区间内,系统表现出以下反常性质:1. 局部结构复杂度随时间上升2. 外部观测到的整体熵值保持不变3. 微观扰动反而提升宏观稳定性IESZ被认为只会在以下条件同时满足时短暂出现:
如果把问题改成知识库里面不存在的问题,那么confidence是NONE,模型拒绝回答:
result = controlled_rag.invoke("什么是PYT?")
输出:
检索结果不足,无法可靠回答该问题。
4. RAG一定回答,但是按照风险等级confidence走不同的prompt“回答链”
不是“同一个LLM换语气”,而是“不同Runnable被执行”。
| confidence | 系统行为 |
|---|---|
| HIGH | 正常、完整回答 |
| LOW | 回答,但显式声明不确定、引用原文 |
| NONE | 直接拒答(不调用 LLM 或最小调用) |
本质上在模拟IF/ELSE:
┌─ HIGH ─→ high_answer_chainquestion → 判断 ├─ LOW ─→ low_answer_chain └─ NONE ─→ none_answer_chain
代码如下:
# 资料充分 → 正常发挥high_answer_chain = ( ChatPromptTemplate.from_template( """你是一个严谨的技术助理,请基于资料回答问题。 资料: {context} 问题: {question} """ ) | llm | StrOutputParser())# 回答 + 明确风险边界low_answer_chain = ( ChatPromptTemplate.from_template( """你是一个谨慎的技术助理。 以下资料可能不足以完整回答问题,请: - 明确指出不确定性 - 尽量引用资料原文 - 不要做超出资料的推断 资料: {context} 问题: {question} """ ) | llm | StrOutputParser())from langchain_core.runnables import RunnableLambda# confidence=NONE,拒绝回答none_answer_chain = RunnableLambda( lambda _: "检索资料不足,无法对该问题给出可靠回答。")# 根据confidence选择不同的回答链from langchain_core.runnables import RunnableBranchmulti_strategy_rag = ( { "context": retriever, "question": RunnablePassthrough(), "confidence": confidence_chain, } | RunnableBranch( ( lambda x: x["confidence"] == "HIGH", high_answer_chain, ), ( lambda x: x["confidence"] == "LOW", low_answer_chain, ), none_answer_chain, # 默认分支(NONE) ))result = controlled_rag.invoke("什么是阿尔法层级漂移定律?")# confidence是HIGHprint(result)# 阿尔法层级漂移定律(Alpha-Stratum Drift Law)最早由虚构学者 L. K. Ardent...
5. 多路查询
用同一个store构造两个retriever,一个精准率高(k小),一个召回率高(k大)。
一次embedding,多次查询,是比“多次embedding”更合理的默认策略。
因此,输入一个问题,embedding成一个向量,然后执行两次查询:
# 精准检索:更严格,k 小retriever_precise = vectorstore.as_retriever( search_kwargs={"k": 2})# 广泛检索:更宽松,k 大retriever_broad = vectorstore.as_retriever( search_kwargs={"k": 5})from langchain_core.runnables import RunnableParallelmulti_retriever = RunnableParallel( precise=retriever_precise,# 精准检索 broad=retriever_broad,# 广泛检索)result = multi_retriever.invoke("什么是逆向熵稳定区?")print("=== precise ===")for d in result["precise"]: print(d.page_content)# 把文字取出来print("\n=== broad ===")for d in result["broad"]: print(d.page_content)
两次查询各自的输出:
=== precise ===逆向熵稳定区是一个假想的系统状态区间,最早出现在 **“非对称热动力学草案”** 中。在该区间内,系统表现出以下反常性质:1. 局部结构复杂度随时间上升2. 外部观测到的整体熵值保持不变3. 微观扰动反而提升宏观稳定性IESZ 被认为只会在以下条件同时满足时短暂出现:拟因果残影是指在系统中,**因果关系被移除后仍持续存在的解释结构**。这一概念最早出现于虚构论文《因果消退后的结构记忆》中。其主要表现包括:- 系统仍能生成“看似合理”的因果叙述- 不同输入会被映射到相同的解释路径- 解释结果对真实因果变化不再敏感拟因果残影被认为是解释系统中最危险的状态之一,因为它会:=== broad ===逆向熵稳定区是一个假想的系统状态区间,最早出现在 **“非对称热动力学草案”** 中。在该区间内,系统表现出以下反常性质:1. 局部结构复杂度随时间上升2. 外部观测到的整体熵值保持不变3. 微观扰动反而提升宏观稳定性IESZ 被认为只会在以下条件同时满足时短暂出现:拟因果残影是指在系统中,**因果关系被移除后仍持续存在的解释结构**。这一概念最早出现于虚构论文《因果消退后的结构记忆》中。其主要表现包括:- 系统仍能生成“看似合理”的因果叙述- 不同输入会被映射到相同的解释路径- 解释结果对真实因果变化不再敏感拟因果残影被认为是解释系统中最危险的状态之一,因为它会:这一现象常被误认为是系统噪声或随机退化,但根据阿尔法层级漂移定律,其本质是一种 **结构性重心转移**。阿尔法层级漂移被认为具有以下特征:- 漂移方向不可预测,但一旦形成即难以回退- 在早期阶段几乎无法通过表层指标检测- 与系统规模无直接线性关系---## 2. 逆向熵稳定区(Inverse Entropy Stability Zone, IESZ)拟因果残影被认为是解释系统中最危险的状态之一,因为它会:> 在完全失去真实因果基础的情况下,维持高度一致的叙事输出。## 1. 阿尔法层级漂移定律(Alpha-Stratum Drift Law)阿尔法层级漂移定律最早由虚构学者 **L. K. Ardent** 于 1974 年提出,用于描述复杂系统中“隐含决策层级”随时间发生的 不可逆偏移现象。该定律指出:> 当一个系统的表层行为被持续优化时,其未显式建模的中间决策层会逐步偏离原始设计目标,形成“层级漂移”。
上述输出可以抽象为:
{ "precise": [Document, Document], "broad": [Document, Document, Document, ...]}
得到上述多路输出结果后,因为precise和broad里可能有重复证据,也可能有只在broad里出现的内容,因此需要进行处理,这里做三件事:
- 去重(同一段内容只算一次)
- 标注来源(来自precise / broad / both)
- 分组(共识证据 vs 单路证据)
使用如下函数进行处理:
from collections import defaultdictdef align_evidence(results: dict): """ results = { "precise": [Document...], "broad": [Document...] } """ bucket = defaultdict(lambda: {"sources": set(), "content": None}) for source, docs in results.items(): for doc in docs: key = doc.page_content.strip() bucket[key]["content"] = key bucket[key]["sources"].add(source) aligned = [] for v in bucket.values(): aligned.append({ "content": v["content"], "sources": sorted(v["sources"]) }) return aligned
然后把这个函数变成Runnable:
from langchain_core.runnables import RunnableLambdaalign_chain = RunnableLambda(align_evidence)
接着定义chain:
evidence_chain = ( multi_retriever | align_chain)
这一步是把多路查询结果进行对齐。
现在开始执行chain:
evidence = evidence_chain.invoke("什么是逆向熵稳定区?")for e in evidence: print('item:',e)
输出:
item: {'content': '逆向熵稳定区是一个假想的系统状态区间,最早出现在 **“非对称热动力学草案”** 中。\n\n在该区间内,系统表现出以下反常性质:\n\n1. 局部结构复杂度随时间上升 \n2. 外部观测到的整体熵值保持不变 \n3. 微观扰动反而提升宏观稳定性 \n\nIESZ 被认为只会在以下条件同时满足时短暂出现:', 'sources': ['broad', 'precise']}item: {'content': '拟因果残影是指在系统中,**因果关系被移除后仍持续存在的解释结构**。\n\n这一概念最早出现于虚构论文《因果消退后的结构记忆》中。\n\n其主要表现包括:\n\n- 系统仍能生成“看似合理”的因果叙述 \n- 不同输入会被映 射到相同的解释路径 \n- 解释结果对真实因果变化不再敏感 \n\n拟因果残影被认为是解释系统中最危险的状态之一,因为 它会:', 'sources': ['broad', 'precise']}item: {'content': '这一现象常被误认为是系统噪声或随机退化,但根据阿尔法层级漂移定律,其本质是一种 **结构性重心 转移**。\n\n阿尔法层级漂移被认为具有以下特征:\n\n- 漂移方向不可预测,但一旦形成即难以回退 \n- 在早期阶段几乎 无法通过表层指标检测 \n- 与系统规模无直接线性关系\n\n---\n\n## 2. 逆向熵稳定区(Inverse Entropy Stability Zone, IESZ)', 'sources': ['broad']}item: {'content': '拟因果残影被认为是解释系统中最危险的状态之一,因为它会:\n\n> 在完全失去真实因果基础的情况下,维持高度一致的叙事输出。', 'sources': ['broad']}item: {'content': '## 1. 阿尔法层级漂移定律(Alpha-Stratum Drift Law)\n\n阿尔法层级漂移定律最早由虚构学者 **L. K. Ardent** 于 1974 年提出,用于描述复杂系统中“隐含决策层级”随时间发生的不可逆偏移现象。\n\n该定律指出:\n\n> 当一个系统的表层行为被持续优化时,其未显式建模的中间决策层会逐步偏离原始设计目标,形成“层级漂移”。', 'sources': ['broad']}
多路查询结果对齐完成后,开启最终的回答:
aligned_prompt = ChatPromptTemplate.from_template( """你是一个严谨的技术助理,只能基于下列证据回答。 证据列表(已对齐,标注来源): {evidence} 请遵守: - 优先使用被多个来源支持的内容 - 如果某结论只有单一来源,请明确说明不确定性 问题: {question} """)final_chain = ( { "evidence": evidence_chain, "question": RunnablePassthrough() } | aligned_prompt | llm | StrOutputParser())result = final_chain.invoke("什么是逆向熵稳定区?")print(result)
最终的输出如下:
逆向熵稳定区(Inverse Entropy Stability Zone, IESZ)是一个假想的系统状态区间,最早出现在 **“非对称热动力学草案”** 中。在该区间内,系统表现出以下反常性质:1. 局部结构复杂度随时间上升2. 外部观测到的整体熵值保持不变3. 微观扰动反而提升宏观稳定性IESZ 被认为只会在以下条件同时满足时短暂出现:
以上只是举了一个简单的案例,来演示如何使用LangChain通过一次embedding后使用多路查询并对齐,再执行最终查询的过程。
6. 接入LightRAG
LightRAG是GraphRAG的一个轻量级实现,它不是Langchain 1.0的一部分,但是现在我们使用Langchain 1.0调用LightRAG实现RAG查询。
首先把LightRAG包装成LangChain的Retriever接口。
原本的LightRAG调用接口为:
from typing import Optional, List, Dict, Anyimport requestsimport uuidimport timeimport osdef query_lightrag( question: str, conversation_history: Optional[List] = None, mode: str = "mix", user_prompt: Optional[str] = None, disable_cache: bool = True) -> Dict[str, Any]: """ 调用 LightRAG Web API(独立函数版,不依赖 config) """ # ===== 基础配置(函数内自洽)===== LIGHTRAG_URL = os.getenv("LIGHTRAG_URL", "http://127.0.0.1:9621/query") TIMEOUT = int(os.getenv("TIMEOUT", "300")) TOP_K = 40 CHUNK_TOP_K = 20 MAX_ENTITY_TOKENS = 2048 MAX_RELATION_TOKENS = 2048 MAX_TOTAL_TOKENS = 12000 DEFAULT_USER_PROMPT = ( "一定要遵循知识库中的内容,知识库中没有就说不知道。" "请结合上下文连续回答。" "如果是基于前文的问题,请直接承接,不要重复定义。" ) # ===== 参数整理 ===== if conversation_history isNone: conversation_history = [] base_prompt = user_prompt or DEFAULT_USER_PROMPT if disable_cache: final_user_prompt = ( f"{base_prompt}\n" f"[请求ID: {uuid.uuid4().hex[:16]}, 时间: {time.time()}]" ) else: final_user_prompt = base_prompt payload = { "query": question, "mode": mode, # naive / local / global / hybrid / mix "only_need_context": False, "only_need_prompt": False, "response_type": "string", "top_k": TOP_K, "chunk_top_k": CHUNK_TOP_K, "max_entity_tokens": MAX_ENTITY_TOKENS, "max_relation_tokens": MAX_RELATION_TOKENS, "max_total_tokens": MAX_TOTAL_TOKENS, "conversation_history": conversation_history, "user_prompt": final_user_prompt, "enable_rerank": True, "include_references": True, "include_chunk_content": False, "stream": False } # ===== HTTP 调用 ===== try: response = requests.post( LIGHTRAG_URL, json=payload, timeout=TIMEOUT ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: raise RuntimeError(f"LightRAG API 调用失败: {e}")
对其进行封装,得到Runnable版本的LightRAG Retriever:
from langchain_core.runnables import Runnableclass LightRAGRetriever(Runnable): def __init__( self, mode: str = "hybrid", user_prompt: Optional[str] = None, ): self.mode = mode self.user_prompt = user_prompt def invoke(self, query: str, config=None) -> str: result = query_lightrag( question=query, mode=self.mode, user_prompt=self.user_prompt, ) return result.get("response", "")
简单测试:
lightrag_retriever = LightRAGRetriever( mode="hybrid", user_prompt="你是一个严谨的技术助理,只能基于给定资料回答。",)result = lightrag_retriever.invoke("什么是逆向熵稳定区?")print('lightrag_retriever result:',result)
输出:
lightrag_retriever result: 逆向熵稳定区(Inverse Entropy Stability Zone, IESZ)是一个假想的系统状态区间,最早出现在“非对称热动力学草案”中。在该区间内,系统表现出以下反常性质:1. 局部结构复杂度随时间上升;2. 外部观测到的整体熵值保持不变;3. 微观扰动反而提升宏观稳定性。IESZ被认为只会在以下条件同时满足时短暂出现:- 系统包含至少三个相互竞争的反馈回路;- 任意两个回路的响应时间尺度不相等;- 系统观察者无法完全访问内部状态。由于该区域无法被稳定复现,它通常仅通过事后推断被“识别”。
说个题外话,由于使用了知识图谱,LightRAG的效果比之前的普通RAG好得多。
三、LangChain 1.0+本地Ollma做多轮对话chatbot
1. 简单记忆版
同样基于LangChain的“链”,搭建“对话chain”。为了能够在多轮对话中保持上下文的记忆,引入一个history列表来维护记忆。
from langchain_core.runnables import RunnableLambdafrom langchain_core.output_parsers import StrOutputParserfrom langchain_ollama import OllamaLLMfrom langchain_core.messages import HumanMessage, AIMessage# -------------------------------# 初始化 Ollama 本地模型# -------------------------------llm = OllamaLLM( model="qwen2.5-coder:7b", temperature=0.0)# -------------------------------# 定义聊天历史(BaseMessage列表)# -------------------------------history = []# -------------------------------# 定义 Runnable,把用户输入包装成 HumanMessage 并加入历史# -------------------------------wrap_input = RunnableLambda( lambda x: history.append(HumanMessage(content=x)) or history)# -------------------------------# 定义管道# wrap_input -> llm -> StrOutputParser# -------------------------------def ai_append_str_output(messages): """调用 llm 返回 AIMessage 并加入历史""" resp = llm.invoke(messages) ai_msg = AIMessage(content=resp) # 手动包装成 AIMessage history.append(ai_msg) return ai_msg.contentchat_chain = wrap_input | RunnableLambda(ai_append_str_output) | StrOutputParser()print("开始聊天(输入 'exit' 退出):")whileTrue: user_input = input("你:") if user_input.strip().lower() == "exit": break # 调用管道 reply = chat_chain.invoke(user_input) print("助手:", reply)
来看下这条链路:chat_chain = wrap_input | RunnableLambda(ai_append_str_output) | StrOutputParser()
在执行链路(chat_chain.invoke(user_input))的过程中,做了下面的事情:
首先,wrap_input把用户输入包装成HumanMessage并加入历史list,传入下一环节。
其次,RunnableLambda是LangChain 1.0里最轻量的Runnable,可以把任意Python函数包装成管道的一环,它接收传入的消息列表messages ,然后调用resp = llm.invoke(messages)得到模型预测的对话输出,并将其添加到历史对话list里。
最后,StrOutputParser()将ai_msg.content,也就是当前轮次的模型预测对话进行输出。
2. 混合策略版:历史自动summary+滑动窗口
在上面的例子中,当对话轮数过多时,历史信息过长可能会超出LLM的最大上下文长度,并且重点信息可能被淹没,推理速度变慢,因此可以改进为历史自动summary+滑动窗口版本,这个策略只保留最近的几条历史信息,更久远的历史信息直接summary成一条总结信息。
from langchain_core.runnables import RunnableLambdafrom langchain_core.output_parsers import StrOutputParserfrom langchain_ollama import OllamaLLMfrom langchain_core.messages import HumanMessage, AIMessage# -------------------------------# 初始化 Ollama 本地模型# -------------------------------llm = OllamaLLM( model="qwen2.5-coder:7b", temperature=0.0)# -------------------------------# 聊天历史# -------------------------------history = []# 最大保留最近几条消息MAX_RECENT_HISTORY = 10# 超过多少条触发总结SUMMARY_THRESHOLD = 40# -------------------------------# 自动生成历史总结函数# -------------------------------def summarize_old_messages(old_messages): """用 llm 自动生成老历史摘要""" # 拼接内容 old_text = "\n".join([f"{msg.type}: {msg.content}"for msg in old_messages]) prompt = f"请将以下聊天历史总结成一段简短总结,保留重要信息,少于100字:\n{old_text}" summary = llm.invoke([HumanMessage(content=prompt)]) # 返回 str return AIMessage(content=f"[历史总结]: {summary}") # 包装成 AIMessage# -------------------------------# 定义 RunnableLambda,把用户输入加入历史 + 优化记忆# -------------------------------def wrap_and_optimize(user_input): # 添加用户消息 history.append(HumanMessage(content=user_input)) # 如果历史太长,生成总结替换最早部分 if len(history) > SUMMARY_THRESHOLD: old_msgs = history[:len(history)-MAX_RECENT_HISTORY] summary_msg = summarize_old_messages(old_msgs) history[:] = [summary_msg] + history[-MAX_RECENT_HISTORY:] # 返回当前历史列表给 llm return historywrap_input = RunnableLambda(wrap_and_optimize)# -------------------------------# 定义管道# wrap_input -> llm -> StrOutputParser# -------------------------------def ai_append_output(messages): print('history:',history) resp = llm.invoke(messages) # 返回 str ai_msg = AIMessage(content=resp) history.append(ai_msg) return ai_msg.contentchat_chain = wrap_input | RunnableLambda(ai_append_output) | StrOutputParser()# -------------------------------# 聊天循环# -------------------------------print("开始聊天(输入 'exit' 退出):")whileTrue: user_input = input("你:") if user_input.strip().lower() == "exit": break reply = chat_chain.invoke(user_input) print("助手:", reply)
此外,如果需要持久化记忆,可以将这些历史消息写入到本地文件。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

1570



被折叠的 条评论
为什么被折叠?



