第3周学习笔记

学习时间 :2026年6月(5天) | 核心主题:LangGraph 图编排 → 复杂工作流 → 多智能体协作 → 生产部署 → 性能优化


一、本周学习路线总览

本周以"从单 Agent 的 ReAct 循环到构建生产级、有状态、多智能体协作系统"为主线,按以下路径递进:

复制代码
LangGraph基础图模型 → 复杂工作流与状态管理 → 多智能体协作 → 生产级部署 → 性能优化与监控
     Day1                Day2                Day3          Day4          Day5

如果说第 1 周是学会"如何用 LCEL 搭积木",第 2 周是学会"如何让模型接入外部世界(RAG + Agent)",那么第 3 周的核心命题是------如何让 Agent 从玩具进化为可部署、可观测、可协作的生产级系统。LangGraph 提供显式的图编排能力让你精确控制每一步,状态持久化让 Agent 拥有跨会话记忆,多智能体架构让复杂任务被拆解为多个专业子 Agent 并行协作,MCP 协议让工具跨框架复用,而性能优化与成本监控则是上线的最后一道关卡。


二、Day 1:LangGraph 入门------从黑盒到透明

2.1 为什么需要 LangGraph:create_agent() 的边界

第 2 周我们使用 create_agent() 一行代码就构建出了能调用工具的智能助手。这种简洁性是高层 API 的优势------它隐藏了 ReAct 循环、工具调用解析、消息管理等复杂细节。但当你需要构建的不是"提问→思考→回答"的线性流程,而是多步骤、有条件分支、需要人工审批、甚至要在失败后从断点恢复的复杂工作流时,这种黑盒抽象就成了桎梏。

LangGraph 不是另一个更高层的 Agent 封装,而是一个低层的、透明的编排框架。它把 Agent 工作流建模为一个有向图:节点负责执行具体逻辑(调用 LLM、执行工具、进行判断),边负责决定下一步走向哪里。这种设计让你可以用 Python 函数定义节点行为,再用简单的图构建 API 把它们串联起来,最终得到一个完全可控、可观测、可持久化的状态机。

重要信号 :在 2026 年的 LangChain 生态中,LangGraph 已经从可选扩展成长为框架的核心基础设施,官方推荐的 create_agent() 底层正是基于 LangGraph 构建的。理解底层之后,高层 API 的种种行为你将了然于胸。

2.2 四大核心概念:StateGraph、State、Node、Edge

LangGraph 的世界由四个核心概念构成:

StateGraph 是主要的图类,接收一个用户自定义的状态 Schema 作为类型参数。所有节点和边的读写都围绕这个共享状态进行。状态 Schema 通常使用 TypedDict 定义,也可用 Pydantic BaseModel 获得递归校验能力(性能略低于 TypedDict)。

State(状态) 的每个字段都有独立的 reducer 函数,决定节点返回的更新如何与现有状态合并。默认行为是覆盖,但通过 Annotated 类型可以指定自定义 reducer。由于消息列表是最高频的状态形式,LangGraph 提供了预置的 MessagesState,内部使用 add_messages 作为 reducer------新消息追加到列表,已存在消息(通过 ID 匹配)原地更新,同时自动将字典格式消息反序列化为 LangChain 消息对象:

python 复制代码
from langgraph.graph import MessagesState

class State(MessagesState):
    documents: list[str]    # 追加自定义字段,自动获得覆盖式 reducer
    llm_calls: int          # 而 messages 字段继承父类的 add_messages reducer

Node(节点) 是图中执行实际工作的 Python 函数。接收当前状态作为第一个参数,返回一个状态更新字典(只需返回需要更新的字段,不需返回完整状态)。在底层节点函数被转换为 RunnableLambda,自动获得批处理和异步支持。

Edge(边) 定义了路由关系,分三种:

  • 普通边 add_edge("A", "B"):固定路径,A 执行完无条件进入 B
  • 条件边 add_conditional_edges("A", routing_fn):动态路由,routing_fn 接收状态返回目标节点名
  • 特殊节点 STARTEND:分别标记图的入口和终止

关键约束 :每个节点只能选择一种出边机制(普通边、条件边或 Command),不要混用,否则多条路径可能同时被激活。

2.3 手写 ReAct 循环:LangGraph 版的 Agent

这是 Day 1 的核心实践------亲手用图 API 构建一个 ReAct Agent,理解 create_agent() 底层到底做了什么。

第一步:定义工具和模型

python 复制代码
from langchain.tools import tool
from langchain.chat_models import init_chat_model

model = init_chat_model("Qwen/Qwen2.5-7B-Instruct", model_provider="openai")

@tool
def multiply(a: int, b: int) -> int:
    """Multiply a and b."""
    return a * b

@tool
def add(a: int, b: int) -> int:
    """Add a and b."""
    return a + b

tools = [add, multiply]
tools_by_name = {tool.name: tool for tool in tools}  # 工具注册表
model_with_tools = model.bind_tools(tools)            # 绑定工具到模型

tools_by_name 字典是工具执行节点中按名称查找工具的桥梁------模型返回的 tool_call 只包含名称和参数,不包含工具对象本身。

第二步:定义两个核心节点

python 复制代码
from langchain.messages import SystemMessage, ToolMessage

def llm_call(state: dict):
    """LLM 决定是否调用工具"""
    return {
        "messages": [
            model_with_tools.invoke(
                [SystemMessage(content="You are a helpful assistant.")]
                + state["messages"]
            )
        ],
        "llm_calls": state.get("llm_calls", 0) + 1
    }

def tool_node(state: dict):
    """执行工具调用"""
    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result}

关键细节:

  • 系统提示放在消息列表最前面,确保模型每次推理都看到系统指令
  • tool_nodefor 循环处理------模型可以一次请求并行调用多个工具
  • ToolMessagetool_call_id 必须与原始 tool_call 的 id 对应,否则模型无法匹配

第三步:条件路由------ReAct 的心脏

python 复制代码
from typing import Literal
from langgraph.graph import StateGraph, START, END

def should_continue(state: MessagesState) -> Literal["tool_node", END]:
    """检查最后一条消息是否包含工具调用"""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tool_node"
    return END

这个函数虽短,但承载了 ReAct 循环的核心决策:tool_calls 非空 → 继续执行工具;为空 → 模型已给出最终答案,终止循环。

第四步:构建和编译图

python 复制代码
agent_builder = StateGraph(MessagesState)
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges("llm_call", should_continue, {"tool_node": "tool_node", END: END})
agent_builder.add_edge("tool_node", "llm_call")  # 循环回路
agent = agent_builder.compile()

图的结构:START → llm_call → [tool_node → llm_call → ...] → ENDcompile() 校验图完整性并绑定运行时配置,未编译的图无法执行。

2.4 流式输出:实时观测 Agent 的每一步

stream() 方法支持多种流模式,v2 格式下统一为 StreamPart 字典:

模式 输出内容 适用场景
updates 每个节点返回的状态更新 调试、查看中间结果
values 每一步后的完整状态快照 追踪状态演变
messages LLM 的 token 级别生成文本 前端逐字展示
custom 节点内通过 get_stream_writer() 推送的自定义事件 进度通知、日志
python 复制代码
for chunk in graph.stream(inputs, stream_mode=["updates", "custom"], version="v2"):
    if chunk["type"] == "updates":
        for node_name, state_update in chunk["data"].items():
            print(f"Node {node_name} updated: {state_update}")
    elif chunk["type"] == "custom":
        print(f"Custom: {chunk['data']}")

2.5 状态持久化:让 Agent 拥有记忆

compile() 时传入 checkpointer,LangGraph 会在每个 super-step 边界自动保存状态快照。同一 thread_id 的多次调用沿同一条时间线累积状态:

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()
graph = agent_builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "conversation-1"}}
graph.invoke({"messages": [HumanMessage(content="北京天气怎么样?")]}, config)
# 第二轮自动继承之前的消息历史
graph.invoke({"messages": [HumanMessage(content="我刚才问了什么?")]}, config)

三种 checkpointer 实现:InMemorySaver(开发用,内存存储)→ SqliteSaver(本地持久化)→ PostgresSaver(生产环境,支持异步和加密)。

2.6 AgentExecutor vs LangGraph 对比

维度 AgentExecutor LangGraph
架构模式 黑盒 while 循环 显式有向图,每步可寻址
状态管理 隐式闭包,外部不可见 显式 TypedDict/dataclass,可读写
流程控制 固定的思考-工具循环 自定义节点+条件边,任意拓扑
流式输出 基础 token 流 多模式流(状态/更新/token/自定义)
持久化 不支持 内置检查点机制,支持断点恢复
人工介入 不支持 原生 interrupt() 暂停-审批-恢复
扩展性 修改源码或包装 Runnable 插入节点/嵌套子图/多 Agent 协作

三、Day 2:LangGraph 进阶------从二元循环到复杂工作流

3.1 多节点顺序流水线

Day 1 的图只有两个节点(LLM + 工具),是 Agent 的最简骨架。真实场景需要更复杂的拓扑。以"研究→分析→写作→审核"的内容生产流水线为例:

python 复制代码
class PipelineState(MessagesState):
    research_notes: str       # 研究阶段产出
    analysis_result: str      # 分析阶段产出
    draft: str                # 写作阶段产出
    review_feedback: str      # 审核阶段产出
    final_output: str         # 最终产出

# 四个节点依次串联
pipeline.add_edge(START, "research")
pipeline.add_edge("research", "analysis")
pipeline.add_edge("analysis", "writing")
pipeline.add_edge("writing", "review")
pipeline.add_edge("review", END)

状态设计的精髓在于 reducer 的混合使用:messages 字段继承 MessagesStateadd_messages 追加语义(积累型数据),而 research_notesdraft 等自定义字段使用默认的覆盖式 reducer(阶段产出型数据),各取所需、互不干扰。

3.2 条件路由:让图拥有决策能力

线性流水线的致命缺陷是"审核不通过怎么办"------不能让不合格的内容直接输出。条件边让图获得"审核→返工"循环:

python 复制代码
def route_after_review(state: PipelineState) -> Literal["writing", END]:
    if "PASS" in state.get("review_feedback", ""):
        return END
    else:
        return "writing"

pipeline.add_conditional_edges("review", route_after_review, {"writing": "writing", END: END})

路由映射表的左侧是函数返回值右侧是图中实际节点名 。两者可以不同,比如函数返回 "pass" / "fail",映射表写成 {"pass": END, "fail": "writing"},实现返回值与节点名的完全解耦。映射表还提供编译时校验------右侧节点名如果未注册,编译阶段就报错。

3.3 Command:合并状态更新与路由

条件边的路由决策住在节点外部 的独立函数中,节点只负责执行并更新状态。Command 把路由权收回节点内部,让节点一次返回同时完成"更新状态"和"指定路由":

python 复制代码
from langgraph.types import Command

def review_node(state: PipelineState) -> Command[Literal["writing", END]]:
    response = model.invoke(f"审查以下文章:\n{state['draft']}")
    if "PASS" in response.content:
        return Command(update={"review_feedback": response.content, "final_output": state["draft"]}, goto=END)
    else:
        return Command(update={"review_feedback": response.content}, goto="writing")

Command 的核心优势:路由决策可以直接使用节点内部的中间变量和 LLM 即时响应,不需要先把信息写回状态再让外部函数读取。Command 还能做条件边做不到的两件事------通过 resume 配合 interrupt() 实现人工审批恢复,通过 graph 实现跨图跳转。

选择原则 :路由逻辑简单、完全基于状态字段 → 用条件边更清晰;路由决策需要节点内部运行时信息、或需要和状态更新打包 → 用 Command 更合适。

3.4 Send 与并行分发

当一个节点需要将不同数据片段分发给多个下游实例并行处理时(如研究节点检索到 5 篇文章,需要 5 个分析实例各分析一篇),使用 Send

python 复制代码
from langgraph.types import Send

def continue_to_analysis(state: PipelineState):
    articles = state.get("articles", [])
    return [Send("analysis", {"current_article": article}) for article in articles]

pipeline.add_conditional_edges("research", continue_to_analysis)

所有 Send 目标在同一个 super-step 中并行执行,总耗时由最慢的那个决定。这是实现搜索聚合、多文档分析、批量评估等 map-reduce 模式的基础原语。

3.5 检查点深入:不仅仅是"记住对话"

检查点的四大价值:跨轮记忆 (同一 thread 多轮对话自动累积上下文)、人工介入 (暂停后从断点恢复)、时间旅行 (回放到任意历史 checkpoint 并分叉新路径)、容错恢复(节点失败后从上一个成功 checkpoint 重试)。

python 复制代码
# 查看最新状态
snapshot = app.get_state(config)
print(snapshot.values)   # 所有状态字段的当前值
print(snapshot.next)     # 下一步要执行的节点

# 查看完整历史
for s in app.get_state_history(config):
    print(f"Step {s.metadata['step']}: next={s.next}")

graph.update_state(config, values, as_node="某个节点名") 可以手动编辑任意 checkpoint 的状态,在人工介入场景中非常实用------审核者可以直接修改初稿字段,让图从修改后的状态继续执行。

3.6 人工参与:interrupt() 的艺术

LangGraph 提供两种中断方式:

方式 触发位置 适用场景
静态断点 interrupt_before/interrupt_after 编译时固定 调试、步进观察
动态中断 interrupt() 节点内部任意位置 生产环境的人工审批

动态中断的核心代码模式:

python 复制代码
from langgraph.types import interrupt, Command

def approval_node(state: PipelineState) -> dict:
    decision = interrupt({
        "action": "review_draft",
        "content": state.get("draft", ""),
        "message": "请审核以上内容。输入 'approve' 通过,或输入修改意见。"
    })
    if decision == "approve":
        return {"final_output": state["draft"]}
    else:
        return {"review_feedback": f"人工修改意见:{decision}"}

# 恢复执行时 Command(resume=value) 的值会成为 interrupt() 的返回值
app.stream_events(Command(resume=user_decision), config=config, version="v3")

四条关键规则 :① 不能用 try/except 包裹 interrupt()------它通过抛特殊内部异常实现暂停;② 节点内有多个 interrupt() 时,每次恢复都从头执行整个节点,所以调用顺序和数量必须一致,不能有条件跳过;③ payload 必须是 JSON 可序列化的简单值;④ interrupt() 之前的副作用代码必须是幂等的(恢复时节点从头重跑导致这些代码再次执行)。


四、Day 3:多智能体协作系统------拆解复杂性的艺术

4.1 单 Agent 的天花板

当把越来越多工具塞进同一个 Agent,或让同一个 Agent 同时处理代码审查、数据分析、客户沟通这些性质迥异的任务时,单 Agent 架构的三个瓶颈会逐渐暴露:

  • 上下文膨胀:每次工具调用(网页搜索、数据库查询等)都在消息历史中堆积数据,逼近上下文窗口上限
  • 工具选择退化:当可选工具超过十几个,模型在决定调用哪个工具时的准确率显著下降,混淆相似工具、忽略冷门工具
  • 指令冲突:不同业务领域需要不同的系统提示、工具集、甚至模型,全塞进一个提示等于让模型每次推理都消化庞杂指令

解决方案:把复杂的单一 Agent 拆分成多个专门的子 Agent,每个负责一个明确领域,由主 Agent 担任协调者 。官方文档强调,多智能体设计的本质是上下文工程------决定每个 Agent 看到什么信息、不知道什么信息。

4.2 四种正式架构模式

模式 核心特征 控制权 适用场景
Subagents 子 Agent 以工具形式暴露给主 Agent 调用,子 Agent 无状态、上下文隔离 集中(主 Agent 全权调度) 多领域并行任务、层级调度
Handoffs 通过状态变量驱动路由切换,控制权在 Agent 之间直接转移 分散(Agent 之间交接) 多跳协作(搜索→分析→写作)
Skills 单 Agent 按需动态加载专业知识文件,渐进式信息披露 集中(Agent 自主加载) 需要深入领域知识但不需多 Agent
Router 入口处一次分类,将请求路由到不同专门 Agent 分散(一次分发) 简单分类、无跨轮记忆需求

决策框架:子 Agent 不需要直接与用户对话 → Subagents;需要 Agent 间传递控制权、多跳协作 → Handoffs;只需让单 Agent "变专业"而不增加架构复杂度 → Skills;只是把不同类型请求分发出去 → Router。这些模式可以混合使用。

4.3 原生子 Agent 委派:as_tool() 方式

LangChain 原生方式的核心是"子 Agent 作为工具"------用 create_agent() 创建子 Agent,用 as_tool() 包装为工具,主 Agent 通过调用工具来委派任务:

python 复制代码
research_agent = create_agent(
    model=model,
    tools=[web_search, file_reader],
    system_prompt="你是一个研究员,负责收集和整理信息。"
)

analysis_agent = create_agent(
    model=model,
    tools=[calculator, chart_generator],
    system_prompt="你是一个数据分析师,基于研究笔记提炼核心观点。"
)

main_agent = create_agent(
    model=model,
    tools=[
        research_agent.as_tool(
            name="research",
            description="当需要搜索和收集某个主题的详细资料时使用此工具"
        ),
        analysis_agent.as_tool(
            name="analysis",
            description="当需要对研究数据进行计算和分析时使用此工具"
        ),
    ],
    system_prompt="你是一个项目经理,通过调用研究和分析工具来完成复杂任务。"
)

关键设计点:

  • 子 Agent 每个有独立的系统提示和工具集,彼此完全隔离
  • as_tool() 自动处理消息构造和结果提取,隐藏子 Agent 内部的思考-行动循环
  • 工具描述是主 Agent 决定何时委派的唯一依据------必须精确、具体、行动导向

4.4 Deep Agents:声明式多智能体

Deep Agents SDK 将子 Agent 配置简化为声明式字典,内部自动完成编译、注册、上下文管理等:

python 复制代码
from deepagents import create_deep_agent

research_subagent = {
    "name": "research-agent",
    "description": "收集某个主题的详细资料并返回结构化的研究笔记。",
    "system_prompt": "你是一个资深研究员...",
    "tools": [web_search]
}

analysis_subagent = {
    "name": "analysis-agent", 
    "description": "对研究笔记和数据进行定量分析和逻辑推理。",
    "system_prompt": "你是一个数据分析师...",
    "tools": [calculator],
    "model": analysis_model,                # 不同的子Agent可以用不同的模型!
    "response_format": AnalysisResult,      # 结构化 JSON 输出
}

main_agent = create_deep_agent(
    model=main_model,
    system_prompt="你是一个项目经理,协调研究Agent和分析Agent完成任务。",
    subagents=[research_subagent, analysis_subagent]
)

Deep Agents 的核心增值能力:

  • 任务规划 :内置 write_todos 工具,自动分解复杂任务为步骤列表并追踪进度
  • 上下文压缩:offloading(大型工具结果卸载到虚拟文件系统)+ summarization(对话历史达到 85% token 上限时自动摘要替换),双轨机制
  • 虚拟文件系统 :提供 ls / read_file / write_file / edit_file 四个基础操作,Agent 可以像操作真实文件系统一样管理数据
  • 子 Agent 生成 :通过内置 task 工具创建和调用子 Agent,子 Agent 在隔离上下文中运行

最佳实践:让每个子 Agent 返回简洁摘要(在 system_prompt 中明确"控制在 X 字以内")→ 保护上下文隔离优势;只给子 Agent 确实需要的工具 → 减少工具选择退化;不同任务用不同模型 → 利用各模型优势;主 Agent 提示中明确引导委派 → 避免主 Agent "自信"地自己回答问题。

4.5 并行协作与架构选型

Send 机制是实现并行分发的核心原语。将它与 Subagents 模式结合,可以构建主 Agent 在单轮中并行调用多个子 Agent 的高效系统------搜索 Agent 同时查多个数据源、分析 Agent 同时审阅多份文档,总延迟由最慢的那个决定。

架构选型四维度评估:

  • 控制权归属:集中(Subagents)vs 分散(Handoffs/Router)
  • 上下文隔离需求:子 Agent 的中间过程是否需要对主 Agent 可见
  • 并行化需求 :是否需要 Send 实现多路并发
  • 开发复杂度:声明式配置(Deep Agents)vs 手写包装函数(原生方式)

五、Day 4:生产级部署------从 python main.py 到线上服务

5.1 错误处理与重试:多层容错体系

生产环境中,一个任务的中间步骤可能数十步,仅因一次临时网络超时就丢弃所有上下文重启,这在经济和体验上都不可接受。LangChain/LangGraph 提供从底层 HTTP 重试到顶层 Agent 自愈的完整容错链:

第一层:RunnableConfig 基础防护

python 复制代码
from langchain_core.runnables import RunnableConfig

config = RunnableConfig(max_retries=3, timeout=30)
result = agent.invoke({"messages": [...]}, config=config)

max_retries 主要针对可重试错误(网络超时、429 限流、5xx),业务逻辑错误不会自动重试。

第二层:节点级 RetryPolicy

python 复制代码
from langgraph.types import RetryPolicy

graph.add_node(
    "call_model", call_model,
    retry=RetryPolicy(
        max_attempts=3,
        initial_interval=1.0,     # 首次等待 1 秒
        backoff_factor=2,         # 指数退避:1→2→4 秒
        retry_on=(TimeoutError, ConnectionError)  # 仅在特定异常时重试
    )
)

第三层:with_fallbacks() 模型降级

python 复制代码
robust_model = primary_model.with_fallbacks([fallback_model])
agent = create_agent(robust_model, tools=[...])

多个后备方案形成降级链:主模型 → 备用模型 → 本地缓存。

第四层:中间件级错误回调

python 复制代码
@wrap_tool_call
def error_handling_middleware(request, handler):
    try:
        return handler(request)
    except RateLimitError:
        time.sleep(60)
        return handler(request)          # 限流→等待后重试
    except TimeoutError:
        return "服务响应超时,请稍后重试。"  # 超时→降级响应
    except Exception as e:
        return f"工具执行遇到错误:{e}"     # 未知→日志+降级

第五层:MCP 工具级自愈 (v0.3.0+):工具执行错误默认包装为 status="error"ToolMessage 返回给模型,模型看到错误后可自行换参数、换工具或请求人工介入,而非直接崩溃。

5.2 MCP 协议:一次开发,多平台复用

Model Context Protocol(MCP)是 Anthropic 提出的标准化协议,让工具的定义与消费端彻底解耦。编写一个 MCP 服务器后,所有支持 MCP 的客户端(LangChain、Cursor、Claude Desktop)都可以直接使用。

创建 MCP 服务器

python 复制代码
# math_server.py
from fastmcp import FastMCP

mcp = FastMCP("Math")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

if __name__ == "__main__":
    mcp.run(transport="stdio")  # 或 transport="http" 部署为远程服务

消费 MCP 工具------协议透明,Agent 不感知工具来自本地子进程还是远程服务器:

python 复制代码
from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "math": {
        "transport": "stdio",
        "command": "python",
        "args": ["/path/to/math_server.py"],
    },
    "weather": {
        "transport": "http",
        "url": "http://localhost:8000/mcp",
        "headers": {"Authorization": "Bearer YOUR_TOKEN"},
    }
})

tools = await client.get_tools()
agent = create_agent(model=model, tools=tools)

拦截器系统 (v0.3.0+):让 LangGraph 运行时信息穿透到 MCP 工具调用中------在调用前注入用户凭证、在调用后更新图状态或通过 Command(goto=...) 驱动路由:

python 复制代码
async def inject_user_context(request, handler):
    runtime = request.runtime
    modified_request = request.override(
        args={**request.args, "user_id": runtime.context.user_id}
    )
    return await handler(modified_request)

client = MultiServerMCPClient({...}, tool_interceptors=[inject_user_context])

MCP 的三种能力(工具、资源、提示)合在一起,让 MCP 服务器成为名副其实的"上下文协议"------同时向 Agent 提供可调用的函数、可读取的数据和可参考的提示模板。

5.3 LangSmith Fleet:无代码 Agent 平台(了解)

Fleet 是运行在 LangSmith 上的无代码 Agent 构建和管理平台,代表 Agent 开发从纯代码走向"低代码 + 代码"混合模式的行业趋势。五个核心能力:内置数十种即用型工具 + MCP 协议接入、多频道调度(被动响应 + 定时任务 + 事件驱动)、两层记忆系统(线程级短期 + 文件持久化长期)、高风险操作人工审批、子 Agent 与技能系统。


六、Day 5:性能优化与监控------速度、成本、稳定性的三角平衡

6.1 LCEL 并行处理:用并发换时间

单模型批量并行 ------batch() 替代逐个 invoke()

python 复制代码
# 串行:逐条等待,总耗时 = 所有请求耗时累加
# for q in questions:
#     answer = model.invoke(q)

# 并行:同时发出,总耗时 ≈ 单个请求耗时
responses = model.batch(questions)

通过 RunnableConfig(max_concurrency=N) 精确控制并发窗口,防止触发 API 限流。batch_as_completed() 支持"完成一个处理一个"的流式结果消费,适合前端实时展示。

多任务并行编排 ------RunnableParallel

python 复制代码
from langchain_core.runnables import RunnableParallel

parallel_chains = RunnableParallel(
    summary=summary_chain,
    keywords=keywords_chain,
    sentiment=sentiment_chain,
)
result = parallel_chains.invoke({"text": article_text})
# result["summary"], result["keywords"], result["sentiment"] 同时就绪

RunnableParallelbatch() 可以组合使用实现二维并行------输入维度批量并发 + 任务维度子任务并发。

图级节点并发------LangGraph 的 super-step 机制:

从节点 A 同时画边到 B 和 C,B 和 C 在同一 super-step 中并发执行。整个 super-step 是事务性的------如果 B 或 C 任一抛出异常,两个分支对状态的所有更新都不会被应用,状态回滚到 super-step 开始之前。

6.2 LangSmith 成本监控:让每一分钱算得清楚

Agent 的动态特性意味着无法静态预测成本------简单查询可能只触发 2 次模型调用,复杂分析可能触发 20 次。LangSmith 通过自动 trace 机制记录每一次 LLM 调用、每一次工具执行的结构化数据。

接入方式 :安装 langsmith,设置 LANGSMITH_API_KEYLANGSMITH_TRACING=true 环境变量,LangChain 框架内的调用自动追踪,零代码改动。

@traceable 装饰器------让任意 Python 函数成为 trace 树节点:

python 复制代码
from langsmith import traceable

@traceable(run_type="llm", name="Chat Completion")
def call_model(messages: list[dict]):
    response = client.chat.completions.create(model="gpt-4.1", messages=messages)
    return response.choices[0].message

@traceable(run_type="tool", name="Search Knowledge Base")
def search_knowledge_base(query: str) -> str:
    return f"搜索结果: {query}"

@traceable(name="Research Pipeline")
def research_pipeline(topic: str):
    context = search_knowledge_base(topic)    # 子 trace
    return call_model([...])                   # 子 trace

嵌套调用自动形成父子关系,构建完整调用层级图。run_type 决定 UI 渲染方式:"llm" 自动展示 token 计数和成本估算,"tool" 结构化展示输入输出,"retriever" 渲染检索文档列表。

对于自部署模型,通过 get_current_run_tree().set(usage_metadata={...}) 手动上报 token 用量和成本(单位美元),让所有模型在 LangSmith 中拥有一致的成本视图。

6.3 上下文压缩:不让 Agent 被历史淹没

过长的上下文带来三个问题:模型对中间信息的注意力稀释("迷失在中间"效应)、推理延迟线性增长、token 成本线性增长。

策略 触发条件 优点 适用场景
消息裁剪 Token 数 > 阈值 实现简单,零额外 LLM 调用 对话主题频繁切换,历史信息价值低
摘要压缩 子任务完成 / 阶段节点 保留语义信息,不打断当前思考 多步推理、长对话中需要跨轮次记忆
混合策略 阈值触发 + 保留最近 N 条 兼顾效率与信息保真 绝大多数生产环境对话 Agent

SummarizationMiddleware 是 LangChain v1 内置的摘要压缩中间件,持续监控 token 数,超阈值时自动用便宜模型将早期消息压缩为精炼摘要:

python 复制代码
from langchain.agents.middleware import SummarizationMiddleware

SummarizationMiddleware(
    model=summary_model,
    trigger=("tokens", 4000),        # 超过 4000 token 触发
    keep=("messages", 20),           # 始终保留最近 20 条原文
)

重要踩坑 :使用 trigger=("fraction", 0.8)(上下文占比模式)时,中间件需要从 model.profile["max_input_tokens"] 获取上下文上限。通过硅基流动等第三方 API 调用 Qwen/DeepSeek 等模型时,init_chat_model 不会自动携带 profile 信息,必须手动传入 profile={"max_input_tokens": 131072},否则 fraction 模式静默失效------摘要永远不会被触发。

原生支持 OR 触发(trigger 传入列表,任一条件满足即触发),需要 AND 触发时可子类化覆写 _should_summarize 方法。


七、核心概念的个性化理解

7.1 对"图即流程"的感悟

LangGraph 的核心洞见是:Agent 的执行流程本质上就是一个有向图create_agent() 隐藏了这个图结构,而 LangGraph 把它显式化。显式化的好处不仅仅是"能看见",更重要的是"能修改"------你可以在任意两个节点之间插入新节点,可以用条件边替换固定边,可以把一个节点替换为整个子图。这种可组合性让 Agent 从"黑盒产品"变成了"可定制的乐高"。回顾第 1 周学的 LCEL Runnable 管道,LangGraph 的图模型是更高维度的"管道"------不仅在时间维度上串联步骤,还在空间维度上支持分支和并行。

7.2 对"状态即记忆"的理解

第 2 周我们用 checkpointer + thread_id 实现多轮对话记忆,当时只觉得这是一个"保存消息历史"的便利功能。学完 LangGraph 的检查点机制后才理解,状态持久化远不止记忆------它是人工介入的基础(暂停后能从断点恢复),是容错的基础(失败后从上一个成功 checkpoint 重试),是时间旅行的基础(回放历史状态探索"如果当时做了不同选择")。状态不是 Agent 的附属品,状态就是 Agent 本身------Agent 的行为由当前状态和图的拓扑共同决定。

7.3 对"多智能体 = 上下文工程"的理解

一开始我以为多智能体是让多个 AI 像人类团队一样"讨论协作"。但实际上它的本质更朴素------控制每个 Agent 看到什么信息。子 Agent 只获得完成任务所需的最小工具集和最小上下文,主 Agent 只看到子 Agent 返回的最终结果而非中间过程。这不是多 Agent 在"对话",而是通过信息隔离来降低每个 Agent 的认知负担。这也解释了为什么 Subagents 是最常用模式------子 Agent 不需要"人格",它只是一个被严格限定输入输出的函数。

7.4 对 MCP 协议与 @tool 的关系理解

第 2 周学 @tool 装饰器时,我以为工具定义就是写个 Python 函数加个装饰器。MCP 协议让我意识到这只是一个"本地实现"。在工程上,工具应该与消费端解耦------工具服务器独立部署、独立扩缩容、独立更新,所有客户端通过标准协议消费。MCP 之于工具,就像 REST API 之于 Web 服务:定义了一套标准化的通信契约。这也是为什么 LangChain 官方在 v0.3.0 中大力投入 MCP 适配器------它代表了工具从"框架绑定"到"协议驱动"的演进方向。

7.5 性能优化的元原则

并行处理、成本监控、上下文压缩看似是三个独立主题,但它们共同遵循一个元原则:在 LLM 应用中,资源(时间、金钱、token)的消耗是不可见且容易被忽略的。本地开发时 3 秒和 0.5 秒的延迟差异不明显,但乘上每天数千次请求就是巨大的体验鸿沟。单次调用几美分的成本不在意,但月底账单会告诉你真相。这个元原则提醒我们:从开发第一天起就应该建立观测意识------没有度量就没有优化。


八、踩坑记录与经验

  1. fraction 触发模式需要手动传 profile :使用 SummarizationMiddlewaretrigger=("fraction", 0.8) 时,通过硅基流动等第三方 API 调用的模型不会自动携带 max_input_tokens。必须在 init_chat_model() 中手动传入 profile={"max_input_tokens": 131072},否则中间件无法计算阈值,摘要永不触发------不会报错,属于静默失效,排查极其耗时。

  2. interrupt() 不能被 try/except 包裹interrupt() 通过抛出特殊内部异常(GraphInterrupt)来实现暂停。如果你在它外面包了 except Exception,这个异常会被捕获,导致中断不被传递到运行时,图不会暂停而是继续执行。调试时发现图"跳过了"中断点,十有八九是这个原因。

  3. 一个节点不要同时用普通边和条件边 :LangGraph 允许你为一个节点同时添加 add_edgeadd_conditional_edges,但结果是两条路径都会被激活------节点执行完后既走固定边又走条件边,导致不可预测的行为(通常是下游节点被执行两次)。正确做法是每个节点只选一种路由机制。

  4. interrupt() 恢复时节点从头重跑,副作用必须幂等 :如果节点在 interrupt() 之前做了数据库插入、文件写入等操作,恢复时这些操作会再次执行,造成重复数据。应该将副作用移到 interrupt() 之后,或者使用 upsert 等幂等操作。这个坑在实际项目中非常常见。

  5. RunnableParallel 中所有分支共享同一个输入 :如果各子任务需要不同的输入字段,需要在每个分支前加 RunnablePassthrough 或 lambda 做字段筛选,否则会把完整输入字典传给每个分支。好消息是 LangChain 的 RunnableParallel 会自动将输出按字段名组装,无需手动合并。

  6. MCP 的 handle_tool_errors 默认值已变为 True :自 langchain-mcp-adapters v0.3.0 起,工具执行错误默认被包装为 ToolMessage(status="error") 返回而非直接抛异常。这意味着 Agent 获得了"自愈"能力------它能读取错误信息并自行调整策略。但如果你需要严格的 fail-fast 行为(如金融交易场景),必须显式设置 handle_tool_errors=False

  7. max_concurrency 是客户端限流,不是服务端RunnableConfig(max_concurrency=N) 控制的是 LangChain 向 API 发送请求的节奏,不会改变模型 API 本身的行为。如果 API 提供商有自己的速率限制,光设置 max_concurrency 不够,还需要配合 RetryPolicy 处理 429 响应。

  8. Send 分发时,目标节点的状态更新是独立合并的 :如果多个 Send 目标并行写同一个状态字段,需要该字段有合适的 reducer(如 operator.add),否则后完成的节点会覆盖先完成的节点的写入。这与 LangGraph super-step 的事务性不矛盾------事务性保证的是"要么全部生效要么全部回滚",不是"保证写入顺序"。


九、下周展望

第 4 周的主题是项目实战与前沿探索。从本周的认知出发:

  • LangGraph 的图编排能力将在实际项目中被完整验证------一个真实业务场景可能需要数十个节点和复杂的条件分支
  • Deep Agents 的子 Agent 委派 + 上下文压缩 + 虚拟文件系统将在长期运行的分析任务中发挥关键作用
  • MCP 协议的工具复用能力使得"构建一次工具服务器,多项目共用"成为现实
  • 通过 LangSmith 的性能数据,可以识别出系统中的瓶颈节点(哪个节点耗时最长、哪个工具失败率最高)并针对性优化
  • 第 4 周的实战项目将把这些零散的技术点串联为端到端的可部署应用

十、本周学习成果自检

  • 理解 LangGraph 的四大核心概念(StateGraph / State / Node / Edge),能解释 add_messages reducer 的工作原理
  • 能手写完整 ReAct 循环的 LangGraph 版本(LLM 节点 + 工具节点 + 条件边 + 循环回路)
  • 理解三种流模式(updates / values / messages)的输出差异,能使用 get_stream_writer() 推送自定义事件
  • 掌握状态持久化机制,能用 InMemorySaver + thread_id 实现多轮对话记忆
  • 能构建 4 节点以上的多节点流水线(如研究→分析→写作→审核),理解混合 reducer 的设计
  • 理解条件边、CommandSend 三种路由机制的区别与适用场景
  • 掌握 interrupt() 动态中断机制,能实现人工审批工作流,理解四条关键规则
  • 理解多智能体的四种正式模式(Subagents / Handoffs / Skills / Router)及其适用场景
  • 能使用 as_tool() 和 Deep Agents 的声明式配置两种方式实现主 Agent + 子 Agent 调度
  • 理解 Deep Agents 的内置能力(任务规划 / 上下文压缩 / 虚拟文件系统)及其设计意图
  • 掌握从 RunnableConfigRetryPolicywith_fallbacks()@wrap_tool_call 的多层容错体系
  • 能创建 MCP 服务器并将工具接入 LangChain Agent,理解协议透明的设计哲学
  • 能使用 batch() / RunnableParallel / LangGraph super-step 三种并行机制提升吞吐量
  • 能使用 @traceable 装饰器和 LangSmith 平台监控 Agent 的调用链路与成本
  • 掌握 SummarizationMiddleware 的配置方法,理解 fraction 模式的 profile 依赖问题
  • 能对比 AgentExecutor 与 LangGraph 在架构/状态管理/扩展性三个维度的差异
相关推荐
码云骑士1 小时前
11-GIL不是性能杀手(上)-CPU密集vsIO密集的实测对比
开发语言·python
johnny2331 小时前
Python生态模版引擎:Django、Jinja2、Liquid、Mustache、Mako、Chameleon
python
喵叔哟2 小时前
Week 3 --Day 5:性能优化与监控
人工智能·python·性能优化·langchain
Kingairy2 小时前
python3装饰器
开发语言·python
暗黑小白2 小时前
第八篇:人在回路与内容安全 —— 当 AI 说“让我请示一下“
python·安全·架构·ai agent
大蚂蚁2号2 小时前
Python 项目架构深度解析:从混乱到清晰
开发语言·python·架构
老徐聊GEO3 小时前
AI搜索流量转化率实测分享:我的案例与复盘
人工智能·python
草莓熊Lotso3 小时前
【LangChain】流式传输原理与 LangSmith 应用监控全解析
人工智能·python·langchain·gpt-3
老毛肚10 小时前
jeecg-boot-base-core 02 day
javascript·python