设计常去的网站wordpress 字体库
2026/1/20 12:37:56 网站建设 项目流程
设计常去的网站,wordpress 字体库,wordpress建站访问提示不安全,wordpress 模板 字体大小各位技术同仁#xff0c;下午好#xff01;今天#xff0c;我们将深入探讨两个在构建大型语言模型#xff08;LLM#xff09;应用中举足轻重的框架#xff1a;LangChain 和 LangGraph。它们都旨在帮助我们编排复杂的LLM工作流#xff0c;但其底层设计哲学#xff0c;尤…各位技术同仁下午好今天我们将深入探讨两个在构建大型语言模型LLM应用中举足轻重的框架LangChain 和 LangGraph。它们都旨在帮助我们编排复杂的LLM工作流但其底层设计哲学尤其是在状态管理和内存处理上存在着本质的物理差异。理解这些差异对于我们构建可扩展、高效且内存友好的LLM应用至关重要。我们将以一种讲座的形式逐步剖析LangChain的线性Chain与LangGraph的Graph在内存管理上的根本区别并辅以代码示例力求揭示其内在机制。LLM应用编排的挑战与框架的崛起随着大型语言模型的普及开发者们不再满足于单次简单的API调用。我们需要构建更复杂的应用例如能够进行多轮对话维持上下文的聊天机器人。能够根据用户指令选择并使用外部工具如搜索、计算器、API调用的智能体。能够执行多步骤任务并根据中间结果调整策略的自动化系统。能够从失败中恢复并重试的鲁棒系统。这些需求引入了新的挑战上下文管理Context Management如何在多轮交互中高效地传递和维护关键信息状态管理State Management如何追踪应用在不同阶段的运行状态尤其是在非线性流程中工具使用Tool Use如何让LLM安全、有效地调用外部函数容错与恢复Fault Tolerance Recovery如何在LLM生成错误或外部工具调用失败时进行处理LangChain和LangGraph正是为了解决这些挑战而诞生的。LangChain以其模块化的组件和链式编程范式迅速普及而LangGraph则在LangChain的基础上引入了图结构和状态机的概念旨在处理更复杂的智能体行为。LangChain 的线性Chain数据流与瞬时状态LangChain的核心理念是将不同的组件如LLM、提示模板、输出解析器、检索器等组合成一个序列数据像流水一样从一个组件流向下一个组件。这便是其“链Chain”的名称由来。1.Chain的基本架构在LangChain中一个Chain本质上是一个由多个可调用对象Runnable组成的序列。每个可调用对象接收输入处理后产生输出并将此输出作为下一个可调用对象的输入。这种模式可以用LangChain Expression Language (LCEL) 优雅地表达。考虑一个简单的链PromptTemplate-ChatModel-StrOutputParser。PromptTemplate接收用户输入生成完整的提示。ChatModel接收提示生成LLM响应。StrOutputParser接收LLM响应将其解析成字符串。from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser # 1. 定义PromptTemplate prompt ChatPromptTemplate.from_template(告诉我一个关于{topic}的冷知识。) # 2. 定义LLM llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.7) # 3. 定义Output Parser output_parser StrOutputParser() # 4. 组合成一个链 simple_chain prompt | llm | output_parser # 调用链 result simple_chain.invoke({topic: 宇宙}) print(result)在这个例子中数据流是线性的{topic: 宇宙}作为输入给prompt。prompt生成一个ChatPromptValue对象包含完整的提示。这个对象是prompt的输出同时也是llm的输入。llm接收ChatPromptValue调用OpenAI API返回一个AIMessage对象。这个对象是llm的输出同时也是output_parser的输入。output_parser接收AIMessage解析出字符串并返回最终结果。2. 内存管理瞬时与显式记忆在LangChain的线性Chain中内存管理可以从两个层面来理解瞬时中间状态和显式长期记忆。a) 瞬时中间状态 (Ephemeral Intermediate State)当数据流经一个链时每个组件的输入和输出都是在Python的内存中创建的临时对象。prompt生成的ChatPromptValue对象在传递给llm后如果不再有其他引用理论上就可以被Python的垃圾回收器回收。llm生成的AIMessage对象在传递给output_parser后同样可能被回收。这种机制的特点是局部性每个组件只关心其直接的输入和输出。瞬时性中间结果通常只在紧接着的下一个步骤中需要之后便可被清除。Python的垃圾回收机制依赖Python解释器的自动垃圾回收来管理这些临时对象的生命周期。当一个对象不再被任何引用指向时它就成为了垃圾回收的候选。物理差异体现在执行simple_chain.invoke()期间内存会暂时持有ChatPromptValue和AIMessage等对象。一旦这些对象完成其在数据流中的使命即其输出被下一个组件接收并且没有其他地方引用它们它们所占据的RAM空间就可能被释放。这意味着在整个链的执行过程中除了最终结果和必要的输入之外通常不会在内存中长期保留所有中间步骤的完整副本。b) 显式长期记忆 (Explicit Long-Term Memory)对于需要维护多轮对话上下文的应用LangChain提供了Memory组件。这些Memory对象独立于链的线性数据流但可以被链中的组件访问和更新。常见的Memory类型包括ConversationBufferMemory存储完整的对话历史。ConversationSummaryMemory存储对话摘要。ConversationBufferWindowMemory存储最近N轮对话。当一个Chain与Memory组件结合时Memory对象作为链的外部状态被维护。链在执行前可以从Memory中加载历史信息作为输入执行后可以将当前轮次的对话内容写入Memory。from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI from langchain_core.runnables import RunnablePassthrough from langchain.memory import ConversationBufferMemory # 1. 定义带历史的PromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一个友好的AI助手。), MessagesPlaceholder(variable_namehistory), # 占位符用于插入历史消息 (human, {input}) ]) # 2. 定义LLM llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.7) # 3. 定义Memory memory ConversationBufferMemory(return_messagesTrue) # 4. 组合成一个链 # 使用RunnablePassthrough来确保memory的输入和输出正确传递 conversation_chain ( RunnablePassthrough.assign( historylambda x: memory.load_memory_variables({})[history] # 从memory加载历史 ) | prompt | llm ) # 第一次对话 inputs {input: 你好我是Alice。} response conversation_chain.invoke(inputs) print(fAI: {response.content}) memory.save_context(inputs, {output: response.content}) # 保存当前对话到memory # 第二次对话 inputs {input: 你还记得我叫什么吗} response conversation_chain.invoke(inputs) print(fAI: {response.content}) memory.save_context(inputs, {output: response.content}) # 保存当前对话到memory print(n当前内存中的对话历史:) for msg in memory.load_memory_variables({})[history]: print(f{msg.type}: {msg.content})在这个例子中ConversationBufferMemory对象memory在链的外部被实例化。每次调用conversation_chain之前memory.load_memory_variables({})[history]会被调用将完整的对话历史从memory对象中读取出来作为prompt的一部分输入。每次调用conversation_chain之后memory.save_context()被调用将当前轮次的输入和输出添加到memory对象中。物理差异体现memory对象是一个独立于链执行流程的、持续存在的Python对象。它在整个应用生命周期内或至少在链的多次调用之间被引用。memory对象内部通常会维护一个消息列表list[BaseMessage]每轮对话结束后新的AIMessage和HumanMessage对象会被添加到这个列表中。这意味着随着对话轮次的增加这个list会不断增长其中包含的BaseMessage对象及其内部字符串内容会持续占用RAM空间。除非显式地对memory进行清理例如使用ConversationBufferWindowMemory限制窗口大小否则它将无限增长直接导致内存占用线性增加。3. 总结 LangChainChain的内存特征特征描述内存管理方式物理差异数据流线性数据从一个组件流向下一个组件。依赖Python的函数调用栈和参数传递。中间数据以临时对象形式存在于函数调用期间完成后可能被GC回收。中间状态大多数中间结果是瞬时且局部的仅在当前步骤和下一个步骤之间传递。依赖Python的垃圾回收机制。一旦对象不再被引用其占用的内存将被释放。内存占用波动在单个链执行过程中峰值内存由最重负荷的组件决定但不会长期保留所有中间步骤。长期记忆通过独立的Memory组件显式管理。Memory对象在链的外部实例化和维护并在每次链调用时被访问和更新。Memory对象自身作为一个持续存在的Python对象其内部维护的数据结构如消息列表会随着时间增长并持续占用RAM。Memory对象及其内部数据结构是内存增长的主要来源。它直接映射到RAM中的一块区域其大小与对话历史长度成正比直到被显式限制或清除。可恢复性链本身的执行状态不易在任意中间点恢复。通常只能恢复Memory组件的状态。恢复需要重新加载Memory对象并重新执行链。如果需要从中断处恢复LangChain通常需要重新运行整个链并从存储的Memory中重建上下文而不是恢复一个精确的执行快照。这可能意味着在恢复过程中重新产生一些中间计算的内存开销。LangGraph 的Graph状态机与显式共享状态LangGraph 是在 LangChain 基础上构建的它引入了图结构和状态机的概念专为复杂、多步骤、非线性的智能体应用而设计。其核心思想是整个应用的状态是显式的、共享的并且在不同的节点Node之间进行转换。1.Graph的基本架构节点、边与状态在LangGraph中状态State定义了一个应用在任何时刻的所有相关信息。它是一个Python字典或者一个继承自TypedDict的自定义类型。这个状态是所有节点共享的。节点Node图中的一个处理步骤。每个节点接收当前的图状态执行一些逻辑如调用LLM、使用工具并返回一个对状态的更新一个字典。边Edge连接节点定义了状态转换。边可以是条件性的根据节点返回的特定键值通常是表示“下一个动作”的字符串来决定下一个要执行的节点。考虑一个简单的智能体根据用户输入决定是调用工具还是直接回答。from typing import TypedDict, Annotated, List import operator from langchain_core.messages import BaseMessage, HumanMessage, AIMessage from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langgraph.graph import StateGraph, END # 1. 定义Graph State class AgentState(TypedDict): messages: Annotated[List[BaseMessage], operator.add] # 消息列表新消息追加 # 2. 定义工具 tool def get_current_weather(location: str) - str: 获取指定地点的当前天气。 if location 北京: return 北京晴朗气温25摄氏度。 else: return f无法获取{location}的天气信息。 # 3. 定义LLM并绑定工具 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) llm_with_tools llm.bind_tools([get_current_weather]) # 4. 定义节点函数 def call_model(state: AgentState): 调用LLM生成回复或工具调用。 messages state[messages] response llm_with_tools.invoke(messages) return {messages: [response]} # 返回对状态的更新 def call_tool(state: AgentState): 执行工具调用。 messages state[messages] last_message messages[-1] tool_calls last_message.tool_calls # 从LLM的响应中获取工具调用信息 tool_outputs [] for tool_call in tool_calls: tool_output globals()[tool_call.name].invoke(tool_call.args) # 调用工具 tool_outputs.append(tool_output) # 将工具输出作为新的AI消息添加到状态中 # 注意这里简化了工具输出的处理实际中可能需要更复杂的封装 return {messages: [AIMessage(contentstr(tool_outputs))]} # 5. 定义决策函数 def should_continue(state: AgentState): 根据LLM的响应决定下一步。 last_message state[messages][-1] if last_message.tool_calls: return call_tool # 如果LLM要求调用工具 else: return end # 如果LLM直接回复 # 6. 构建图 workflow StateGraph(AgentState) workflow.add_node(llm, call_model) workflow.add_node(tool, call_tool) # 设置入口点 workflow.set_entry_point(llm) # 定义边 workflow.add_conditional_edges( llm, # 从llm节点出发 should_continue, # 根据should_continue的返回值决定下一个节点 { call_tool: tool, # 如果返回call_tool则去tool节点 end: END # 如果返回end则结束 } ) workflow.add_edge(tool, llm) # 工具执行完后将工具输出作为新消息再次输入给LLM # 编译图 app workflow.compile() # 运行图 print(--- 第一次运行 (查询天气) ---) inputs1 {messages: [HumanMessage(content北京现在天气怎么样)]} for s in app.stream(inputs1): if __end__ not in s: print(s) print(app.get_state().values[messages][-1].content) # 打印最终回复 print(n--- 第二次运行 (简单问候) ---) inputs2 {messages: [HumanMessage(content你好)]} for s in app.stream(inputs2): if __end__ not in s: print(s) print(app.get_state().values[messages][-1].content) # 打印最终回复2. 内存管理显式共享状态与原子更新LangGraph的核心内存管理机制是围绕着一个显式、共享且可变的图状态Graph State。a) 显式共享状态 (Explicit Shared State)单一事实来源AgentState对象是整个图执行过程中所有相关信息的唯一真实来源。所有节点都通过这个对象来读取和写入数据。状态的传递当一个节点执行时它会收到当前AgentState的副本或引用取决于底层实现但逻辑上可视为副本。节点执行其逻辑后返回一个字典这个字典包含了对AgentState的“增量更新”或“修改”。状态的合并LangGraph会使用定义在AgentState上的Annotated类型如operator.add用于列表的追加或者operator.replace用于完全替换来将节点的更新合并到全局的AgentState中。物理差异体现AgentState对象是一个中央持久化数据结构。在图的整个执行生命周期中这个对象或其不断更新的版本始终存在于内存中。当一个节点返回{ messages: [new_message] }时LangGraph会根据Annotated[List[BaseMessage], operator.add]的定义将new_message对象追加到AgentState中已有的messages列表中。这意味着AgentState中的messages列表会像一个累积器一样不断地在内存中增长。所有的BaseMessage对象及其内容都会被持续引用直到整个图的执行结束或者直到某个节点显式地移除或替换了messages列表。与LangChain的Memory组件类似但AgentState的范围更广它不仅仅是对话历史可以是智能体所有的内部思绪、工具调用结果、决策路径等。b) 原子更新与检查点 (Atomic Updates Checkpointing)LangGraph设计的一个关键优势是其对状态的原子更新和检查点机制。原子更新节点返回的更新是原子性的。LangGraph确保这些更新能正确地合并到主状态中即使在并发或分布式环境中也能保持数据一致性。检查点Checkpointing由于整个图的状态都是显式的、单一的AgentState对象LangGraph可以非常容易地将这个完整的状态进行序列化例如存储到SQLite数据库和反序列化。这使得我们能够在任何节点执行后保存当前状态。从任何保存的状态点恢复执行。实现人机协作Human-in-the-Loop或长时间运行的智能体。from langgraph.checkpoint.sqlite import SqliteSaver from langgraph.graph import StateGraph, END # ... (AgentState, tools, llm, call_model, call_tool, should_continue, workflow 定义同上) ... # 使用SqliteSaver作为内存管理器支持检查点 memory_saver SqliteSaver.from_conn_string(:memory:) # 使用内存数据库进行演示 app_with_memory workflow.compile(checkpointermemory_saver) # 第一次运行 config {configurable: {thread_id: thread-1}} # 定义一个线程ID inputs1 {messages: [HumanMessage(content北京现在天气怎么样)]} print(--- 运行智能体 (第一次) ---) for s in app_with_memory.stream(inputs1, config): if __end__ not in s: print(s) final_state_1 app_with_memory.get_state(config) print(f最终状态 (thread-1, 运行1): {final_state_1.values[messages][-1].content}) # 第二次运行 (同一线程继续对话) inputs2 {messages: [HumanMessage(content那上海呢)]} print(n--- 运行智能体 (第二次同一线程) ---) for s in app_with_memory.stream(inputs2, config): if __end__ not in s: print(s) final_state_2 app_with_memory.get_state(config) print(f最终状态 (thread-1, 运行2): {final_state_2.values[messages][-1].content}) # 检查点中存储的完整历史 print(n--- 检查点中保存的完整对话历史 ---) # 注意get_state()会返回当前线程的最新状态 # 如果需要查看所有历史需要直接访问checkpointer的存储 # 这里我们通过两次get_state来展示状态的累积 print(f第一次运行后的messages长度: {len(final_state_1.values[messages])}) print(f第二次运行后的messages长度: {len(final_state_2.values[messages])})物理差异体现当使用checkpointer时每次状态更新后AgentState的完整当前版本都会被序列化并存储例如写入SQLite数据库文件或内存数据库。这意味着AgentState对象不仅仅是在RAM中累积数据它的完整内存快照还会在外部存储中被创建。这个序列化过程本身会消耗CPU和内存用于序列化/反序列化缓冲区。AgentState的大小直接影响序列化/反序列化的开销进而影响I/O性能和内存利用率。一个巨大的AgentState将导致频繁且昂贵的序列化操作即使在内存中也需要维护这个大对象。3. 总结 LangGraphGraph的内存特征| 特征 | 描述|物理存储|AgentState对象在内存中持续存在。如果配置了检查点其状态的完整快照会被序列化并写入外部存储。 LangGraph is to be very precise about how the state management interacts with memory. |内存管理上的本质物理差异现在让我们从物理层面更深入地对比 LangChain 的线性Chain与 LangGraph 的Graph在内存管理上的根本差异。1. 中间数据生命周期与内存分配LangChainChain:物理机制当Chain执行时数据从一个组件流向下一个。每个组件在其内部执行逻辑时会创建临时变量和对象。这些对象在组件完成其任务并将其输出传递给下一个组件后如果不再有任何其他强引用指向它们就成为了Python垃圾回收器Reference Counting Mark-and-Sweep的回收目标。内存分配模式类似函数调用栈上的局部变量。一个组件的输出是下一个组件的输入这通常意味着内存中会有短暂的、临时的对象复制或引用传递。例如prompt组件生成一个字符串或消息对象这会占用RAM。一旦llm组件接收并处理了它并且prompt组件的内部引用被清理那么之前生成的那个字符串或消息对象就有资格被回收。物理内存足迹在单个Chain执行的任何给定时刻内存中主要存在的是当前正在执行的组件的代码和数据。上一个组件的输出作为当前组件的输入。当前组件正在生成的输出。以及任何外部、显式配置的Memory对象如果存在。除了Memory对象大部分中间数据都是瞬时的不会在整个链执行过程中累积。LangGraphGraph:物理机制Graph的核心是一个AgentState对象它在整个图的执行过程中甚至跨越多个用户请求如果配置了检查点持续存在并被修改。每个节点接收对AgentState的引用执行逻辑然后返回一个字典代表对AgentState的增量更新。LangGraph的运行时环境负责将这些增量更新合并到主AgentState对象中。内存分配模式类似于一个全局的、可变的共享数据结构。AgentState本身是一个字典或TypedDict它在堆内存中被分配。当节点返回更新时这些更新会直接修改或扩展AgentState内部的数据结构。例如如果AgentState包含一个messages列表每次节点追加新消息时这个列表就会在堆内存中被扩展新消息对象被创建并添加到列表中。物理内存足迹AgentState对象是内存占用的核心。它的内存大小会随着图的执行而持续增长因为新的信息如LLM的响应、工具的输出、新的决策变量会被添加到其中并持续被引用。除非节点显式地从AgentState中移除旧数据否则所有历史信息都会累积在AgentState中。如果配置了检查点那么每次状态更新后AgentState的完整副本会被序列化例如到JSON或pickle格式并写入外部存储。这个序列化过程本身需要额外的RAM来缓冲数据。反序列化时也一样。2. 长期上下文与内存增长模式LangChainChain:长期上下文存储主要通过独立的Memory组件实现。Memory对象通常是一个Python类实例其内部维护着一个数据结构如list或dict来存储对话历史。内存增长模式无Memory每次调用Chain内存使用会有一个峰值然后回落。不同调用之间除了最终结果几乎没有内存残留。有MemoryMemory对象所占用的内存会随着对话轮次的增加而增长。例如ConversationBufferMemory会不断向其内部的messages列表中添加新的BaseMessage对象。这些BaseMessage对象及其内部的字符串内容如content会持续占用RAM。这种增长是线性的直到Memory被显式清理或限制如ConversationBufferWindowMemory。物理影响Memory对象是独立的内存块其增长直接对应于RAM中Python对象的创建和保留。如果Memory对象变得非常大每次加载和保存上下文时都需要复制或遍历大量数据可能导致性能下降和GC压力。LangGraphGraph:长期上下文存储AgentState就是长期上下文的载体。它不仅包含对话历史还可以包含任何对智能体决策和行为至关重要的信息。内存增长模式AgentState对象本身会随着每次节点执行并返回更新而持续增长。如果AgentState包含了类似messages列表的字段其增长模式与LangChain的ConversationBufferMemory类似也是线性的。关键区别在LangGraph中AgentState是唯一的、中心化的状态存储。所有需要跨节点、跨轮次保留的信息都必须放入AgentState中。这意味着所有需要长期保留的数据都会集中在一个大的Python对象中。物理影响AgentState的持续增长会导致一个单一的大型Python对象在RAM中占据越来越大的空间。访问效率访问AgentState中的数据可能比在LangChain中分散地访问多个Memory组件更高效因为数据集中。序列化/反序列化成本如果使用检查点巨大的AgentState会显著增加序列化和反序列化操作的CPU和内存开销。每次AgentState被存储或加载时都需要将其完整内容转换为字节流或从字节流恢复。3. 垃圾回收与对象生命周期LangChainChain:垃圾回收Python的引用计数和分代垃圾回收机制会相对有效地管理Chain中瞬时中间对象的生命周期。一旦一个组件的输出不再被引用它就成为垃圾回收的候选。这有助于保持内存使用在合理范围内避免不必要的累积。对象生命周期大多数中间对象是短命的它们的生命周期仅限于一到两个组件的执行。只有Memory对象及其内部数据具有较长的生命周期。LangGraphGraph:垃圾回收AgentState对象具有较长的生命周期它在整个图的执行过程中都被持续引用。Python的垃圾回收器不会自动清理AgentState内部的数据除非某个节点显式地修改了AgentState从而解除对旧数据的引用例如用一个新的、更小的列表替换掉一个旧的大列表。对象生命周期AgentState及其内部的所有数据例如messages列表中的BaseMessage对象都是长命的。它们会一直存在于内存中直到图的执行结束或者直到它们在AgentState中被显式地替换或删除。这意味着开发者需要更主动地管理AgentState的大小例如通过在节点中实现历史消息的裁剪或摘要。示例分析内存占用模拟为了更直观地理解我们来模拟一下两种机制下内存中关键数据结构的变化。假设每条消息占用 1KB 内存。LangChainConversationChain(使用ConversationBufferMemory)# 模拟 LangChain 内存增长 class MockBaseMessage: def __init__(self, content): self.content content self.size_kb len(content.encode(utf-8)) / 1024 0.1 # 模拟消息内容和对象开销 class MockConversationBufferMemory: def __init__(self): self.messages [] self.current_memory_usage_kb 0 def load_memory_variables(self): return {history: self.messages} def save_context(self, input_msg_content, output_msg_content): human_msg MockBaseMessage(input_msg_content) ai_msg MockBaseMessage(output_msg_content) self.messages.append(human_msg) self.messages.append(ai_msg) self.current_memory_usage_kb human_msg.size_kb ai_msg.size_kb print(f Memory save: Added {human_msg.size_kbai_msg.size_kb:.2f}KB. Total memory in Memory object: {self.current_memory_usage_kb:.2f}KB) # 模拟链执行 def simulate_langchain_conversation(num_turns): print(fn--- LangChain Conversation (模拟 {num_turns} 轮) ---) memory MockConversationBufferMemory() total_ephemeral_peak_kb 0 # 模拟瞬时内存峰值 for i in range(num_turns): input_content f用户输入 {i1} output_content fAI回复 {i1} # 模拟加载历史到当前执行的链中 loaded_history memory.load_memory_variables()[history] ephemeral_data_size_kb sum(msg.size_kb for msg in loaded_history) # 历史数据被复制或引用到当前执行上下文 # 模拟当前输入/输出对象的创建 current_input_obj_size MockBaseMessage(input_content).size_kb current_output_obj_size MockBaseMessage(output_content).size_kb # 瞬时内存峰值 加载的历史 当前输入/输出对象 (简化模拟) current_ephemeral_peak ephemeral_data_size_kb current_input_obj_size current_output_obj_size total_ephemeral_peak_kb max(total_ephemeral_peak_kb, current_ephemeral_peak) # 记录整个执行过程中的瞬时峰值 print(fTurn {i1}: Ephemeral peak for this turn: {current_ephemeral_peak:.2f}KB) memory.save_context(input_content, output_content) print(f最终 Memory 对象实际占用内存: {memory.current_memory_usage_kb:.2f}KB) print(fLangChain模拟瞬时中间数据在每次调用后理论上可被GC但Memory对象持续增长。) print(fLangChain模拟瞬时峰值内存 (不含Memory对象本身): {total_ephemeral_peak_kb:.2f}KB) simulate_langchain_conversation(3)LangGraphStateGraph(使用AgentState)# 模拟 LangGraph 内存增长 class MockAgentState(TypedDict): messages: Annotated[List[MockBaseMessage], operator.add] # 其他可能的AgentState字段... # LangGraph中的节点函数会接收并返回对AgentState的更新 def mock_node_execution(state: MockAgentState, input_content, output_content): print(f Node execution: Current state has {len(state[messages])} messages.) new_messages [ MockBaseMessage(input_content), MockBaseMessage(output_content) ] return {messages: new_messages} # 返回增量更新 # 模拟LangGraph执行 def simulate_langgraph_agent(num_turns): print(fn--- LangGraph Agent (模拟 {num_turns} 轮) ---) current_state MockAgentState(messages[]) # 初始状态 current_state_memory_kb 0 for i in range(num_turns): input_content f用户输入 {i1} output_content fAI回复 {i1} # 模拟节点执行和状态更新 updates mock_node_execution(current_state, input_content, output_content) # 模拟LangGraph运行时合并状态 for msg in updates[messages]: current_state[messages].append(msg) current_state_memory_kb msg.size_kb print(fTurn {i1}: AgentState total memory: {current_state_memory_kb:.2f}KB) print(f最终 AgentState 对象实际占用内存: {current_state_memory_kb:.2f}KB) print(fLangGraph模拟AgentState对象持续增长所有长期数据都在其中。) simulate_langgraph_agent(3)模拟结果分析LangChainMemory对象current_memory_usage_kb持续增长。每次调用链时ephemeral_data_size_kb会与memory对象的大小相关因为它需要将历史数据加载到当前执行上下文。但这个ephemeral_data_size_kb在每次调用结束后理论上是可以被垃圾回收的。核心是Memory对象本身的累积。LangGraphAgentState对象current_state_memory_kb持续增长。它直接包含了所有长期数据。每次节点执行都是在修改或扩展这个中心化的AgentState。没有显式的“加载历史”到临时变量的过程因为历史直接就是AgentState的一部分。这个模拟清晰地展示了LangChain的Memory和LangGraph的AgentState都代表了长期存在的内存区域它们会随着交互轮次而增长。LangChain的中间数据非Memory部分更倾向于瞬时性在每次链调用后可被回收。LangGraph的AgentState是所有长期和中间状态的唯一集中地其内部数据会被持续引用。性能与优化考量理解这些内存差异能帮助我们更好地进行性能优化对于 LangChainChain:优点简单线性流程的内存开销较低瞬时中间数据能够被高效回收。对于只需要短期上下文或简单对话历史的应用其内存管理模型非常轻量。缺点Memory对象如果无限增长会成为内存瓶颈。每次加载和保存Memory上下文的开销也会增加。优化策略使用ConversationBufferWindowMemory或ConversationSummaryMemory来限制Memory的大小。对于不需要上下文的独立请求不使用Memory组件。利用Python的生成器和流式传输streaming来减少内存中一次性持有的数据量尤其是对于大型LLM响应。对于 LangGraphGraph:优点显式的AgentState使得对整个应用状态的推理和管理变得容易。集中式的状态管理非常适合复杂的智能体、人机协作和检查点恢复。缺点AgentState的持续增长可能导致内存占用过高。如果AgentState变得非常大序列化/反序列化用于检查点的开销会变得显著。开发者需要主动管理AgentState的大小。优化策略裁剪AgentState在节点中实现逻辑定期清理或摘要AgentState中不再需要的数据例如旧的对话消息、临时的工具执行结果。延迟加载/部分状态对于某些非常大的字段考虑在需要时才从外部存储加载而不是始终保留在AgentState中。选择高效的序列化格式对于检查点选择效率更高的序列化格式如msgpack或自定义二进制格式而非默认的pickle或JSON。限制检查点频率如果不需要每一步都进行检查点可以减少检查点的频率。总结性思考LangChain的线性Chain与LangGraph的Graph在内存管理上的本质物理差异根植于它们对应用“状态”的不同抽象。LangChain的Chain更侧重于数据流其核心是组件间的输入输出传递。中间数据大多是瞬时的依赖Python的垃圾回收机制。长期上下文通过独立的Memory对象管理它是一个在链外部持续存在的、累积性的内存结构。LangGraph的Graph则围绕着一个显式、共享的AgentState进行状态转换。AgentState是所有长期和中间状态的单一事实来源它在整个图的执行过程中持续存在并被修改。这意味着所有需要保留的信息都集中在一个大的Python对象中其大小会随着执行而累积并且在进行检查点时需要对这个完整状态进行序列化。理解这一核心差异能够帮助我们根据应用的复杂性、内存需求和持久化策略明智地选择合适的工具并实施有效的内存优化。对于简单的、一次性的LLM调用或线性流程LangChain的Chain提供了轻量而高效的解决方案。而对于需要复杂决策、多步骤交互、容错恢复以及精细状态管理的智能体LangGraph的Graph则提供了更强大、更具弹性的架构但同时也要求开发者对AgentState的生命周期和内存占用有更深入的理解和管理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询