2026/1/19 1:58:15
网站建设
项目流程
达内培训网站开发,静态网站培训,电商平台如何宣传,最近一周新闻热点大事件本文详细介绍了LangGraph的持久化机制#xff0c;通过Thread和Checkpoint概念#xff0c;使AI智能体具备记忆能力。持久化机制支持多轮对话、状态恢复、人工介入和时间旅行等场景#xff0c;提供了InMemorySaver、SqliteSaver、PostgresSaver和RedisSaver等多种实现方式。理…本文详细介绍了LangGraph的持久化机制通过Thread和Checkpoint概念使AI智能体具备记忆能力。持久化机制支持多轮对话、状态恢复、人工介入和时间旅行等场景提供了InMemorySaver、SqliteSaver、PostgresSaver和RedisSaver等多种实现方式。理解这些核心概念对于构建高质量AI智能体应用至关重要。本文为 LangGranph 系列的第三篇介绍 LangGraph 的持久化机制看看它是如何让 AI 智能体拥有记忆能力的主要内容如下AI智能体为什么需要持久化LangGraph 的持久化机制的相关概念介绍持久化实现的编程示例持久化机制的应用场景为什么需要持久化在实际的 AI 智能体应用中经常会遇到这样的场景多轮对话用户问我的名字是什么智能体需要记住之前用户说过的话状态恢复智能体执行到一半出错了需要从上次成功的地方继续时间旅行想要回看智能体之前的执行过程用于调试和分析人工介入在关键步骤需要人工审核和干预这些场景都离不开一个核心能力状态持久化。LangGraph 通过一套完整的持久化机制让这些需求成为可能。理解 LangGraph 的基础概念在深入持久化机制之前需要先理解几个核心概念这些概念是理解持久化机制的基础。Graph图在 LangGraph 中Graph 是一个有向图结构由节点Node和边Edge组成。每个节点代表一个处理单元边定义了节点之间的执行顺序。from langgraph.graph import StateGraph, START, END# 创建一个图builder StateGraph(State)# 添加节点builder.add_node(node_a, function_a)builder.add_node(node_b, function_b)# 添加边定义执行流程builder.add_edge(START, node_a) # 从开始到节点 Abuilder.add_edge(node_a, node_b) # 从节点 A 到节点 Bbuilder.add_edge(node_b, END) # 从节点 B 到结束# 编译图graph builder.compile()图定义了智能体的执行流程就像一个工作流引擎按照预定义的路径执行各个节点。Super-steps超级步骤Super-step 是 LangGraph 执行图的一个关键概念。理解 super-step 需要区分两种情况情况一简单线性图一个 super-step 一个节点在简单的线性图中每个 super-step 通常只执行一个节点# 简单的线性图START - node_a - node_b - ENDbuilder.add_edge(START, node_a)builder.add_edge(node_a, node_b)builder.add_edge(node_b, END)# 执行过程# Super-step 1: 执行 node_a保存 checkpoint_1# Super-step 2: 执行 node_b保存 checkpoint_2这种情况下一个 super-step 确实对应一个节点的执行。情况二复杂图一个 super-step 多个并行节点在复杂的图中如果多个节点没有依赖关系它们可以在同一个 super-step 中并行执行# 复杂图START - node_a - [node_b, node_c] - node_d - ENDbuilder.add_edge(START, node_a)builder.add_edge(node_a, node_b) # node_b 依赖 node_abuilder.add_edge(node_a, node_c) # node_c 也依赖 node_abuilder.add_edge(node_b, node_d) # node_d 依赖 node_bbuilder.add_edge(node_c, node_d) # node_d 也依赖 node_c# 执行过程# Super-step 1: 执行 node_a保存 checkpoint_1# Super-step 2: 并行执行 node_b 和 node_c它们都依赖 node_a但彼此独立保存 checkpoint_2# Super-step 3: 执行 node_d等待 node_b 和 node_c 都完成保存 checkpoint_3这种情况下Super-step 2 包含了两个节点的并行执行。Super-step 的执行过程无论哪种情况每个 super-step 都包含以下过程读取当前状态从上一个 checkpoint 恢复状态确定可执行节点找出所有依赖已满足、可以执行的节点执行节点执行这些节点可能是一个也可能是多个并行合并状态将各个节点的输出合并到状态中保存 checkpoint将当前状态保存为新的 checkpoint关键理解Super-step 不是按节点数量定义的而是按所有依赖都满足后执行所有准备好的节点这个逻辑定义的。在简单情况下可能只有一个节点准备好在复杂情况下可能有多个节点可以并行执行。每个 super-step 都会自动保存一个 checkpoint这就是持久化机制的基础。即使程序中断也能从最后一个 super-step 的 checkpoint 恢复执行。StateSnapshot状态快照StateSnapshot 是 checkpoint 的具体表示形式它完整记录了图在某个时间点的状态信息。每个 StateSnapshot 包含以下关键属性values状态通道的当前值这是图的核心数据next下一步要执行的节点名称元组config配置信息包括 thread_id 和 checkpoint_idmetadata元数据记录执行信息如步骤号、写入的节点等created_atcheckpoint 的创建时间parent_config父 checkpoint 的配置信息tasks待执行的任务信息# 获取一个 StateSnapshot 示例snapshot graph.get_state({configurable: {thread_id: 1}})print(snapshot.values) # {messages: [...], foo: bar}print(snapshot.next) # (node_a, node_b) 或 () 表示执行完成print(snapshot.metadata) # {step: 2, writes: {...}, source: loop}print(snapshot.config) # {configurable: {thread_id: 1, ...}}StateSnapshot 不仅保存了状态数据还保存了执行上下文这使得时间旅行和状态恢复成为可能。三者之间的关系这三个概念紧密相关图定义了执行流程super-step 是执行单位StateSnapshot 记录状态checkpoint 将状态持久化保存。核心概念Thread 和 Checkpoint理解了基础概念后现在来看持久化机制的两个核心概念Thread线程Thread 可以理解为一个对话会话的唯一标识符。每次调用图时需要指定一个thread_id所有相关的状态都会被保存到这个线程中。config {configurable: {thread_id: conversation_1}}graph.invoke(input_data, config)不同的thread_id对应不同的会话它们之间互不干扰。这样就能同时处理多个用户的对话每个用户都有自己独立的对话历史。Checkpoint检查点Checkpoint 是图在某个时间点的状态快照。LangGraph 会在每个 super-step超级步骤自动保存一个 checkpoint记录下当前的状态值、下一步要执行的节点、元数据等信息。一个简单的图执行过程可能会产生多个 checkpoint1. 初始 checkpoint空状态准备执行 START 节点2. 用户输入后的 checkpoint包含输入数据3. 节点 A 执行后的 checkpoint包含节点 A 的输出4. 节点 B 执行后的 checkpoint包含节点 B 的输出5. 最终 checkpoint执行完成每个 checkpoint 都是一个完整的StateSnapshot对象包含values当前状态的值next下一步要执行的节点metadata元数据信息config配置信息created_at创建时间编程示例让我们通过一个完整的例子来理解持久化机制是如何工作的。基础示例让智能体记住对话首先创建一个简单的对话智能体from langchain.chat_models import init_chat_modelfrom langgraph.checkpoint.memory import InMemorySaverfrom langgraph.graph import StateGraph, MessagesState, STARTimport os# 初始化模型llm init_chat_model( modelqwen-plus, model_provideropenai, api_keyos.getenv(DASHSCOPE_API_KEY), base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1)# 定义节点函数def call_model(state: MessagesState): response llm.invoke(state[messages]) return {messages: response}# 创建 checkpointercheckpointer InMemorySaver()# 构建图builder StateGraph(MessagesState)builder.add_node(agent, call_model)builder.add_edge(START, agent)# 编译图时传入 checkpointergraph builder.compile(checkpointercheckpointer)关键点在于最后一行builder.compile(checkpointercheckpointer)。这行代码启用了持久化机制。现在来测试一下记忆功能# 第一次对话config {configurable: {thread_id: user_123}}response1 graph.invoke( {messages: [{role: user, content: 你好我的名字是张三}]}, config)print(fAI: {response1[messages][-1].content})# 第二次对话相同 thread_idresponse2 graph.invoke( {messages: [{role: user, content: 我的名字是什么}]}, config # 使用相同的 thread_id)print(fAI: {response2[messages][-1].content})运行这段代码会发现第二次调用时AI 能够准确回答你的名字是张三。这就是持久化机制在起作用第二次调用时checkpointer 自动恢复了第一次对话的历史记录。查看状态历史持久化机制还提供了查看历史状态的能力# 获取最新状态current_state graph.get_state(config)print(f当前消息数: {len(current_state.values[messages])})# 获取完整历史history list(graph.get_state_history(config))print(f总共有 {len(history)} 个 checkpoint)# 遍历历史记录for i, snapshot in enumerate(history): print(f\nCheckpoint {i}:) print(f 时间: {snapshot.created_at}) print(f 消息数: {len(snapshot.values[messages])}) print(f 下一步节点: {snapshot.next})这个功能在调试时非常有用可以清楚地看到图执行的每一步。Checkpointer 的实现方式LangGraph 提供了多种 checkpointer 实现适用于不同的场景 InMemorySaver内存存储适合开发和测试数据保存在内存中程序重启后丢失from langgraph.checkpoint.memory import InMemorySavercheckpointer InMemorySaver()graph builder.compile(checkpointercheckpointer) SqliteSaverSQLite 数据库适合本地开发和实验数据持久化到 SQLite 文件from langgraph.checkpoint.sqlite import SqliteSaverimport sqlite3conn sqlite3.connect(checkpoints.db)checkpointer SqliteSaver(conn)checkpointer.setup() # 首次使用需要初始化graph builder.compile(checkpointercheckpointer)️ PostgresSaverPostgreSQL 数据库适合生产环境性能好支持并发from langgraph.checkpoint.postgres import PostgresSaverDB_URI postgresql://user:passwordlocalhost:5432/dbnamecheckpointer PostgresSaver.from_conn_string(DB_URI)checkpointer.setup() # 首次使用需要初始化graph builder.compile(checkpointercheckpointer) RedisSaverRedis 存储适合需要高性能缓存的场景from langgraph.checkpoint.redis import RedisSaverDB_URI redis://localhost:6379checkpointer RedisSaver.from_conn_string(DB_URI)checkpointer.setup()graph builder.compile(checkpointercheckpointer)持久化带来的强大能力应用场景持久化机制不仅仅是保存状态那么简单它开启了四个强大的能力 Memory记忆这是最直观的应用。通过 thread_id智能体可以记住之前的对话内容实现真正的多轮对话。# 第一次对话graph.invoke( {messages: [{role: user, content: 我喜欢 Python}]}, {configurable: {thread_id: chat_1}})# 第二次对话智能体记得之前的信息graph.invoke( {messages: [{role: user, content: 我刚才说了我喜欢什么}]}, {configurable: {thread_id: chat_1}}) Human-in-the-loop人工介入持久化让人工介入成为可能。可以在关键步骤暂停执行等待人工审核。但这里有一个重要的机制需要理解状态更新与 reducer 函数的关系。理解 update_state 的行为当使用update_state更新状态时LangGraph 会采用与节点更新相同的处理方式关键规则有 reducer 的通道更新值会传递给 reducer 函数处理不会直接覆盖而是通过 reducer 函数合并例如messages: Annotated[list, add_messages]会追加消息而不是替换没有 reducer 的通道更新值会直接覆盖直接替换原有的值这意味着update_state不会自动覆盖所有通道的值而是根据每个通道是否定义了 reducer 来决定处理方式。实际例子from typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph.message import add_messages# 定义状态包含有 reducer 和没有 reducer 的通道class State(TypedDict): messages: Annotated[list, add_messages] # 有 reducer追加 counter: int # 没有 reducer直接覆盖 user_name: str # 没有 reducer直接覆盖# 假设当前状态current_state { messages: [{role: user, content: Hello}], counter: 5, user_name: Alice}# 使用 update_state 更新graph.update_state( config, { messages: [{role: assistant, content: Hi}], # 会追加不会替换 counter: 10, # 会直接覆盖为 10 user_name: Bob# 会直接覆盖为 Bob })# 更新后的状态# {# messages: [# {role: user, content: Hello}, # 保留原有# {role: assistant, content: Hi} # 追加新的# ],# counter: 10, # 被覆盖# user_name: Bob # 被覆盖# }理解 as_node 参数控制执行流程update_state还有一个重要的可选参数as_node它用于控制下一步要执行的节点。关键机制下一步执行哪个节点取决于最后一个更新状态的节点图的边edges定义了从某个节点到其他节点的路径通过as_node参数可以伪装更新来自某个特定节点从而控制执行流程行为规则如果提供了as_node更新会被当作来自该节点的更新如果没有提供as_node会使用最后一个更新状态的节点如果不模糊如果无法确定模糊情况需要显式指定as_node实际例子控制执行路径1情况 A条件边Conditional Edge# 假设图结构如下条件边# START - node_a - [条件判断] - node_b 或 node_c# node_a 有条件边根据条件选择 node_b 或 node_c# 场景 1不指定 as_node使用默认行为state graph.get_state(config)graph.update_state(config, {counter: 10})# 下一步会从最后一个更新状态的节点继续执行# 场景 2指定 as_node控制执行路径graph.update_state( config, {counter: 10}, as_nodenode_a# 指定更新来自 node_a)# 下一步会从 node_a 的条件边继续执行根据条件选择 node_b 或 node_c只执行其中一个2情况 B多条普通边并行执行# 假设图结构如下多条普通边# START - node_a - node_b# - node_c# node_a 有两条普通边分别指向 node_b 和 node_c# 指定 as_node 为 node_agraph.update_state( config, {counter: 10}, as_nodenode_a # 指定更新来自 node_a)# 下一步会从 node_a 的边继续执行# 由于 node_b 和 node_c 都依赖 node_a且彼此独立它们会在同一个 super-step 中并行执行从特定节点重新开始# 从特定节点重新开始执行graph.update_state( config, {messages: [{role: user, content: 重新开始}]}, as_nodenode_b # 指定更新来自 node_b)# 下一步会从 node_b 的边继续执行关键区别条件边根据条件选择其中一个节点执行不并行多条普通边如果节点之间没有依赖关系会在同一个 super-step 中并行执行上述的机制在以下场景非常有用场景一人工修正后改变执行路径# 1. 获取当前状态state graph.get_state(config)print(f当前在节点: {state.next})# 2. 人工审核发现需要改变执行路径# 假设原本应该执行 node_b但人工决定改为执行 node_c# 3. 更新状态并指定 as_node 为 node_a分支节点graph.update_state( config, {decision: go_to_c}, # 修改决策 as_nodenode_a# 从 node_a 重新路由)# 4. 继续执行现在会从 node_a 根据新的状态路由到 node_cgraph.invoke(updated_input, config)场景二从特定节点重新执行# 从某个中间节点重新开始执行graph.update_state( config, {messages: [{role: user, content: 重新处理}]}, as_nodeagent # 从 agent 节点重新开始)# 下一步会从 agent 节点的边继续执行场景三创建执行分支Fork# 从某个 checkpoint 创建新分支old_state graph.get_state({ configurable: { thread_id: thread_1, checkpoint_id: checkpoint_2_id }})# 更新状态指定从特定节点开始graph.update_state( {configurable: {thread_id: thread_1_fork}}, old_state.values, as_nodenode_a # 从 node_a 开始新分支)人工介入的完整流程# 1. 获取当前状态state graph.get_state(config)print(f当前状态: {state.values})print(f下一步节点: {state.next})# 2. 人工审核和修改updates { messages: [{role: user, content: 修正后的消息}], counter: 0,}# 3. 更新状态并控制执行路径graph.update_state( config, updates, as_nodeagent# 指定从 agent 节点继续执行)# 4. 从检查点继续执行# 现在会从 agent 节点的边继续执行graph.invoke(updated_input, config)为什么这样设计这种设计的好处一致性update_state和节点更新使用相同的规则保证行为一致️安全性有 reducer 的通道不会被意外覆盖保护累积的数据灵活性可以根据需要选择直接覆盖或通过 reducer 合并理解这个机制对于正确使用人工介入功能非常重要。特别是在修改包含 reducer 的通道时需要明确知道是追加还是替换。⏰ Time Travel时间旅行可以回放之前的执行过程这对于调试和分析非常有用。但这里有一个重要的机制需要理解checkpoint 回放的分支行为。理解 Checkpoint 回放机制当从某个checkpoint_id回放执行时LangGraph 会采用智能的回放策略关键规则checkpoint_id 之前的步骤只重放re-play不重新执行LangGraph 知道这些步骤已经执行过直接使用已保存的 checkpoint 数据不会重新运行节点函数这样可以节省计算资源避免重复执行checkpoint_id 之后的步骤会重新执行创建新分支即使这些步骤之前执行过也会重新运行这相当于从该 checkpoint 创建了一个新的执行分支fork允许探索不同的执行路径# 假设执行历史如下# Step 0: START - checkpoint_0# Step 1: node_a - checkpoint_1# Step 2: node_b - checkpoint_2# Step 3: node_c - checkpoint_3# 从 checkpoint_1 回放config { configurable: { thread_id: thread_1, checkpoint_id: checkpoint_1_id# 指定回放起点 }}# 执行结果# - checkpoint_0 和 checkpoint_1直接使用已保存的数据不重新执行# - checkpoint_1 之后的步骤重新执行 node_b 和 node_c创建新分支graph.invoke(new_input, config)实际应用场景这个机制在以下场景非常有用场景一探索不同的执行路径# 第一次执行config {configurable: {thread_id: explore_1}}result1 graph.invoke(input_data, config)# 获取历史找到关键决策点history list(graph.get_state_history(config))decision_point history[2] # 假设这是某个决策节点# 从决策点重新执行尝试不同的输入config_fork { configurable: { thread_id: explore_1, checkpoint_id: decision_point.config[configurable][checkpoint_id] }}result2 graph.invoke(different_input, config_fork) # 创建新分支场景二调试和分析# 查看某个时间点的状态history list(graph.get_state_history(config))old_state graph.get_state({ configurable: { thread_id: chat_1, checkpoint_id: history[2].config[configurable][checkpoint_id] }})# 分析该时间点的状态print(fStep {old_state.metadata.get(step, 0)} 的状态:)print(f 值: {old_state.values})print(f 下一步: {old_state.next})# 如果需要可以从这个点重新执行graph.invoke(modified_input, { configurable: { thread_id: chat_1, checkpoint_id: history[2].config[configurable][checkpoint_id] }})场景三错误恢复和重试# 执行过程中出错try: graph.invoke(input_data, config)except Exception as e: # 获取最后一个成功的 checkpoint last_state graph.get_state(config) # 从该 checkpoint 重新执行只重新执行之后的步骤 fixed_input fix_input(input_data) graph.invoke(fixed_input, { configurable: { thread_id: config[configurable][thread_id], checkpoint_id: last_state.config[configurable][checkpoint_id] } })性能优势这种设计带来的好处⚡节省计算已执行的步骤不会重复运行直接使用保存的结果灵活分支可以从任意 checkpoint 创建新的执行路径便于调试可以精确控制从哪个点开始重新执行状态一致确保回放时的状态与原始执行时完全一致理解这个机制对于充分利用 LangGraph 的时间旅行功能非常重要。️ Fault-tolerance容错当节点执行失败时可以从最后一个成功的 checkpoint 恢复不会丢失已完成的工作try: graph.invoke(input_data, config)except Exception as e: # 获取最后一个成功的状态 last_state graph.get_state(config) # 从失败的地方重新开始 graph.invoke(updated_input, config)实际应用场景在实际项目中持久化机制的应用场景非常广泛场景一多用户对话系统每个用户分配一个唯一的 thread_id实现用户之间的对话隔离def handle_user_message(user_id, message): config {configurable: {thread_id: fuser_{user_id}}} response graph.invoke( {messages: [{role: user, content: message}]}, config ) return response场景二长时间运行的智能体对于需要长时间运行的智能体任务持久化可以确保即使程序重启也能从上次中断的地方继续# 第一次执行config {configurable: {thread_id: long_task_1}}graph.invoke(initial_input, config)# ... 程序重启 ...# 恢复执行last_state graph.get_state(config)graph.invoke(continue_input, config)场景三调试和分析通过查看 checkpoint 历史可以分析智能体的决策过程def analyze_agent_behavior(thread_id): config {configurable: {thread_id: thread_id}} history list(graph.get_state_history(config)) for snapshot in history: print(fStep {snapshot.metadata.get(step, 0)}:) print(f State: {snapshot.values}) print(f Next nodes: {snapshot.next}) print(f Writes: {snapshot.metadata.get(writes, {})})性能考虑在使用持久化机制时需要注意一些性能问题⚡Checkpoint 频率每个 super-step 都会保存 checkpoint对于高频调用的场景需要考虑存储成本存储选择生产环境建议使用 PostgreSQL 或 Redis而不是内存存储历史清理定期清理旧的 checkpoint避免存储空间无限增长# 删除某个线程的所有 checkpointcheckpointer.delete_thread(old_thread_id)总结LangGraph 的持久化机制通过 checkpointer 和 thread 的概念为 AI 智能体提供了强大的状态管理能力。它不仅解决了多轮对话的问题还开启了人工介入、时间旅行、容错等高级功能。在实际开发中选择合适的 checkpointer 实现很重要开发测试使用InMemorySaver本地部署使用SqliteSaver生产环境使用PostgresSaver或RedisSaver理解持久化机制是构建高质量 AI 智能体应用的关键一步。希望这篇文章能帮助大家更好地理解和使用 LangGraph 的持久化功能。最后我在一线科技企业深耕十二载见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事早已在效率与薪资上形成代际优势我意识到有很多经验和知识值得分享给大家也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我整理出这套 AI 大模型突围资料包✅AI大模型学习路线图✅Agent行业报告✅100集大模型视频教程✅大模型书籍PDF✅DeepSeek教程✅AI产品经理入门资料完整的大模型学习和面试资料已经上传带到CSDN的官方了有需要的朋友可以扫描下方二维码免费领取【保证100%免费】为什么说现在普通人就业/升职加薪的首选是AI大模型人工智能技术的爆发式增长正以不可逆转之势重塑就业市场版图。从DeepSeek等国产大模型引发的科技圈热议到全国两会关于AI产业发展的政策聚焦再到招聘会上排起的长队AI的热度已从技术领域渗透到就业市场的每一个角落。智联招聘的最新数据给出了最直观的印证2025年2月AI领域求职人数同比增幅突破200%远超其他行业平均水平整个人工智能行业的求职增速达到33.4%位居各行业榜首其中人工智能工程师岗位的求职热度更是飙升69.6%。AI产业的快速扩张也让人才供需矛盾愈发突出。麦肯锡报告明确预测到2030年中国AI专业人才需求将达600万人人才缺口可能高达400万人这一缺口不仅存在于核心技术领域更蔓延至产业应用的各个环节。资料包有什么①从入门到精通的全套视频教程⑤⑥包含提示词工程、RAG、Agent等技术点② AI大模型学习路线图还有视频解说全过程AI大模型学习路线③学习电子书籍和技术文档市面上的大模型书籍确实太多了这些是我精选出来的④各大厂大模型面试题目详解⑤ 这些资料真的有用吗?这份资料由我和鲁为民博士共同整理鲁为民博士先后获得了北京清华大学学士和美国加州理工学院博士学位在包括IEEE Transactions等学术期刊和诸多国际会议上发表了超过50篇学术论文、取得了多项美国和中国发明专利同时还斩获了吴文俊人工智能科学技术奖。目前我正在和鲁博士共同进行人工智能的研究。所有的视频教程由智泊AI老师录制且资料与智泊AI共享相互补充。这份学习大礼包应该算是现在最全面的大模型学习资料了。资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。智泊AI始终秉持着“让每个人平等享受到优质教育资源”的育人理念通过动态追踪大模型开发、数据标注伦理等前沿技术趋势构建起前沿课程智能实训精准就业的高效培养体系。课堂上不光教理论还带着学员做了十多个真实项目。学员要亲自上手搞数据清洗、模型调优这些硬核操作把课本知识变成真本事如果说你是以下人群中的其中一类都可以来智泊AI学习人工智能找到高薪工作一次小小的“投资”换来的是终身受益应届毕业生无工作经验但想要系统学习AI大模型技术期待通过实战项目掌握核心技术。零基础转型非技术背景但关注AI应用场景计划通过低代码工具实现“AI行业”跨界。业务赋能 突破瓶颈传统开发者Java/前端等学习Transformer架构与LangChain框架向AI全栈工程师转型。获取方式有需要的小伙伴可以保存图片到wx扫描二v码免费领取【保证100%免费】**