🟡 Langchian生态全景
🔘各版块生态位
LangChain是构建基于大型语言模型应用的框架,它提供了工具、组件和库,支持从chain、到agent、引入RAG。具体到AI程序构建的的两种模式:chain 和 Agent ,差异在于 LLM 的参与度 。如果没有Agent,那么React的过程其实是人是做的,LLM只是用于简化开发工作,比如理解自然语言提问从里面提取出参数。
langchain-community
通过提供丰富的第三方工具和数据处理能力。一个原本只能进行简单数学计算的 Agent 可以利用社区提供的 WolframAlpha
工具解决高等数学,利用toolkits
调用天气查询工具OpenWeatherMap
,或者让 Retrieval
支持从 Markdown 文件加载文档,再比如自带的RAG链 RetrievalQA
可以通过社区集成的 Pinecone
进行更好的向量存储等等... ...所以社区最大意义在于为整个Langchian生态提供现成的、标准化的组件,直接融入现有的AI应用,就是拿来就用。
langsmith
可以追踪和分析整个模型的运行数据,比如用它记录 RetrievalQA
链的每一步,检查检索结果是否准确,召回率如何,分析 Agent
的工具调用频率,调整提示模板以减少低效操作。还能验证 Memory
是否正确保存上下文,并通过测试数据集改进 Retrieval
的嵌入模型和分块策略。
python
import os
from langchain.chains import RetrievalQA
os.environ["LANGSMITH_API_KEY"] = "your-api-key"
os.environ["LANGSMITH_PROJECT"] = "my-AI-project"
Xmodel = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever())
result = Xmodel.run("What is in the document?")
# 在 LangSmith UI 查看 (https://smith.langchain.com/onboarding)
langchain-server
将制作好的Chains
和 Agents
转化为 REST API,让它们从本地开发环境走向外部系统。比如一个带有 Memory
的 ConversationChain
可以维持多轮对话,所以把它发布为一个聊天机器人,通过 /chat
端点实时响应前端请求;一个使用 Tools
的 Agent
可以调动某个工具对吧,所以可以命名一个 /execute
发布,处理咱具体的某个任务。当然这些路径前缀随意取,LangServe 中通过 add_routes 的 path 参数为部署的 Chains 或 Agents 指定任意前缀,比如 /JJG_chat、/HKY_chat、/my-api...,前缀只是为了语义上可以反映功能,但是端点固定 ,即无论前缀是什么,最终的完整路径必须以 LangServe 的内置端点结尾,因为这些端点直接对应 Runnable 的方法,包括:/invoke:单次执行、/stream:流式输出、/batch:批量处理、/playground:交互界面(用于调试)。所以请求的完整路径是 前缀 + 内置端点,例如 /jjgchat/invoke、/moonlesschat/stream,最后的端点一定是 Runnable 支持的操作。
🔘问答系统示例
假设我们要构建一个文档问答系统:langchain
提供基础的 RetrievalQA
链,结合 Retrieval
加载 PDF 和 Memory
保存对话历史;langchain-community
引入 Pinecone
替换默认向量存储,并添加 Google Search
工具补充外部信息;langsmith
跟踪运行数据,发现检索准确性不足,调整分块策略;langgraph
将问答逻辑扩展为多步骤流程,先检索文档、再搜索外部数据、最后生成回答;langchain-server
则将这个系统部署为 /qa
API,供前端实时调用。
-
基础开发(langchain) :创建
RetrievalQA
链,使用Retrieval
(加载 PDF)和Memory
(保存对话)。pythonfrom langchain.chains import RetrievalQA from langchain.memory import ConversationBufferMemory loader = PyPDFLoader("doc.pdf") docs = loader.load_and_split() vectorstore = FAISS.from_documents(docs, embeddings) qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever(), memory=ConversationBufferMemory())
-
插件扩展(langchain-community) :替换为
Pinecone
向量存储,添加Google Search
工具。pythonfrom langchain_community.vectorstores import Pinecone from langchain_community.tools import GoogleSerpAPIWrapper vectorstore = Pinecone.from_documents(docs, embeddings, index_name="my-index") search_tool = GoogleSerpAPIWrapper() qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever(), tools=[search_tool])
-
模型优化(langsmith) :记录运行日志,在
langsmith
UI 中查看召回率,修改splitter
参数。 -
复杂化(langgraph) :将问答升级为多步骤:检索文档 -> 搜索补充 -> 生成回答。
pythonfrom langgraph.graph import StateGraph def retrieve(state): return {"docs": vectorstore.as_retriever().get_relevant_documents(state["input"])} def search(state): return {"supplement": search_tool.run(state["input"])} def generate(state): return {"answer": llm.invoke(state["docs"] + state["supplement"])} graph = StateGraph() graph.add_node("retrieve", retrieve) graph.add_node("search", search) graph.add_node("generate", generate) graph.add_edge("retrieve", "search") graph.add_edge("search", "generate") app = graph.compile()
-
部署(langchain-server) :将最终的智能体
graph
部署为 API。pythonfrom langserve import add_routes add_routes(app, app, path="/qa")
这个检索增强的问答系统:涉及到的组件如下
- 基础组件层:
LLM
(例如ChatOpenAI
): 用于生成答案。PromptTemplate
: 用于构建提示。Embeddings
(例如OpenAIEmbeddings
): 用于生成文档和查询的嵌入向量。TextSplitter
(例如RecursiveCharacterTextSplitter
): 用于将文档分割成更小的块。- 功能模块层:
Vectorstore
(例如Chroma
): 用于存储文档嵌入并执行相似性搜索。Retriever
: 基于Vectorstore
,用于从向量数据库中检索相关文档。DocumentLoaders
: 各种加载器, 用于读取不同类型的文件.- 应用封装层:
- 自定义
Runnable
链 (使用RunnableParallel
和管道符): 将检索和生成答案的步骤组合在一起。
🔘分层设计思想
对于初学者,很多概念是模糊的,我当时一直把ConversationBufferMemory
与RunnableWithMessageHistory
这俩搞不清楚,我觉得都是实现记忆的,这俩之间到底是啥关系?C
属于功能模块层 。 它的作用是存储对话历史,而R
属于应用封装层 , 作用是将 Memory
组件 (例如 C
) 集成到 Runnable
链中。前者负责存储,后者负责使用存储,因为层级关系,功能层的 C
不依赖于 R
,但 R
依赖于 C
(或其他 Memory
组件)。所以这就必须引出分层,分层思想是整个计算机体系都通用的。
- 解耦 :将存储 (Memory) 和使用存储 (RunnableWithMessageHistory) 分离开来,可以提高代码的灵活性和可维护性。你可以更换不同的
Memory
实现 (例如,从内存存储切换到数据库存储),而无需修改RunnableWithMessageHistory
的代码。 - 重用 :
ConversationBufferMemory
可以被其他组件使用,不仅仅是RunnableWithMessageHistory
。 - 扩展 你可以创建自定义的
Memory
组件,然后使用RunnableWithMessageHistory
将其集成到你的链中。
如果更倾向于完全掌控历史管理逻辑,可以自定义一个Tool,通过数据库查询获取历史记录,Langchian提供的RunnableWithMessageHistory
是一个"开箱即用"的解决方案,直接将Runnable流程(比如语言模型的调用链)和消息历史的管理的结合起来,可快速实现带有历史上下文的对话功能。ConversationBufferMemory
是内存存储的一种实现,它是一个简单的内存缓冲区,用于在会话期间临时存储消息历史,很显然,它并不是唯一的选择,若从数据库中获取历史记录,可以使用诸如Postgres/Redis ChatMessageHistory
。但无论是哪种,都是实现了BaseChatMessageHistory
这个接口。历史存储的具体实现可以根据需求替换,而上层逻辑(比如RunnableWithMessageHistory
)无需改动。
LangChain 的架构可以这样理解:
- 基础组件层: 最小的、可重用的构建块 (
Runnable
及其实现)。 - 功能模块层: 由基础组件拼接而成的,实现特定小功能的 "大
Runnable
"。 - 应用封装层: 由多个功能模块(以及可能的基础组件)组合而成的,实现完整任务流程的 "更大
Runnable
"。
1、应用封装层:多个功能模块组合成的完整任务流程
应用封装层(例如 Chains
、Agents
、RunnableWithMessageHistory
),是将多个功能模块(以及可能的其他基础组件)组合在一起,形成一个完整的、可以执行某个特定任务的流程。本质是更高级复杂的 Runnable
,但它们通常代表了一个更复杂的、更完整的业务逻辑。例如,一个 RetrievalQA
检索链将功能组件 Retriever
、与基础组件PromptTemplate
和 LLM
组合在一起,便实现了检索增强的问答功能。
Chains
:LLMChain
:基础链。(早期链类)ConversationChain
:对话链。(早期链类)RetrievalQA
:检索问答。(早期链类)SequentialChain
:顺序链。(早期链类)MapReduceChain
:映射归约链。- LCEL 链 :通过
|
自定义拼装的链(如prompt | llm
)。
Agents
:ZeroShotAgent
:零样本代理。ReActAgent
:推理行动代理。StructuredChatAgent
:结构化对话代理。OpenAIFunctionsAgent
:OpenAI 函数调用代理。ToolCallingAgent
:工具调用代理(现代)。
Graph-based Constructs
(langgraph
):StateGraph
:状态图,用于复杂工作流。Pregel
:图计算框架。
RunnableWithMessageHistory
:将历史集成到Runnable
。
高层封装,整合下层模块,面向具体应用。还有一些专用链,如 SQLChain
(数据库查询)、GraphQAChain
(图数据库问答)。 langgraph
的引入扩展了应用层,支持状态管理和循环逻辑。 LCEL 的灵活性让自定义链成为主流,早期链类逐渐减少使用。
2、功能模块层:由基础组件层拼接而成的大"Runnable"
功能模块层(例如 Retriever
、Tools
)本质上是由基础组件层(Runnable
及其各种实现、Embeddings
、Vectorstore
等)拼接而成的。功能模块通常更具体,专注于某个特定的功能,一般在特定的场景下复用。
Memory
:ConversationBufferMemory
:内存存储完整历史。ConversationSummaryMemory
:总结历史。ConversationBufferWindowMemory
:滑动窗口历史。PostgresChatMessageHistory
:数据库存储。RedisChatMessageHistory
:Redis 存储。
Retriever
:FAISS
:本地向量存储检索。Pinecone
:云端向量存储。Weaviate
:多模态检索。BM25Retriever
:基于关键词检索。MultiQueryRetriever
:多查询扩展。
Tools
:GoogleSearchAPIWrapper
:谷歌搜索。ShellTool
:执行 shell 命令。SQLDatabaseToolkit
:数据库操作。
DocumentLoader
:TextLoader
:文本加载。PDFLoader
:PDF 解析。WebBaseLoader
:网页抓取。MarkdownLoader
:Markdown 文件加载。
TextSplitter
:RecursiveCharacterTextSplitter
:递归分块。SemanticChunker
:语义分块。
基于基础组件的具体实现,提供特定功能。社区模块(如 langchain-community
)还提供更多实现,例如额外的 Embeddings
(HuggingFaceEmbeddings
)、LLMs
(LlamaCpp
)。这一层种类繁多,覆盖数据处理、存储、检索等功能。
3、基础组件层也能实现小功能
基础组件层(例如 LLM
、Tool
、PromptTemplate
)本身也能实现一些小功能。但更通用、更原子化,只执行一个最基本操作,它们是构建更高级功能的基石,基础组件由于简单, 可以在很多地方被复用。
Runnable
:核心接口,所有可执行组件的基类。(这个有非常非常多,详看Runnable章节表格)BaseChatMessageHistory
:对话历史接口。Document
:文档对象,用于表示文本内容和元数据。Embeddings
:嵌入模型接口(如OpenAIEmbeddings
)。
当然还有其他基础接口,如 BaseRetriever
(检索器基类)、BaseStore
(键值存储基类)。社区扩展(如 langchain-community
)会增加更多具体实现,但这些都基于核心抽象。这属于底层零件,覆盖所有基本操作。
🔘Langchian的核心模块
💠Runnable
重要性超过其他概念,它是所有功能的起点。无论是简单任务还是复杂工作流,最终都依赖 Runnable
。它定义了输入、输出、执行逻辑的标准化方式。因为所有组件无论是基础组件层、功能模块层,还是应用封装层,每个层的各个模块都实现Runnable接口。作为"原子单位",多复杂的模块也都是Runnable
的拼装实现。
💠Chains
Chains
是 LangChain 的"骨架"提供结构化执行。将分散的组件整合成可复用的流程,是 LangChain 的主要应用形式。例如,RetrievalQA
链包含检索和生成两个步骤。它依赖 Runnable
的标准化接口,没有 Runnable
,Chains
无法运行。同时它整合了 Memory
(上下文管理)和 Tools
(外部功能),形成完整的解决方案。
💠Agents
Agents
是动态执行任务的高级组件,基于 LLM推理后调用工具解决问题,它赋予 LangChain 自主性和智能化,超越了静态生成。Agents
由 LLM/ChatModel
(推理核心)、Tools
(执行能力)和 PromptTemplate
(指导行为)组成。依赖 Runnable
(作为执行基础)和 LLM
(作为决策引擎)。同时又整合 Tools
和 Memory
,实现动态工作流。例如,ReAct 代理结合推理和行动。
💠Tools
Tools
是 LangChain 中连接 LLM 与外部功能的接口,如搜索、计算或 API 调用,扩展了 LLM 的能力,使其从语言生成转向实际操作,是GPT这种语言对话模型进化到Agent的核心区别。与 Agents
紧密结合,增强其功能性;也通过 MCP 等协议整合外部生态。
💠Memory
可能大家会误以为对话历史是大模型本身实现的功能,其实所有的LLM都是无状态的(stateless),它们不会自动记住之前的对话内容,必须显式地将历史对话内容作为输入的一部分传递给模型。Memory
解决了 LLM 无状态的局限,是交互应用的基础。
RunnableWithMessageHistory
类的初始化方法(__init__
)有以下几个关键参数:
python
class RunnableWithMessageHistory:
def __init__(self,
runnable, # 必填,比如 prompt | llm,这是要封装的基础链
get_session_history, # 必填,一个return历史对象的函数。
input_messages_key=None, # 可选
history_messages_key=None, # 可选
output_messages_key=None, # 可选
):
RunnableWithMessageHistory
的核心作用是:记录对话历史 :用键值对或消息列表形式将用户的输入和模型的输出存储到内存或者数据库。 注入上下文 :对话时,将当前session的历史记录附加到当前输入中,形成一个完整的上下文。管理长度限制 :模型的上下文长度有限,需要负责裁剪或压缩历史记录,以确保输入不会超出限制。除了 RunnableWithMessageHistory
,还有其他方法可以实现对话历史管理,例如手动记录对话历史,并在每次调用模型时将其附加到输入中。也有一些框架或工具(如 Rasa、Dialogflow)内置的对话历史管理功能。比如Dify 与Coze 的工作流中看到类似"知识检索"或"上下文管理"的节点,背后原理类似,只是被封装成了可视化组件。平台维护一个会话存储(可能是内存、数据库或云端)。
💠Retrieval
Retrieval
通过外部数据源(如向量存储)增强 LLM 的知识,通常涉及 Indexes
(文档加载、分块、存储、检索)。解决了 LLM 知识截止和幻觉问题,是企业应用的杀手锏。Retrieval
由 Document Loaders
、Text Splitters
、VectorStores
和 Retrievers
组成。依赖 Runnable
(执行流程)和嵌入模型(如 OpenAI Embeddings)。与 Chains
(如 RetrievalQA
)和 Memory
(长期上下文)整合。 。
🟡 LCEL
LCEL------LangChain Expression Language,旨在简洁声明式的描述如何组合Runnable组件构建链。类比 Django 的 DTL(Django Template Language)或 GLSL(OpenGL Shading Language),都是特定领域的 DSL。其实目前大多数标准用例已经不需要显式写 | 去构建链,因为 LangChain 的高级 API 已经封装了这些逻辑,但是底层依然都是LCEL的思想。该语言负责定义如何构建和优化链,是一切功能的基础,LCEL语言的特性如下:
- 并行(Parallelization): 允许多个任务同时执行,提高效率。
- 回退(Fallbacks): 在某个组件或模型失败时,提供备用方案,确保系统鲁棒性。
- 追踪(Tracing): 跟踪链的执行过程,便于调试和监控。
- 批处理(Batching): 批量处理输入,提高性能。
- 流处理(Streaming): 支持实时输出流,适用于交互式应用。
- 异步(Async): 支持异步编程,适合高并发场景。
- 组合(Composition): 允许灵活组合不同组件(如模型、检索器、工具等)。
🟡 Runnable
Runnable 是桥梁",它是功能的载体(实现某件事),还是系统的连接点(让这件事融入链)。
Runnable
是 LangChain 的核心抽象基类,规定了下面三个核心:
- 标准化 :
invoke
(单次攒结果)、batch
(批量)、stream
(边生成边展示),都是自回归生成。 - 组合化 :用
|
连接,前后对接。 - 扩展化 :继承
Runnable
加新模块。
无论是多么复杂的任务,最终都会分解为一个个确定性的原子操作 ,这些原子操作由 Runnable
实现,且每个 Runnable
的输入和输出是固定的、静态的。这是所有的链或者Agent运行的本质基础。核心在于将静态的 Runnable
按需拼接、重组,构建出适应不同任务需求的执行路径。从底层静态的确定性,到上层动态的灵活性。
低代码平台上用户通过拖拽和连线模块化组件 将其组合成工作流,本质上就是 Runnable的可视化实现。在Langchian中,Runnable 让 LLM、Prompt、Parser 等模块统一接口,既能独立运行,也能串成链,它是模块化 + 组合的基石,没它就没法统一。
Langchian自带的
Runnable
类型概览表
类别 | 类型 | 功能描述 |
---|---|---|
基础构建块 | RunnableLambda |
封装自定义 Python 函数为 Runnable |
RunnablePassthrough |
透传输入,占位或增强上下文 | |
流程控制 | RunnableSequence |
顺序执行多个 Runnable ,形成管道 |
RunnableParallel |
并行运行多个任务,返回结果字典 | |
RunnableBranch |
根据条件选择执行路径 | |
模型与提示 | ChatOpenAI |
调用语言模型执行推理 |
ChatPromptTemplate |
生成结构化提示,确保模型按预期输出 | |
增强功能 | RunnableWithMessageHistory |
管理对话历史,保持上下文 |
RunnableWithFallbacks |
失败时执行备用方案 | |
RunnableWithRetry |
自动重试失败任务 | |
RunnableWithListeners |
触发执行时回调(如日志) | |
RunnableWithConfig |
动态调整运行时参数 | |
RunnableWithMetadata |
传递额外上下文信息 | |
RunnableWithBranching |
条件分支增强(基于 RunnableBranch ) |
|
RunnableWithParallel |
并行增强(基于 RunnableParallel ) |
|
输出处理 | StrOutputParser |
简化输出,提取纯文本 |
JsonOutputParser |
结构化输出,解析 JSON 格式 | |
工具与绑定 | Tool |
定义工具,可通过 RunnableLambda 或 as_runnable() 转为 Runnable |
RunnableBinding |
绑定特定配置到 Runnable |
这些 Runnable 的参数设计分为两类:初始化时提供的和运行时动态传入的。初始化时提供的参数
- 比如
RunnableLambda
的func
、RunnableSequence
的runnables
列表、或者RunnableWithMessageHistory
的get_session_history
,主要是定义这个 Runnable 的"结构"和"行为",就像搭积木时先决定用什么零件、怎么拼------这些是固定的基础,决定了它是什么、能干啥,所以必须在创建时就给好,不然链就没法成型。 - 而运行时传入的参数,比如 config(包括 temperature、timeout、甚至 session_id),则是"调节旋钮",用来在执行时根据具体情况微调行为------比如这次任务要快点(调 timeout)、这次对话要更创意(调 temperature),或者指定某个用户的上下文(传 session_id)。因为初始化参数是静态的"蓝图",得提前定好才能保证链的稳定性和可复用性;而运行时参数是动态的"开关",让同一个链能适应不同场景,避免每次需求变了就得重搭一个新链。这种分工既保证了灵活性(运行时调整),又保持了结构清晰(初始化固定)。
RunnableLambda
-
func
(必需):一个 Python 可调用对象(如函数或 lambda)。由开发者手动定义,通常是自定义的业务逻辑。一般在代码编写时直接提供,作为链的初始化部分。pythonfrom langchain_core.runnables import RunnableLambda runnable = RunnableLambda(lambda x: x.upper())
RunnablePassthrough
-
无参数,若使用
assign
,则func
来自开发者自定义逻辑。主要靠链的上下文传递数据。pythonfrom langchain_core.runnables import RunnablePassthrough runnable = RunnablePassthrough().assign(extra=lambda x: "extra")
RunnableSequence
-
按顺序执行多个
Runnable
。需要多个Runnable
实例(如模型、提示、工具)。pythonfrom langchain_core.runnables import RunnableSequence chain = RunnableSequence(preprocess, model, parser)
RunnableBranch
-
branches
(必需):列表或元组,格式为(condition, runnable)
,condition
是函数。 -
default
(可选):默认Runnable
,当所有条件不满足时执行。pythonfrom langchain_core.runnables import RunnableBranch branch = RunnableBranch((lambda x: x > 0, lambda x: "positive"), lambda x: "negative")
ChatOpenAI
-
model_name
(可选):模型名称(如 "gpt-4"),默认值视库而定。 -
temperature
(可选):控制输出随机性,默认 0.7。 -
max_tokens
(可选):最大输出长度。 -
api_key
(可选):OpenAI API 密钥。 -
streaming
(可选):是否启用流式输出,默认 False。pythonfrom langchain_openai import ChatOpenAI model = ChatOpenAI(model_name="gpt-4", temperature=0.9, api_key="sk-xxx")
ChatPromptTemplate
-
messages
(必需):提示模板,通常通过from_template
指定。pythonfrom langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_template("Answer: {input}")
RunnableWithMessageHistory
-
runnable
(必需):主Runnable
链。链构建时的主逻辑 -
get_session_history
(必需):开发者定义的存储逻辑(如内存、数据库)。基于session_id
。 -
input_messages_key
(可选):输入消息的键名。开发者根据输入格式指定。 -
history_messages_key
(可选):历史消息的键名。pythonfrom langchain_core.runnables.history import RunnableWithMessageHistory chain = RunnableWithMessageHistory(runnable, lambda x: memory[x])
RunnableWithFallbacks
-
runnable
(必需):主Runnable
。 -
fallbacks
(必需):备用Runnable
列表。pythonfrom langchain_core.runnables import RunnableWithFallbacks chain = RunnableWithFallbacks(runnable, fallbacks=[backup])
RunnableWithRetry
-
runnable
(必需):主Runnable
。 -
max_attempts
(可选):最大重试次数,默认 3。 -
delay
(可选):重试间隔(秒)。pythonchain = RunnableWithRetry(runnable, max_attempts=3, delay=1)
StrOutputParser
-
无特定参数。
pythonfrom langchain_core.output_parsers import StrOutputParser parser = StrOutputParser()
JsonOutputParser
-
schema
(可选):期望的 JSON 结构。开发者根据下游需求定义pythonfrom langchain_core.output_parsers import JsonOutputParser parser = JsonOutputParser()
RunnableWithThrough
(自定义)
-
runnable
(必需):主Runnable
。 -
through_func
(必需):中间处理函数。开发者自定义pythonchain = RunnableWithThrough(runnable, lambda x: x * 2)
RunnableBinding
-
runnable
(必需):主Runnable
。 -
kwargs
(必需):绑定的参数字典。pythonbound = runnable.bind(temperature=0.9)
🟡 LangChain-Core
LangChain-Core就是引擎,就比如Django的django.core
或React的react-core
,再比如HTML语言由浏览器引擎负责渲染。因为LCEL是一种语法而已,不涉及具体实现细节,LangChain-Core则提供按这种语法写的程序的具体的实现,比如Runnable
类包含invoke()
、stream()
等方法,真正执行这个语法prompt | model | output_parser
(依赖LangChain-Core中的Runnable
和相关类来实际执行,Runnable接口是LangChain-Core的核心抽象,定义了如何运行和组合链。任何链、模型或工具都必须实现或继承这个接口)。
🟡 LangChain-Community
社区扩展了LangChain-Core的核心功能,提供了丰富的第三方集成、工具和组件增强LangChain。主要分为三个子模块:Model I/O 、Retrieval 和Agent
💠Model I/O 模块
Model负责调用API、传递输入并接收输出。这是与语言模型交互的核心组件,负责优化模型的输入和输出,确保高效、准确地与LLM(如GPT、LLaMA等)通信。Model整合了大量社区贡献的模型适配器,让开发者快速接入新模型,包括各种语言模型 (如 GPT-4、Claude-3)、嵌入模型 (如 text-embedding-3)、多模态模型 (如 Gemini-Pro-Vision)以及工具链相关模型(如支持函数调用的 GPT-4)。Prompt模块主要面向开发者,是LangChain链中的"变量提取器"和"数据桥梁",通过提取关键信息(如订单号、状态)并将其传递到下一环,确保链条的连续性和复杂任务的完成。没有Prompt,链无法流动,模型只能处理孤立简单任务。
💠Retrieval RAG模块
💠RAG组成
- 【1】Document Loader:RAG流程的第一步,将外部知识加载到LangChain中。提供统一的接口加载各种格式的文档,支持从本地文件、云存储、API加载数据。社区贡献了大量Loader。
- 【2】Text Splitter:文本预处理的剪刀,根据预设规则(如字符数、段落、句子)分割长文本,保留文本的语义完整性(如避免切断句子中关键信息),支持重叠分割或元数据传递,确保上下文关联。可自定义分割规则,适配特定文档或模型需求。社区提供了多种Splitter。
- 【3】Embedding Model :将Splitter向量化,这些向量存储在Vector Store中,供Retriever进行语义匹配。社区提供了多种嵌入模型(如
langchain-huggingface
、langchain-openai
)。 - 【4】Vector Store:向量数据库,是RAG中关键的性能瓶颈优化点,使用高效索引(如FAISS、HNSW)存储向量,支持快速相似性搜索,提供查询接口,返回与输入向量最相似的文档片段(通常按余弦相似度排序)。社区提供了众多向量数据库。
- 【5】Retriever :是RAG中"搜索引擎"的角色。从文档或数据库中检索与用户查询最相关的片段,通常基于向量搜索(语义相似性)或关键词匹配(传统搜索)。支持多种检索算法:比如向量搜索 如FAISS、HNSW算法,基于语义相似性比如关键词匹配如BM25,基于传统文本检索,也可自定义检索策略(如加权评分、过滤器、重新排序)。社区提供了策略??没有吧
💠RAG路由
普通 LLM 路径不涉及 RAG:用户提问 → 大模型直接生成答案。大模型仅依赖其内部知识和推理能力。用户问"2+2等于多少?" → 大模型回答"4"。RAG 路径涉及 RAG:用户提问 → 嵌入模型将问题转化为向量 → Retriever 检索相关文档 → 大模型结合文档生成答案。 用户问"退货政策是什么?" → 嵌入模型将问题转化为向量 → 检索到"退货需在30天内提出" → 大模型生成答案:"退货需在30天内提出,商品需保持原包装完整。"(嵌入模型通过语义相似性(如余弦相似度)实现高效检索,而大模型无法直接完成这一任务。)如何设计切换逻辑?可以基于规则:预定义关键词(如"政策""订单")触发 RAG 路径。
python
if "政策" in user_question:
use_rag = True
else:
use_rag = False
也可以直接基于大模型意图识别: 使用大模型分析用户提问,判断是否需要外部知识。相比于简单的规则匹配(如关键词检测),这种方法能够更智能地理解用户提问的语义,
python
intent_prompt = "判断以下问题是否需要外部知识:{question}"
intent_response = llm(intent_prompt.format(question=user_question))
if "需要外部知识" in intent_response:
use_rag = True
else:
use_rag = False
💠RAG召回率
在RAG系统中,召回率(Recall)是指Retriever能够检索到与用户查询相关文档的百分比。如果召回率低,意味着系统返回的文档片段可能不完整、不相关或遗漏关键信息,最终影响语言模型生成答案的准确性和质量。以下是Retrieval模块中可能导致召回率低的原因,按模块逐一分析: 优先级排序(从对召回率影响最大的到最小的):
- Embedding Model 词嵌入模型:向量质量决定语义匹配的基石。
- Retriever 检索召回器:检索策略和参数直接影响召回结果。
- Text Splitter 文本分割器:分割方式影响上下文和向量生成质量。
- Vector Store 向量数据库:存储和索引效率影响检索性能。影响效率而不是召回率
- Document Loader 文档加载器:如果数据输入有问题,会间接影响性能。
Embedding Model 词嵌入模型的质量是召回率的关键,因为它直接决定文档和查询的向量表示准确性。如果向量表示不准确的话呢么Retriever无论使用何种算法都无法找到相关文档。某些嵌入模型可能偏向特定领域(如英语文本),对多语言或专业术语(如法律、医疗)表现较差,建议根据自己的知识库,选择适合领域的高质量嵌入模型,调整模型参数以适配特定数据集。
Retriever 检索召回器是直接负责召回的组件,其算法选择和参数配置对召回率影响显著。如果检索算法不 适合,比如如果使用关键词匹配(如BM25)而非向量搜索(如FAISS),可能无法捕捉语义相似性,导致相关文档被遗漏。再比如向量搜索中,如果使用简单的余弦相似度而没有重新排序或过滤,可能返回不相关的文档。如果Top-K(返回的文档数量)设置不当 如Retriever的Top-K设置太低(如k=1),可能遗漏相关文档;如果太高,可能引入噪声。
Text Splitter 分割方式影响Embedding Model生成的向量质量和Retriever的检索结果。分割得太小可能会丢失语义上下文,导致Retriever无法找到包含完整信息的段落。如果分割得太大可能包含过多无关信息,稀释了相关性。如果切断了关键句子或段落,相关信息可能被分散到不同片段中。
💠Agent Tooling 模块
Tool :定义代理可以使用的具体工具,如API调用、数据库查询、文件操作或网络请求。LangChain-Community提供统一接口(BaseTool
),开发者可以自定义工具或使用社区提供的现成工具。代理通过推理(通常基于大模型)决定何时、何地调用哪个工具。开发者可以创建自定义工具(如企业内部API、特定业务逻辑),并提交到LangChain-Community仓库或本地使用。也支持社区贡献的工具。(description正是工具的核心说明,告诉代理"这个工具是干嘛用的)
ToolKit :组合多个工具形成工具集,可以定义工具集的优先级、描述和适用场景,让代理能够根据任务复杂性和上下文灵活选择工具,代理根据用户输入或上下文选择工具。比如在电商客服场景中开发者创建一个工具集,包括: (1)"查询订单状态"工具 :通过API获取订单状态;(2)"查找退货政策"工具 :从知识库检索退货政策文档(3)"联系客服"工具:调头API发送邮件或生成客服链接。
💠Tool
python
from langchain.tools import BaseTool
import requests
class GetWeatherTool(BaseTool):
name = "GetWeather"
description = "查询指定城市的当前天气。输入应为城市名称(如'北京')。" # 必须有
def _run(self, order_id: str) -> str:
url = f"http://api.weatherapi.com/v1/current.json?q={city}"
response = requests.get(url)
data = response.json()
return f"{city}当前天气:{data['current']['condition']['text']},{data['current']['temp_c']}°C"
# 调用
tool = GetWeatherTool()
result = tool.run("北京") # 输出如"北京当前天气:晴,25°C"
这个工具没有自己写逻辑,只是通过requests
请求库调用现成的天气API而已,但如果直接用requests.get
调用API,那么它只是一个Python函数,无法被LangChain的代理识别或调用。代理需要Tool
的标准化格式(如BaseTool
接口),才能通过推理动态选择和执行工具。我通过继承BaseTool
、添加这个抽象类的属性name
和description
,将一个简单的API调用封装为LangChain的Tool
,这样就能被Agent调用啦。
💠BaseTool
那BaseTool
是什么?为啥继承了BaseTool
之后,这个函数就变成了Langchian里的工具了?BaseTool
这个抽象类就是用于定义Langchian里的Tool的规范:要求每个工具都必须实现统一的方法(如 run() ),必须有 name 和 description,用于告诉 LLM 这个工具的名称和调用方式。需要注意,Tool是不能直接写入链里的,因为链必须是Runnable对象的组合,它没有实现Runnable
的接口(如invoke
、stream
、batch
),因此无法直接与链或LCEL的管道语法(如|
)兼容。没有实现接口的对象不能适配,是因为接口定义了行为规范,如果允许未实现接口的对象直接参与,会导致调用失败和设计原则的破坏。 链(Chain)的核心机制就是通过 invoke
方法 将多个 Runnable
对象串联起来。前一个 Runnable
的输出会作为后一个 Runnable
的输入,从而实现数据的流动和任务的执行。所以也能发现,我们的tool的单独使用,是靠.run,而不是靠.invoke,单独作为一个环节放入链里肯定是不行,因为不是Runnable。虽然工具不是 Runnable,但 Agent 可以直接调用工具的 run() 方法。 Agent 的设计允许它动态选择和调用工具,而不需要工具实现 Runnable 接口。
💠RunnableLambda
如果工具真的需要直接用在链中 ,必须将其用RunnableLambda 包装为Runnable。因为有时候需要考虑Agent的开销,Agent
本身依赖大模型行推理,这需要额外的计算资源和时间,如果您只需要调用一个固定的工具(如"查询天气"),直接用链调用工具比用Agent
推理更快速。还需要考虑Agent的不可控性:但凡是通过大模型推理动态选择工具,那就可能出现误判调用错误工具等,如果任务逻辑简单明确("查询天气"),直接用链调用工具更可控。
其实区别就是Agent 是"全自动" ,LLM 作为大脑持续控制全流程,链式语法是"半自动",依赖开发者预定义逻辑,LLM 仅在特定节点辅助。
假设你有一个工具集,包含以下工具:
python
tools = [ GetWeatherTool(),
SearchNewsTool(),
TranslateTextTool(),
CalculateMathTool()]
💠RunnableLambda 模式
python
import requests # API调用
from langchain.tools import BaseTool # 工具基类
from langchain.prompts import PromptTemplate # 提示模板
from langchain.llms import OpenAI # 语言模型
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
from langchain_core.runnables import RunnableLambda # 包装为Runnable
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# 定义工具
class GetWeatherTool(BaseTool):略
# RunnableLambda可将工具包装为Runnable对象
⛔tool_as_runnable = RunnableLambda(lambda x: GetWeatherTool().run(x))
# 创建链
prompt = PromptTemplate(input_variables=["city"], template="查询{city}的天气。")
llm = OpenAI(model="gpt-4")
chain = prompt | ⛔tool_as_runnable | llm
# 调用链
if __name__ == "__main__":
result = chain.invoke({"city": "北京"}) # 输出如"北京当前天气:晴,25°C"
print(result)
这也意味着工具的调用逻辑是固定的,无法动态选择其他工具。如果需要实现多工具协作或复杂逻辑,必须手动编写分支结构或循环逻辑。假设你需要根据用户输入调用不同的工具,可以这样实现:
python
def choose_tool(input_data):
if "天气" in input_data["input"]:
return GetWeatherTool().run(input_data["city"])
elif "新闻" in input_data["input"]:
return SearchNewsTool().run(input_data["query"])
else:
return "无法识别的请求。"
chain = prompt | RunnableLambda(choose_tool) | llm
而Agent
可以基于 LLM 的自然语言理解和推理能力,结合工具的描述(description
),动态决定调用哪个工具**。用户输入可能是:"北京今天的天气怎么样?" 或者 "帮我翻译'你好'成法语。"Agent
会根据用户输入的内容和工具的描述,自动匹配到最合适的工具。例如:对于"天气"相关的输入,会选择 GetWeatherTool
。
💠Agent 模式
python
import requests # API调用
from langchain.tools import BaseTool # 工具基类
from langchain.prompts import PromptTemplate # 提示模板
from langchain.llms import OpenAI # 语言模型
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
from langchain.agents import AgentExecutor, create_react_agent # 代理创建和执行
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# 定义工具
class GetWeatherTool(BaseTool):
name = "GetWeather"
description = "查询指定城市的当前天气。输入应为城市名称(如'北京')。" # Agent就是靠它判断的
def _run(self, city: str) -> str:
url = f"http://api.weatherapi.com/v1/current.json?q={city}"
response = requests.get(url)
data = response.json()
return f"{city}当前天气:{data['current']['condition']['text']},{data['current']['temp_c']}°C"
# 初始化组件
llm = OpenAI(model="gpt-4")
tools = [GetWeatherTool()]
prompt = PromptTemplate(input_variables=["input"], template="你是一个智能助手。用户输入:{input}\n工具可用:{tools}\n回答:")
# 创建ReAct代理和执行器
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools) # AgentExecutor是Runnable
# 用链调用代理
if __name__ == "__main__":
result = agent_executor.invoke({"input": "北京今天天气怎么样?"})
print(result["output"])
关于Agent 调用 tools
的动态性,首先需要了解一下 ReAct框架,这是一种结合 推理(Reasoning) 和 行动(Action) 的框架,旨在通过 LLM 的自然语言理解和推理能力,动态选择工具并完成任务。而create_react_agent
是 LangChain 提供的一个函数,用于创建一个基于 ReAct 框架 的Agent,这样代理会根据用户输入和工具描述,生成推理步骤,并调用合适的工具。
python
create_react_agent( llm大语言模型, tools工具集合, prompt提示词模板)
然后这个Agent将被传递给执行器 AgentExecutor
。
python
AgentExecutor(agent, tools, verbose=False, max_iterations=15, early_stopping_method="force")
agent
:由create_react_agent
创建的智能代理。tools
:List[BaseTool]
,与create_react_agent
中的工具集一致。verbose
:类型:bool
,是否打印详细的执行日志(默认为False
)。max_iterations
:类型:int
。代理的最大迭代次数。多次迭代后仍未完成任务,会停止执行。early_stopping_method
:类型:str
。当达到最大迭代次数时的处理方式(默认"force"
强制停止)。
所以,Agent模式中,LLM 作为大脑贯穿整个流程,而不仅仅是提取出语言中的参数传入固定的链里的tool。
阶段 | LLM 的作用 | 示例场景 |
---|---|---|
输入解析 | 将用户自然语言转换为结构化参数(如提取城市名、关键词) | 用户说"北京天气",LLM 提取 city="北京" |
工具选择 | 基于工具描述(description )和输入语义,动态选择最合适的工具 |
输入"翻译'Hello'成法语" → 选择 TranslateTextTool |
多步骤推理 | 分解复杂问题为子任务,生成中间推理步骤(ReAct 框架的核心) | 解决数学题时先调用计算工具,再用翻译工具解释结果 |
结果整合与格式化 | 将工具返回的原始数据(如 JSON、API 响应)转换为自然语言回答 | 将天气 API 的 {temp:25, weather:"晴"} 转换为"北京今天晴,气温25摄氏度" |
错误处理与重试 | 当工具调用失败或返回无效结果时,LLM 决定重试、切换工具或终止流程 | 天气API超时 → LLM 判断是否重试或提示用户 |
🟡 LangChain
LangChain 是一个主框架,自带核心的 Runnable 接口(LLM、PromptTemplate、Chain、Retriever)和组件(如Document、Embeddings、VectorStore、Memory),为构建复杂应用提供了基础规范;此外,社区工具(如 Pinecone 替代 VectorStore、Redis 替代 Memory、Hugging Face 替代默认 LLM)通过更高性能、更灵活的实现方式,扩展了 LangChain 的能力,开发者可根据需求选择自带功能或社区替代品以平衡易用性与性能。
早期没有LCEL语法,构建连不是通过管道符 Runnalbe1 | Runnable2
这么简单,而是通过实例化链类 ,去实现的链对象,首先需要显式导入一些链类(最基础的链类LLMChain
、顺序链类SequentialChain
、检索链类RetrievalQA
),然后再用Runnable组件来构建链。
简单的 LLM 调用构建单一的链
python
=============================== LLMChain的方式 ======================================
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain (虽然这种简单到就一个链,还是得用LLMChian)
from langchain_community.llms import OpenAI
# 初始化 LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 定义提示模板
prompt_template = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
# 创建 LLMChain
chain = LLMChain(prompt=prompt_template, llm=llm)
# 运行链
response = chain.run / invoke({"topic": "bears"})
=============================== LCEL语法的方式 ====================================
from langchain.prompts import ChatPromptTemplate
from langchain_community.llms import OpenAI
# 初始化 LLM
llm = ...
# 定义提示模板
prompt_template = ...
# 构建链:显式连接组件
chain = prompt_template | llm |
# 运行链
response = chain.invoke({"topic": "bears"})
顺序链
python
============================ 传统方式:使用 `SequentialChain` ======================
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain, SequentialChain
from langchain_community.llms import OpenAI
# 初始化 LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 第一个链:生成剧本
prompt_template1 = ChatPromptTemplate.from_template("Write a short screenplay about {topic}.")
chain1 = LLMChain(llm=llm, prompt=prompt_template1, output_key="screenplay")
# 第二个链:评论剧本
prompt_template2 = ChatPromptTemplate.from_template("Review the following screenplay:\n\n{screenplay}")
chain2 = LLMChain(llm=llm, prompt=prompt_template2, output_key="review")
# 组合成顺序链
overall_chain = SequentialChain(
chains=[chain1, chain2],
input_variables=["topic"],
output_variables=["screenplay", "review"]
)
# 运行链
response = overall_chain.invoke({"topic": "a robot learning to love"})
=========================== RunnablePassthrough.assign ========================
from langchain.prompts import ChatPromptTemplate
from langchain.runnables import RunnablePassthrough
from langchain.output_parsers import StrOutputParser
from langchain_community.llms import OpenAI
# 初始化 LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 第一个链:生成剧本
prompt_template1 = ChatPromptTemplate.from_template("Write a short screenplay about {topic}.")
chain1 = prompt_template1 | llm | StrOutputParser()
# 第二个链:评论剧本
prompt_template2 = ChatPromptTemplate.from_template("Review the following screenplay:\n\n{screenplay}")
chain2 = (
RunnablePassthrough.assign(screenplay=chain1) #通过 `RunnablePassthrough.assign` 隐式传递中间结果
| prompt_template2
| llm
| StrOutputParser()
)
# 运行链
response = chain2.invoke({"topic": "a robot learning to love"})
带有检索的问答
python
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA
from langchain.prompts import ChatPromptTemplate
from langchain.runnables import RunnablePassthrough
from langchain.output_parsers import StrOutputParser
from langchain_community.llms import OpenAI
# 创建文档库和向量存储(两种方式共用)
documents = [
"LangChain is a framework for developing applications powered by language models.",
"LCEL is LangChain Expression Language.",
"RetrievalQA is a chain for question-answering with retrieval.",
]
vectorstore = Chroma.from_texts(documents, OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
# ============================== 传统方式:`RetrievalQA` ==============================
# 创建 `RetrievalQA` 链
qa_chain = RetrievalQA.from_chain_type(
llm=OpenAI(model="gpt-3.5-turbo"), # 使用 OpenAI LLM
chain_type="stuff", # 定义链类型(如 "stuff"、"map_reduce" 等)
retriever=retriever, # 检索器
)
# 运行链
response_traditional = qa_chain.invoke({"query": "What is LCEL?"})
# 【区别点】
# 1. 提示模板是固定的,无法轻松修改,如果需要自定义提示模板,必须深入研究 `RetrievalQA` 的源码
# 2. 检索逻辑被封装在 `retriever` 中,行为是固定的,无法动态调整,如果需要过滤检索结果,必须通过外部代码实现。
# 3. 后处理逻辑需要额外的代码,无法直接集成到链中。而手动可以轻松添加后处理步骤
# ============================== LCEL 方式:手动构建链 ==============================
# 定义提示模板(可以完全自定义)
prompt_template = ChatPromptTemplate.from_messages([
("system", "Answer the question based only on the following context:\n{context}"),
("user", "{question}")
])
# 构建链
chain = (
{"context": retriever, "question": RunnablePassthrough()} # 将检索结果作为上下文
| prompt_template # 应用提示模板
| OpenAI(model="gpt-3.5-turbo") # 调用 LLM
| StrOutputParser() # 解析输出为字符串
)
# 运行链
response_lcel = chain.invoke("What is LCEL?")
# 【区别点】
# 1. 提示模板完全由开发者控制,可以轻松修改。例如:
# - 添加系统消息:("system", "You are an expert on LangChain.")
# - 修改用户输入格式:("user", "Please explain: {question}")
# 2. 检索逻辑可以动态调整。例如:
# - 添加过滤器:filtered_retriever = retriever.filter(lambda doc: "LCEL" in doc.page_content)
# 3. 可以轻松添加后处理逻辑。例如:
# - 将输出转换为大写:| (lambda x: x.upper())
RunnableParallel并行处理
python
from langchain.prompts import ChatPromptTemplate
from langchain.runnables import RunnableParallel
from langchain_community.llms import OpenAI
# 初始化 LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 定义提示模板
joke_prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
poem_prompt = ChatPromptTemplate.from_template("Write a short poem about {topic}")
# 并行处理多个任务
parallel_chain = RunnableParallel(
joke=joke_prompt | llm |
poem=poem_prompt | llm | ,
)
# 运行链
response = parallel_chain.invoke({"topic": "the sea"})
传统方式没有直接对应的并行处理工具。LCEL 方式:RunnableParallel
提供了原生的并行处理能力,显著提高效率,尤其是在处理多个独立任务时。
RunnableBranch条件分支
python
from langchain.prompts import ChatPromptTemplate
from langchain.runnables import RunnableBranch
from langchain.output_parsers import StrOutputParser
from langchain_community.llms import OpenAI
# 初始化 LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 定义分支
branch_french = ChatPromptTemplate.from_template("Translate this to French: {input}") | llm | StrOutputParser()
branch_spanish = ChatPromptTemplate.from_template("Translate this to Spanish: {input}") | llm | StrOutputParser()
branch_german = ChatPromptTemplate.from_template("Translate this to German: {input}") | llm | StrOutputParser()
default_branch = ChatPromptTemplate.from_template("I don't know how to translate to {language}") | llm | StrOutputParser()
# 构建条件分支链
chain = RunnableBranch(
(lambda x: x.get("language", "").lower() == "french", branch_french),
(lambda x: x.get("language", "").lower() == "spanish", branch_spanish),
(lambda x: x.get("language", "").lower() == "german", branch_german),
default=default_branch
)
# 运行链
print(chain.invoke({"input": "Hello", "language": "French"})) # 翻译成法语
print(chain.invoke({"input": "Hello", "language": "Japanese"})) # 触发默认分支
传统方式没有直接对应的条件分支工具。LCEL的RunnableBranch
提供了原生的条件分支能力,可以构建更智能、更灵活的应用,根据不同的情况执行不同的逻辑。
Agent的"隐式链"
而在 Agent 模式中,动态推理和规划取代了**显式**的链式结构。Agent 能够根据任务需求,灵活调用工具并决定执行顺序(如串行或并行),也就是说不用费心设计链结构: 一句代码:agent_executor = AgentExecutor(agent=agent, tools=tools)
通常已经足够应对大多数场景。
Tool并不局限于一个函数而已,它可以是一个复杂的链式结构,或者只是一个简单的 Runnable
组件。无论工具是简单的函数还是复杂的链对智能体的调用而言只是看description而已,根据语义与上下文动态决定如何调用它们(顺序、循环、并行等),并将结果组合起来。所以智能体不是取代链,而是将链作为底层构建块。在某些复杂任务中,开发者仍然可以将链式结构嵌入到 Agent 的工作流中,用于定义特定的子流程。
智能体和链是底层与上层:链提供确定性、可控的流程框架;智能体提供动态决策能力。例如,开发者可以预先设计好几条链(作为工具),智能体根据任务需求选择调用哪条链。但假设有好几个常用业务,每个业务流程需要组合几十个小工具,你真的放心让Agent自己决定这几十个工具的顺序?但是你确实可以放心Agent选择执行哪一套业务,这个能力还是有的,而不用手动ifelse。
假设你设计一个智能客服系统:对于这种少量工具组合,Agent有能力自己组合"隐式链"
- 工具箱 :
Tool1: fetch_order_status
(简单函数,返回订单状态)。Tool2: generate_detailed_report
(一个SequentialChain
,检索状态 → 分析 → 生成报告)。Tool3: send_notification
(调用 API 发送邮件)。
- 用户输入:"查一下我的订单状态并通知我。"
- 智能体行为 :
- 推理:需要先查状态,再通知。
- 调用
fetch_order_status
获取状态。 - 调用
send_notification
发送结果。
- 等价链 :智能体动态生成了一个临时的
SequentialChain
:fetch_order_status -> send_notification
。 - 替代设计:如果开发者觉得"查状态并通知"是一个常见需求,流程很确定啊,那就把这个流程封装成一个链作为工具提供给智能体,Agent是具备自动组合这些Too1、2、3的能力的。
智能体如何选择工具?
- 基于语义匹配:智能体会分析输入的语义,选择最适合的工具。例如:
- 基于上下文:如果用户之前提到过某个特定主题,智能体会优先选择与该主题相关的工具。
- 基于反馈:智能体会根据之前的执行结果,优化工具的选择策略,所以这也是我们说它能动态调整。
🟡 Templates、Reference Applications
Templates是LangChain提供的一组预定义的模板和代码示例,帮助开发者快速启动和构建常见应用(如聊天机器人、RAG系统、任务自动化等)。提供快速启动的模板:开发者只需下载模板,填入API密钥(如OpenAI API Key)、数据源(如知识库文档)或自定义逻辑,即可运行。
- 聊天机器人模板:提供一个基于Agents和Tool的对话系统,适合电商客服或技术支持。
- RAG系统模板:提供检索增强生成的工作流,结合Retriever和LLM回答知识密集型问题。
- 任务自动化模板:提供Chains和Tools的组合,适合自动化查询或数据处理。
Reference Applications位于LangChain架构的第五层,与Templates并列,Templates是快速启动的代码模板,适合新手或原型设计,提供简洁框架和基本配置,是代码片段或小规模脚本,无完整应用逻辑。Reference Applications是完整的、可运行的应用示例,通常包括Python项目结构(如src/
、config/
、tests/
),包含完整代码、依赖清单(requirements.txt
)和运行说明(README)并提供示例数据(如测试查询、知识库样本)和部署指南。通常托管在LangChain的GitHub仓库、文档(python.langchain.com)或LangSmith Hub中,开发者可通过git clone
或下载获取。
🟡 LangServe
LangServe处于Templates和Reference Applications之上,是生态系统的部署层。作为一个部署工具,提供一键式部署功能,将LangChain的逻辑(如Chains、Agents)转换为RESTful服务。部署过程简单:
- 定义LangChain链或应用(如
RetrievalQAChain
)。 - 使用LangServe的
add_chain
或add_routes
将链注册为API端点。 - 运行LangServe服务,生成RESTful API。
假设您已经用LangChain构建了一个简单的电商客服机器人,它能回答用户问题(如"我的订单号是123456,订单状态是什么?"),基于一个RetrievalQA链(结合Retriever和LLM)。现在您需要将其部署为RESTful API,供网站或App调用。构建链的步骤都是一样,就是发布不一样而已。
python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.retrievers import VectorStoreRetriever
from langserve import add_chain # LangServe 提供的核心函数
import uvicorn
# 假设 `vector_store` 已定义(存储文档向量)
# 初始化组件
llm = OpenAI(model="gpt-4") # 初始化 OpenAI 的语言模型
retriever = VectorStoreRetriever(vectorstore=vector_store) # 初始化检索器
chain = RetrievalQA.from_chain_type(llm=llm, retriever=retriever) # 创建一个 RetrievalQA 链
# ============================== 使用 FastAPI 部署 ==============================
# 定义请求数据的模型(输入格式)
class QueryRequest(BaseModel):
question: str
# 创建一个 FastAPI 应用实例
app = FastAPI()
# 定义 API 端点
@app.post("/query") # 创建一个 POST 请求的 `/query` 端点,接收用户问题
async def query(request: QueryRequest):
try:
# 调用 LangChain 链处理问题
response = chain.run(request.question)
return {"answer": response}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 【区别点】
# 1. 手动定义输入模型(`QueryRequest`)。
# 2. 手动编写路由逻辑(`@app.post`)。
# 3. 手动显式处理错误(`try/except`)。
# 4. 手动序列化输入输出。
# 运行应用
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000) # 用 Uvicorn 运行 FastAPI 应用,监听本地 8000 端口
# ============================== 使用 LangServe 部署 ==============================
# 使用 LangServe的add_chain直接将现成的链作为 API
add_chain(chain, path="/query") # 将 LangChain 链注册为 `/query` 的 API 端点
# 【区别点】
# 1. 不需要定义输入模型(`QueryRequest`),LangServe 自动处理 JSON 输入。
# 2. 不需要编写路由逻辑(`@app.post`),LangServe 自动生成 FastAPI 路由。
# 3. 不需要显式处理错误,LangServe 内置了错误处理逻辑。
# 4. 不需要手动序列化输入和输出,LangServe 自动完成 JSON 序列化。
# 运行应用
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=8000) # 用 Uvicorn 运行应用,与 FastAPI 示例相同
🟡 LangSmith
python
import os
from langchain import LLMChain
# 配置 LangSmith 环境变量
os.environ["LANGCHAIN_TRACING_V2"] = "true" # 启用 LangSmith 监控
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com" # LangSmith API 端点
os.environ["LANGCHAIN_API_KEY"] = "your-api-key" # LangSmith API 密钥
# 创建并运行链
chain = LLMChain(llm=llm, prompt=prompt_template)
response = chain.invoke({"input": "What is LangSmith?"})
环境变量已启用 LangSmith 的监控功能,如果需要局部启用监控或自定义回调,可以使用 tracing_enabled()
或 LangChainTracer
。
(1) 局部启用监控
python
from langchain.callbacks import tracing_enabled
with tracing_enabled():
#在这个代码块中运行的所有链都会被监控。而代码块外的链不会上传轨迹。
response = chain.invoke({"input": "What is LangSmith?"})
(2) 自定义回调
python
from langchain.callbacks.tracers import LangChainTracer
tracer = LangChainTracer()
response = chain.invoke({"input": "What is LangSmith?"}, callbacks=[tracer])
🟡 LangGraph
🔘 链式思维 Vs 状态驱动
LangChain:链式思维与动态智能体的线性执行
LangChain 的核心是"链"(Chain),一种按预定顺序执行的模块化工作流。链的执行路径是固定的,从头到尾按顺序走一遍,想根据中间结果动态调整行为很复杂。记忆模块增强输入但不改变逻辑,因为它的作用仅限于提供额外的输入信息,无法改变链的执行逻辑。链本身仍然是无状态的 ,每次运行独立完成,步骤顺序不可更改。即使Agent 看似"超级智能",但关键在于:每次决策后的执行仍是线性的。一旦 Agent 选择了某个工具或链,该工具的内部逻辑(比如检索链的"查询 → 返回")按固定顺序执行,无法在过程中根据中间结果调整路径。再进一步讲,即使引入多 Agent 协同,比如一个主 Agent 将任务分配给多个子 Agent,整体的动态性依然存在。然而,最终执行仍需落实到最基本的执行单元------Runnable(可运行对象)。例如:
- 主 Agent 分配任务:子 Agent A 检索,子 Agent B 生成。
- 子 Agent A 的检索链:线性执行"输入 → 查询 → 输出"。
也就是说:无论 Agent 如何动态分配任务,底层 Runnable 的执行逻辑始终是线性的。这种"表层动态、底层线性"的特性是 LangChain 的根本限制。
LangGraph:图结构与状态驱动的非线性工作流
LangGraph 将工作流从链升级为图(Graph),通过节点(Nodes)和边(Edges)定义执行逻辑。每个节点是一个功能单元(如检索、生成、检查),边则指定节点间的跳转规则。与链的线性顺序不同,图结构允许:
- 分支:根据条件选择不同路径。
- 循环:反复执行某些步骤。
- 跳跃:跳过某些节点。
这种非线性结构为复杂逻辑提供了天然支持。
LangGraph 的核心创新是状态(State)。状态贯穿整个图,记录运行过程中的所有信息(如输入、中间结果、控制信号)。状态驱动的动态跳转让 LangGraph 从根本上摆脱了线性限制。每个节点可以:
- 读取状态:了解当前任务进展。
- 修改状态:更新信息或决策。
- 根据状态决定跳转:选择下一步行动。
需要澄清的是:LangGraph 的底层节点内部逻辑可以是线性的 。例如:这些节点类似于 LangChain 的 Runnable,其内部执行是按固定步骤完成的。只是说LangGraph 的非线性体现在节点间的组合方式:节点完成后,根据状态选择跳转到任意其他节点,而非按顺序执行。这种设计将多个线性流程(节点内部逻辑)通过动态决策连接成一个非线性的整体。因此,LangGraph 不是说推翻了线性执行,而是摆脱,将线性节点嵌入一个非线性框架中,通过状态驱动实现更高的灵活性。因此底层节点可能是线性的,但整体是非线性组合。
链:LangChain | 图:LangGraph | |
---|---|---|
状态管理 | 附加式:记忆模块作为输入增强,状态与链分离 | 内置化:状态贯穿整个工作流,嵌入到每个节点 |
执行路径 | 固定:链的执行逻辑不可动态调整,路径是线性的 | 动态:节点可以根据状态调整执行路径,支持分支、循环和回溯 |
决策时机 | 执行前的一次性决策:Agent 根据初始输入选择路径 | 执行过程中的持续性决策:每个节点根据当前状态动态调整行为 |
智能分布 | 集中在 Agent:链本身无智能,依赖预定义逻辑 | 分布在每个节点:每个节点具备局部智能,能够感知状态并作出决策 |
复杂逻辑 | 复杂逻辑需要手动实现(如通过条件判断模拟分支或循环),难以维护 | 天然支持复杂逻辑:分支、循环、并行处理等通过图结构轻松实现 |
状态传递 | 单向传递:状态只能沿着链条从一个节点传递到下一个节点 | 自由流动:状态可以在节点间共享,无需显式传递 |
适用场景 | 简单任务、固定流程 | 复杂任务、动态流程 |
设计哲学 | 线性执行:链的设计初衷是按顺序完成任务 | 图式执行:图的设计初衷是灵活应对复杂任务 |
🔘细节补充:记忆与状态
LangChain 的记忆属于是 附加性 的状态管理,为啥说叫附加性 呢? 每次运行链时,记忆模块会把历史信息(比如之前的对话)作为上下文注入到输入 中。Langchian 提供的 Memory 功能来保存上下文,本质是附属在 Chains 或 Agents 上的,它不是系统的核心部分,所有记忆的传递和更新需要开发者手动处理 ,容易导致复杂任务中的混乱。例如,一个问答智能体可以利用记忆模块记住用户之前的问题和回答,从而生成更连贯的回答。但是链的执行路径是固定的,从头到尾按预定顺序走一遍。记忆模块只是提供了额外的输入,但不会改变链的执行逻辑
LangGraph 将状态内置化 ,每个节点都可以读取和更新状态,从而实现更自然的上下文共享,每个节点可以根据中间结果或状态 动态决定下一步的行动。例如,在问答系统中,智能体可以根据之前的检索结果决定是否需要进一步搜索。再比如循环调用,智能体在某些节点之间反复调用工具,直到满足某种条件为止,比如不断优化答案或验证信息。同理再看分支处理,如果检索结果为空,则跳转到外部搜索引擎;否则直接生成回答。
状态是整个系统的"灵魂",它记录了运行过程中的所有信息,比如当前任务的进展、用户的输入、历史的决策等等。无论是链结构还是图结构,状态都提供了动态决策的基础。而图结构之所以被采用,就是因为它能更好地组织和利用这些状态,让系统具备更高的灵活性和智能性。本质来讲,即使不用图结构,就用之前的链结构,其实一样可以引入状态,但是链结构引入状态意义不大。比如让每个节点都能访问和修改一个全局状态对象。然而,这种设计的意义有限,原因如下:受限链结构是线性的,状态只能从一个节点单向传递到下一个节点。这种顺序是固定的,无法支持灵活的跳转或循环。即使状态中包含了丰富的信息,链结构也只能按照预定义的路径"往下走",无法根据状态动态调整执行流程。而且链的节点连接是静态的,状态的利用仅限于"传递信息",比如告诉下一个节点"上一步的结果是什么"。它无法根据状态改变执行路径,比如跳回之前的节点或选择完全不同的分支。链结构就像一条直路,即使你给它装上"状态"这个导航仪,它也只能沿着固定的路线前进,发挥不出导航的真正潜力。当然啊,你可以在链结构的每个节点中加入if等条件逻辑,但手动在每个节点中编写条件分支会让代码变得非常复杂。如果流程中有几十个节点,每个节点都需要判断状态并决定跳转方向,维护起来将是一场噩梦。并且你难道没发现,当你在链中加入条件分支时,你实际上已经在模拟图的行为------根据状态选择不同的路径,这所谓的"带条件分支的链"本质上就是在向图结构靠拢。既然如此,为什么还要在链结构上"硬撑"呢?直接采用图结构不是更自然、更高效吗?这正是 LangGraph 设计的出发点,也是"图+状态"融合的终极意义。
🔘细节补充:LangGraph组成
节点 (Nodes)::工作流中的基本执行单元,通常是一个 Runnable (可运行对象)。每个节点代表一个独立的计算步骤,例如:调用工具、执行检索、生成文本等。
边 (Edges):: 定义节点之间的连接和跳转逻辑。可以是条件边 (根据状态决定跳转到哪个节点),也可以是固定顺序的边。边的存在使得工作流可以有分支、循环等非线性结构,从而实现更复杂的行为。
状态 (State): 状态是一个数据结构 (通常是 Python 类或字典),用于存储和传递工作流运行过程中的所有关键信息。状态在整个图中流转,每个节点都可以读取和更新状态。状态中可以包含:用户的输入、工具的输出、检索到的文档、中间计算结果、决策路径、对话历史 (Memory)等等...
节点和边定义了工作流的结构 (架构),而状态才是驱动图运行、承载信息流转的"灵魂"。正是由于它记录了图在运行过程中的所有信息,使得循环、复杂的逻辑和更强的智能成为可能。
🔘动态性有限
你需要意识到:Agent的动态性是有限的 。Agent 的决策是基于初始输入做出的,一旦它选择了某个工具或链,那个工具或链就会按照其固定的逻辑执行到底。例如,Agent 决定调用一个检索链,检索链内部的步骤(比如查询数据库 → 返回结果)是线性的,无法在执行过程中根据中间结果调整行为。所以链的设计是根基,Agent 只能在这上面锦上添花"---Agent 的动态性仅限于在预定义的框架内选择,而不能改变框架本身。
再说一个很实际的问题:"你能确保这个 Agent 每次都能根据你的记忆调动正确的模块吗?" 毕竟Agent 的决策依赖于其底层语言模型的推理能力,如果记忆信息复杂或模棱两可,Agent 可能会误判,导致调用错误的工具。这种不确定性正是链的静态设计带来的限制。但是记忆嵌入流程,每个节点都能知道状态,那么每个节点都是智能的,而不仅仅说是把所有的状态一股脑放在一次'输入'里直接灌给 Agent 节点让他自己全局谋划。"
- 在 LangChain 中,智能体的决策基于上一个环节的输入,无法在执行过程中根据中间结果调整行为。
- 在 LangGraph 中,节点可以根据状态中的中间结果决定下一步,实现了真正的动态智能
Langchain vs Langgraph: 天气查询与空调控制 - 简化对比
场景:用户查询"帮我查一下北京今天的天气,如果太热就开空调"。系统需要:1. 提取城市名称、2. 查询天气数据、3. 根据温度判断是否需要开启空调(>30°C时开启)、4. 控制智能家居设备(空调)、5. 生成最终回答
py
# =====================================================================
# 方案一:LangChain(链式结构)实现
# =====================================================================
from langchain.agents import AgentExecutor, create_react_agent
from langchain.llms import OpenAI
from langchain.tools import BaseTool
from langchain.prompts import PromptTemplate
import random # 用于生成随机温度
# === 定义工具 ===
class WeatherTool(BaseTool):
name = "GetWeather"
description = "获取指定城市的天气信息,输入为城市名称(如'北京')"
def _run(self, city: str) -> str:
# 随机生成温度以便测试不同情况
temp = random.randint(25, 35)
return f"晴天,{temp}°C,湿度45%"
class ACControlTool(BaseTool):
name = "ControlAC"
description = "控制家中空调,输入为'on'或'off'以及温度设置,格式如'on,26'"
def _run(self, command: str) -> str:
# 解析命令参数
parts = command.split(',')
if len(parts) < 1:
return "命令格式错误"
action = parts[0].strip().lower()
if action == "on":
temp = parts[1].strip() if len(parts) > 1 else "26"
return f"空调已开启,设置温度为{temp}°C"
elif action == "off":
return "空调已关闭"
else:
return "未知命令"
# === 定义Agent ===
def create_langchain_agent():
# 创建工具集
tools = [WeatherTool(), ACControlTool()]
# 定义LLM
llm = OpenAI(temperature=0)
# 创建提示词模板
prompt = PromptTemplate.from_template("""
你是一个智能家居助手,可以根据天气情况自动控制家中设备。
请按照以下步骤操作:
1. 从用户问题中提取城市名称
2. 使用GetWeather工具查询天气
3. 从天气信息中提取温度数字
4. 如果温度超过30°C,使用ControlAC工具开启空调并设为26°C
5. 综合信息回答用户问题,包括天气情况和空调控制结果
可用工具:
{tools}
用户问题: {input}
思考并一步步解决这个问题。
""")
# 创建ReAct风格的Agent
agent = create_react_agent(llm, tools, prompt)
return AgentExecutor(agent=agent, tools=tools, verbose=True)
# === 运行示例 ===
def run_langchain_example():
agent = create_langchain_agent()
result = agent.invoke({"input": "帮我查一下北京今天的天气,如果太热就开空调"})
return result["output"]
# =====================================================================
# 方案二:LangGraph(图结构)实现
# =====================================================================
from langgraph.graph import StateGraph, END
from typing import TypedDict, Literal, Union
# === 定义状态类型 ===
class WeatherState(TypedDict):
city: str # 存储城市名
weather_data: str # 存储天气数据
temperature: int # 存储温度
ac_status: str # 存储空调状态
final_answer: str # 最终回答
# === 定义节点函数 ===
def extract_city(state: WeatherState) -> WeatherState:
"""提取用户查询中的城市名称"""
# 在实际应用中,这里可能调用LLM从用户输入中提取城市
state["city"] = "北京"
return state
def query_weather(state: WeatherState) -> WeatherState:
"""查询天气API"""
# 随机生成温度便于测试不同情况
temp = random.randint(25, 35)
state["weather_data"] = f"晴天,{temp}°C,湿度45%"
# 提取温度数值
temp_str = state["weather_data"].split(",")[1]
state["temperature"] = int(temp_str.split("°C")[0])
return state
def check_temperature(state: WeatherState) -> Union[WeatherState, Literal["control_ac", "generate_final_answer"]]:
"""检查温度并决定是否需要控制空调"""
if state["temperature"] > 30:
return "control_ac"
else:
state["ac_status"] = "未开启(温度适宜)"
return "generate_final_answer"
def control_ac(state: WeatherState) -> WeatherState:
"""控制空调"""
# 模拟控制空调API调用
state["ac_status"] = "已开启,设置温度为26°C"
return state
def generate_final_answer(state: WeatherState) -> WeatherState:
"""生成最终回答"""
weather_desc = state["weather_data"].split(",")[0]
temp = state["temperature"]
ac_status = state["ac_status"]
state["final_answer"] = f"{state['city']}今天天气{weather_desc},温度{temp}°C。空调{ac_status}。"
return state
# === 构建状态图 ===
def create_weather_graph():
# 初始化图
graph = StateGraph(WeatherState)
# 添加节点
graph.add_node("extract_city", extract_city)
graph.add_node("query_weather", query_weather)
graph.add_node("check_temperature", check_temperature)
graph.add_node("control_ac", control_ac)
graph.add_node("generate_final_answer", generate_final_answer)
# 添加边和条件边
graph.add_edge("extract_city", "query_weather")
graph.add_edge("query_weather", "check_temperature")
# 温度检查节点决定是否需要控制空调
graph.add_conditional_edges(
"check_temperature",
lambda x: x if isinstance(x, str) else "generate_final_answer"
)
# 控制空调后生成最终回答
graph.add_edge("control_ac", "generate_final_answer")
# 生成回答后结束
graph.add_edge("generate_final_answer", END)
# 设置入口节点
graph.set_entry_point("extract_city")
return graph.compile()
# === 运行示例 ===
def run_langgraph_example():
weather_app = create_weather_graph()
# 初始状态为空字典,所有内容将在节点中添加
initial_state = {}
result = weather_app.invoke(initial_state)
return result["final_answer"]
# =====================================================================
# 功能对比测试
# =====================================================================
if __name__ == "__main__":
print("=== LangChain实现 ===")
langchain_result = run_langchain_example()
print(f"回答: {langchain_result}")
print("\n=== LangGraph实现 ===")
langgraph_result = run_langgraph_example()
print(f"回答: {langgraph_result}")
LangChain vs LangGraph 核心设计思想对比:
-
架构模型:
- LangChain: 链式结构,通过"代理(Agent)"加"工具(Tools)"模式运行
- LangGraph: 图结构,通过"节点(Nodes)"和"边(Edges)"明确定义执行流程
-
流程控制:
- LangChain: 隐式流程,依赖LLM根据提示词决定工具调用顺序
- LangGraph: 显式流程,通过图的边清晰定义执行路径
-
状态管理:
- LangChain: 状态保存在LLM的上下文中,不易跟踪
- LangGraph: 使用统一的状态对象,每个节点可读写状态
-
决策逻辑:
- LangChain: 决策逻辑(如温度判断)由LLM负责
- LangGraph: 决策逻辑通过专门的节点和条件边实现
-
可视化与调试:
- LangChain: 工具调用过程可见,但内部决策不透明
- LangGraph: 整个执行流程可以可视化,便于调试
基本区别:
- LangChain的方法是"让LLM思考下一步该做什么",当任务简单时,两种方法都能完成
- LangGraph的方法是"我们告诉系统下一步该做什么"当任务复杂时,LangGraph的显式流程控制和状态管理优势更明显