在这里插入图片描述

从“一脸懵”到“玩转Agent Protocol”:LangGraph平台三大核心组件全拆解,这才是构建生产级AI Agent的底层密码!读完全文,你会彻底搞懂Runs、Threads、Assistants到底怎么配合,不再被官方文档绕晕。

Agent Protocol 核心组件总览

1. Assistants

2. Threads

3. Runs

4. 协作关系

5. 状态管理

6. 最佳实践

Agent “灵魂蓝图”

配置与指令封装

对话 “记忆容器”

状态持久化上下文

单次 “动作实例”

生命周期管理

调用到完成闭环

Checkpoint 检查点

开发避坑指南

文字目录:

  1. Assistants:Agent的“灵魂蓝图”
  2. Threads:对话的“记忆容器”
  3. Runs:单次执行的“动作实例”
  4. 协作关系:从调用到完成的闭环
  5. 状态管理与Checkpoint持久化
  6. 生产环境最佳实践

嗨,大家好呀,我是你的老朋友精通代码大仙。接下来我们一起学习 《LangChain核心技术与LLM项目实践》

俗话说“饭要一口一口吃,代码要一行一行敲,但Agent Protocol要是啃不透,生产环境能让你敲到地老天荒”。你是不是也这样?照着LangGraph官方示例跑通了Demo,心里正美滋滋,结果一打开LangGraph Platform的文档,看到Runs、Threads、Assistants这三个名词,瞬间脑子嗡的一声——这三个家伙到底啥关系?谁先谁后?我该怎么调接口?别慌,这种“一看就会,一写就废”的无力感,我当年踩坑的时候全经历过。今天咱们就把这三个核心组件掰开了、揉碎了,聊明白。

1. Assistants:Agent的“灵魂蓝图”

在LangGraph Platform的语境里,Assistants可不是OpenAI那个Assistant API的翻版。它更像是一份“施工蓝图”,或者说是一个“模具”。你把LangGraph的图注册到平台后,Assistants负责把这个图封装成一个可远程调度、可配置、可追踪的实体。它身上通常带着三样东西:graph(定义了节点和边)、config(运行时的默认参数,比如用哪个模型、温度值多少)、metadata(版本号、负责人、业务标签等)。

说白了,Assistants回答的是“我是谁、我能干什么、我的默认配置是什么”这三个哲学问题。一个设计良好的Assistants,应该是稳定、可复用、版本化的,而不是每次都现场捏一个。

Assistants 配置实体

graph

config

metadata

节点与边定义

模型参数

回调配置

版本信息

业务标签

新手最容易犯的错,就是把Assistants当成“临时变量”或者“Prompt存储桶”。我见过太多同学,每来一个新请求,就现场创建一个Assistants,或者把几百字的系统Prompt直接硬编码在Assistants的config里。更绝的是,有人觉得Assistants里应该塞业务逻辑代码,比如“如果用户问订单,就走A分支”——打住!那是Graph该干的事,不是Assistants的职责。

来看看典型的反模式:

# 这是一个典型的反模式
def handle_request(user_input):
    # 每次请求都新建一个Assistant,这谁扛得住?
    assistant = client.assistants.create(
        graph="support_bot",
        config={
            "configurable": {
                # 把业务规则和超长Prompt全塞进去
                "system_prompt": "你要先问候用户,然后询问订单号,然后查询物流,然后...(此处省略500字)"
            }
        }
    )
    # 用完即抛,完全没有复用
    run = client.runs.create(assistant_id=assistant.id, ...)

这样做的问题太大了。第一,创建Assistants是有开销的,你把它当成一次性筷子,接口延迟直接起飞。第二,Prompt和规则写死在配置里,产品运营想改个欢迎语,都得找研发发版,灵活性为零。第三,你根本没有版本管理意识,线上出问题都不知道是哪个“临时Assistant”干的。

正确的姿势是把Assistants当作“长期资产”来维护。基础能力(比如图结构、默认模型)在创建时定好,动态变量(比如本次会话的业务上下文、温度值)放到Run的输入或config里传进去。metadata一定要打标签,做版本控制。

# 1. 预定义,一次创建,长期复用
assistant = client.assistants.create(
    graph="customer_support_graph",
    config={
        "configurable": {
            "model": "gpt-4o",  # 基础能力配置
            "temperature": 0.3
        }
    },
    metadata={
        "name": "客服助手V2",
        "version": "2.1.0",
        "team": "ai-platform",
        "env": "production"
    }
)
# 记下这个 assistant.id,后面反复用

# 2. 真正运行时,动态传业务上下文
run = client.runs.create(
    assistant_id=assistant.id,
    thread_id=thread.id,
    input={"messages": [("human", "帮我查订单")]},
    config={
        "configurable": {
            "biz_context": "双11大促",  # 动态变量
            "user_tier": "VIP"
        }
    }
)

这样做的好处一目了然。Assistants保持稳定,你可以围绕它做灰度发布、A/B测试;Run保持灵活,每次请求都能注入新鲜上下文。而且metadata里的版本信息,让你在排查线上问题时,一眼就能定位到是哪个版本的逻辑在跑。

小结:Assistants是Agent的“灵魂蓝图”,先画好图、定好规,再谈盖房子。别把它当成一次性草稿纸。

2. Threads:对话的“记忆容器”

Thread这个单词,在程序员的第一反应里往往是“线程”。但在LangGraph Platform里,它跟操作系统线程半毛钱关系都没有,它是一个“状态容器”、一个“对话的记事本”、一个“记忆的抽屉”。每一个Thread内部都维护着一套状态(State),包括消息历史(messages)、图的当前值(values)、以及一些元数据(metadata)。最关键的是,LangGraph通过Thread实现了跨Run的状态持久化。你第一次Run走到一半,用户去喝了杯咖啡,回来接着聊,Thread能帮你从断点无缝续上。

Thread 状态容器

messages

values

metadata

用户消息

AI消息

工具与观察消息

图节点输出状态

待执行队列

user_id 标记

创建与更新时间

新手对Thread最大的误解,就是把它等同于一个简单的List[Message]。我见过不少同学,自己吭哧吭哧在Redis里存聊天记录,然后每次调用LangGraph时把这段纯文本历史塞进去,觉得自己“管理了上下文”。殊不知,LangGraph的Thread里存的可不只是聊天记录,还有图执行的中间状态、下一个该执行哪个节点的路由信息、以及Checkpoint检查点。你自己存的那点纯文本,根本不足以让图恢复到上次执行到一半的现场。

看看这个误区:

# 误区:自己用Redis管理历史,断了LangGraph的上下文
history = redis.get("chat_history:user_123")  # 只存了文本字符串

# 直接传给图,以为这样就接续了上下文
result = graph.invoke({
    "messages": [HumanMessage(content=msg) for msg in history.split("\n")]
})

# 如果图里有个“审批流”,第一步让用户填金额,第二步让用户确认,
# 现在用户回复“确认”,图根本不知道自己处在第二步,因为状态全丢了!

这种情况特别常见。比如在一个人机协作的审批流里,第一个Run已经问了用户“请输入金额”,用户回复了“5000元”。第二个Run开始,如果没有Thread的原生状态,LangGraph根本不知道“5000元”已经填完了,现在该进入“确认”节点。它可能还会从头执行,再问一遍“请输入金额”。你说用户气不气?

相信原生的Thread,让LangGraph自己管理状态。你要做的,只是为每个用户或每个会话创建一个Thread,然后在后续的所有Run里复用同一个thread_id。LangGraph的Checkpoint机制会自动在后台帮你落盘、恢复。

# 1. 给每个用户创建一个独立的Thread
thread = client.threads.create(
    metadata={
        "user_id": "user_123",
        "session_id": "sess_456",
        "biz_type": "refund"
    }
)

# 2. 第一轮对话:用户提交申请
run1 = client.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id,
    input={"messages": [("human", "我要申请退款,订单号是ABC123")]}
)

# 3. 第二轮对话:用户回复确认(复用同一个Thread!)
run2 = client.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id,
    input={"messages": [("human", "确认退款")]}  # 同一个thread_id
)
# LangGraph会自动从Thread的最后一个Checkpoint恢复,
# 知道“确认退款”对应的是审批流的第二个节点,直接推进

Thread还有一个隐藏福利:你可以随时查询它的完整状态历史,甚至“时间旅行”回到之前的某个状态重新分叉。这种能力在调试复杂工作流时,简直是救命稻草。

小结:Thread是Agent的“记事本”,一页只记一个客户的事。别自己造轮子存聊天记录,让LangGraph替你操心。

3. Runs:单次执行的“动作实例”

如果Assistants是蓝图,Thread是场地,那Run就是真正开干的那支“施工队”。Run代表的是对Assistant的一次具体调用,它会经历完整的生命周期:从排队(pending/queued),到执行(running),再到完结(complete/error/interrupted)。每一次Run都会产生一个新的状态快照,并写回Thread。所以Run既是动作的发起者,也是状态的制造者。

LangGraph Platform的Run设计是异步的,这意味着你提交一个Run之后,它不会立刻给你最终结果,而是给你一个Run ID,让你去轮询、去监听流式事件、或者配置Webhook接收回调。这跟咱们平常写同步函数result = func()的思维习惯完全不同。

pending

queued

running

interrupted

complete

error

expired

很多新手拿到SDK的第一反应,就是把Run当成普通函数调用。“我创建一下,然后直接拿结果”。结果要么是拿到一个None,要么是在running状态就急吼吼去取结果,程序直接报错。还有一种更隐蔽的坑:LangGraph图里设计了人机交互节点(比如interrupt),Run的状态会变成interrupted,这时候如果你不处理,傻等着它变成complete,那能等到天荒地老。

看看这个反模式:

# 反模式:把Run当同步函数
run = client.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id,
    input={"messages": [("human", "查一下库存")]}
)

# 刚创建完,Run可能还在pending或者queued
print(run.status)  # 输出可能是 "pending"
print(run.result)  # 大概率是None,或者抛异常

# 或者这样轮询,但完全没处理中断和异常
while run.status != "complete":
    time.sleep(0.5)
    run = client.runs.get(run.id)
# 如果Run状态变成interrupted,这个循环就死循环了!

第一,接受异步思维。第二,善用Stream模式,实时感知Run的执行过程。第三,对每种终态(complete/error/interrupted/expired)都做显式处理。

# 方案A:Stream模式,实时监听事件(推荐)
for event in client.runs.stream(
    thread_id=thread.id,
    assistant_id=assistant.id,
    input={"messages": [("human", "帮我生成周报")]},
):
    if event.event == "on_chain_start":
        print(f"节点 {event['name']} 开始执行...")
    elif event.event == "on_chain_stream":
        print(f"中间状态: {event.data}")
    elif event.event == "on_chat_model_stream":
        print(f"模型输出: {event.data}", end="")

# 方案B:后台轮询 + 完备状态处理
run = client.runs.create(...)
terminal_states = {"complete", "error", "expired", "cancelled"}

while run.status not in terminal_states:
    time.sleep(1)
    run = client.runs.get(run.id)
    
    if run.status == "interrupted":
        # 人机交互节点,需要前端介入
        user_input = get_user_input_from_frontend(run.thread_id)
        # 重新注入输入,继续执行
        run = client.runs.create(
            thread_id=thread.id,
            assistant_id=assistant.id,
            input={"messages": [("human", user_input)]}
        )

if run.status == "error":
    logger.error(f"Run failed: {run.error}")
elif run.status == "complete":
    print("执行成功:", run.result)

Stream模式特别适合前端打字机效果展示;而轮询模式更适合后台任务。无论哪种,你都得把Run的生命周期纳入掌控,不能让它裸奔。

小结:Run是Agent的“一次心跳”,既要发得出,也要等得回。学会异步思维,你就从“写脚本”进化到“做系统”了。

4. 协作关系:从调用到完成的闭环

单独看懂三个组件不算本事,能把它们串成一条流畅的流水线,才是架构能力的体现。正确的调用链条是:客户端发起请求 → 定位到具体的Assistants(能力定义) → 准备或复用Thread(状态容器) → 启动Run(执行实例) → 图节点开始执行 → 中间状态写入Thread(Checkpoint) → Run到达终态 → 返回结果 → 下一次对话复用同一个Thread。

这个链条里,Assistants提供“能力”,Thread提供“记忆”,Run提供“动力”。三者缺一不可,而且顺序不能乱。

客户端请求

定位 Assistant

创建或复用 Thread

启动 Run

图节点执行

状态写入 Thread

Run 完结

返回响应

下次对话复用 Thread

顺序搞反、资源错配,是这一趴最常见的两副毒药。我见过有人先create一个Run,然后发现Run没地方写状态,再回头补建Thread;还有人为了图省事,把多个不同业务的Assistant往同一个Thread里塞。比如A业务的Assistant是“客服退款图”,B业务的Assistant是“营销推荐图”,两个Run在同一个Thread上执行,状态直接互相覆盖,LangGraph恢复Checkpoint时直接懵圈。

看看这个错误示范:

# 错误示范:跨Assistant混用Thread
thread = client.threads.create(metadata={"biz": "refund"})

# 第一次用退款图
run1 = client.runs.create(
    thread_id=thread.id,
    assistant_id="asst_refund_graph",  # 退款图
    input={"messages": [("human", "我要退款")]}
)

# 第二次居然用推荐图,但Thread里存的是退款图的Checkpoint!
run2 = client.runs.create(
    thread_id=thread.id,  # 同一个Thread!
    assistant_id="asst_recommend_graph",  # 推荐图
    input={"messages": [("human", "给我推荐商品")]}
)
# 推荐图读到退款图的状态,节点对不上,直接报错或者行为异常

严格遵循“一个业务流对应一个Assistant,一个用户会话对应一个Thread”的原则。Run只是无状态的执行动作,它不拥有任何状态,所有状态都归Thread管。在Thread的metadata里打上业务标签,调用前做一致性校验。

# 标准三板斧
ASSISTANT_ID = "asst_customer_support_v2"  # 1. 确定能力定义

def handle_user(user_id, message):
    # 2. 复用该用户的会话容器
    threads = client.threads.search(
        metadata={"user_id": user_id, "biz": "support"}
    )
    if threads:
        thread = threads[0]
    else:
        thread = client.threads.create(
            metadata={"user_id": user_id, "biz": "support"}
        )
    
    # 3. 触发执行(Run只负责动,不负责存)
    run = client.runs.create(
        assistant_id=ASSISTANT_ID,
        thread_id=thread.id,
        input={"messages": [("human", message)]}
    )
    return run.id

这个流程的好处是权责清晰。出了bug,你先查Assistants版本对不对,再查Thread里的状态历史,最后看Run的执行日志。三个维度一交叉,问题定位快得很。

小结:Assistants出图纸,Thread当场地,Run是施工队。三者各安其位,Agent系统才能稳如老狗。

5. 状态管理与Checkpoint持久化

LangGraph最迷人的地方,不是它能画图,而是它能“断点续传”。这背后的功臣就是Checkpoint机制。每次Run执行节点时,LangGraph会根据策略把当前状态快照(Checkpoint)落盘到持久化存储(比如PostgreSQL、Redis)中,并与Thread绑定。这意味着你的Agent不是“无状态函数”,而是一个可以暂停、可以恢复、可以重试的“有状态服务”。

对于LangGraph Platform来说,状态管理基本是透明的。但如果你是自托管,或者想深入理解原理,就必须搞清楚Checkpoint是怎么写入Thread的,以及如何利用它实现时间旅行(Time Travel)和错误重试。

Run 执行节点

状态变更

触发 Checkpoint 写入

持久化到 Postgres 或 Redis

绑定到 Thread

Run 中断或失败

从最新 Checkpoint 恢复

继续执行后续节点

最容易踩的坑,就是以为LangGraph的图是“纯函数”,每次invoke都从零开始。于是很多新手在编译图的时候不配置checkpointer,然后在图外面自己包一层Redis,手动存个最终结果。这简直是买椟还珠——你丢了中间状态,一旦某个节点失败,只能从头重跑,而且根本无法利用LangGraph原生的人机协作能力。

from langgraph.graph import StateGraph

builder = StateGraph(State)
# ... 添加各种节点 ...
graph = builder.compile()  # 注意:没有配置 checkpointer!

# 调用方自己存结果
result = graph.invoke({"messages": [("human", "很复杂的长任务")]})
# 只存了最终结果,中间状态全丢了
redis.set("final_result", str(result))

# 如果任务有5步,第3步因为网络抖动失败了,
# 你想从第3步重试?没门,只能全部重跑。

在编译图时注入Checkpointer,生产环境用PostgresSaver,开发环境用MemorySaver。让LangGraph平台层或自托管的API来帮你管理持久化。同时,学会利用Thread的state查询和时间旅行接口。

# 自托管时正确配置持久化
from langgraph.checkpoint.postgres import PostgresSaver
from psycopg import Connection

conn = Connection.connect("postgresql://user:pass@localhost/db")
checkpointer = PostgresSaver(conn)
checkpointer.setup()  # 初始化checkpoint表

# 编译图时注入
graph = builder.compile(checkpointer=checkpointer)

# 通过SDK查询Thread状态(平台层)
states = client.threads.get_history(thread_id=thread.id)
# 可以看到完整的Checkpoint链条,每个节点执行后的状态

# 时间旅行:回到某个历史状态重新执行
config = {
    "configurable": {
        "thread_id": thread.id,
        "checkpoint_id": states[-2].checkpoint_id
    }
}
# 从倒数第二个状态重新分叉

使用原生Checkpoint的另一个好处是“人机协作”(Human-in-the-loop)。当Run遇到interrupt节点暂停时,状态已经被安全地保存在Thread里。你可以放心地让前端弹窗让用户确认,确认后同一个Thread继续跑,LangGraph从断点无缝恢复。

小结:Checkpoint是Agent的“存档点”。学会用存档,就不怕死机重来,也不怕复杂流程半途而废。

6. 生产环境最佳实践

前面的五个章节,咱们聊的是“是什么”和“为什么”。现在来到最关键的“怎么做”——怎么把这些知识打包成一套能扛住真实流量的生产系统。这里的坑包括但不限于:并发冲突、Run超时、状态僵尸、Assistant版本混乱、监控缺失。咱们一个个来排雷。

30% 25% 20% 15% 10% "生产环境常见问题分布" Assistant 配置错误 Thread 未隔离 Run 状态处理不当 Checkpoint 未配置 监控与超时缺失

用Demo代码直接上线,是新手崩溃最快的方式。我见过一个典型的“裸奔”代码:接口收到请求后,现场创建Thread、创建Run,然后死循环轮询直到complete,期间没有任何超时、没有异常处理、没有用户隔离。上线第一天,流量一并发,数据库连接池打满,Redis内存爆炸,接口响应时间直接飙到30秒。

看看这个反面教材:

# 裸奔代码,千万别学
@app.post("/chat")
async def chat(message: str):
    # 每次请求都新建Thread,没有查重
    thread = client.threads.create()
    
    # 同步阻塞,死等Run完成
    run = client.runs.create(
        thread_id=thread.id,
        assistant_id=ASSISTANT_ID,
        input={"messages": [("human", message)]}
    )
    
    # 死循环轮询,没有超时,没有退避策略
    while run.status != "complete":
        await asyncio.sleep(0.1)
        run = client.runs.get(run.id)
    
    return run.result  # 如果Run是error,这里直接抛异常给用户

生产环境必须穿上“铠甲”。我给你列一个六层防护:

  1. 用户隔离:收到请求后,先搜索该用户是否已有活跃Thread,有则复用,无则创建。避免一个人开十个Thread。
  2. 异步化:把Run提交到后台任务队列(Celery、RQ、Arq),接口立即返回task_id。前端用SSE或WebSocket推送结果。
  3. 并发控制:对同一个Thread,使用multitask_strategy="enqueue",让多个Run排队执行,避免状态冲突。
  4. 超时与重试:给Run设置合理的timeout,对error状态做指数退避重试,对expired做清理。
  5. 生命周期管理:定时归档已完成的Thread,清理长期interrupted的僵尸Run。
  6. 版本灰度:Assistant更新时,通过metadata区分版本,逐步切流,不要一刀切。
@app.post("/chat")
async def chat(req: ChatRequest):
    # 1. 用户隔离:查复用
    threads = client.threads.search(
        metadata={"user_id": req.user_id, "status": "active"}
    )
    thread = threads[0] if threads else client.threads.create(
        metadata={"user_id": req.user_id, "status": "active"}
    )
    
    # 2. 提交Run(带排队策略)
    run = client.runs.create(
        thread_id=thread.id,
        assistant_id=ASSISTANT_ID,
        input={"messages": [("human", req.message)]},
        multitask_strategy="enqueue",  # 同Thread并发时自动排队
        timeout=300  # 5分钟超时
    )
    
    # 3. 立刻返回任务ID,不阻塞
    return {"task_id": run.id, "status": run.status}

# 后台Worker处理(伪代码)
def process_run_events(run_id: str):
    run = client.runs.get(run_id)
    if run.status == "interrupted":
        notify_frontend(run.thread_id, "需要人工确认")
    elif run.status == "error":
        logger.error(f"Run {run_id} failed", extra={"error": run.error})
        # 根据策略重试或告警
    elif run.status == "complete":
        push_to_frontend(run.thread_id, run.result)

配合监控大盘,统计每分钟Run的complete/error/interrupted分布,配上P95延迟,你的系统才算真正长出了眼睛。

小结:上线不是Demo的复制粘贴,多加几层保护伞,才能经得起用户折腾。生产级Agent,拼的是细节。

写在最后

不知不觉,咱们已经把Runs、Threads、Assistants这三个核心组件,从里到外扒了个干净。回顾一下:Assistants是Agent的“灵魂蓝图”,定义了你能做什么;Threads是“记忆容器”,承载了每一次对话的上下文和状态;Runs是“动作实例”,负责把蓝图和记忆变成现实。Checkpoint像存档点,让整个过程可以暂停、可以重试、可以回溯。

它们不是三个孤立的API,而是“行为定义、状态容器、执行实例”的铁三角。你只有把这套协议吃透,才算真正从“调包侠”进化成了“架构师”。

学编程这条路,就像跑马拉松,不是谁起步快,而是谁节奏稳。Agent开发是未来十年的大方向,你现在觉得晦涩的每一个概念,都会变成日后面试和项目里的闪光点。别怕这些名词多,当年我啃微服务的服务发现、配置中心、熔断降级,也是一口老血。但只要你耐着性子,画个图、写两行代码、跑通一个流程,那种“哦!原来如此”的通透感,真的会上瘾。

保持好奇,保持耐心,持续学习。你流的每一滴汗,代码都记得。咱们下一篇,继续肝!

关注私信备注:“资料代找获取”,全网计算机学习资料代找:例如:
《课程:AI 大模型工程师系统课程 (22 章完整版 持续更新)》
《课程:AI 大模型系统实战课第四期 (2026 年开课 持续更新)》
《课程:2026 年 AGI 大模型系统课 23 期》
《课程:2026 年 AGI 大模型系统课 21 期》
《课程:AI 大模型实战课 8 期 (2026 年 2 月最新完结版)》
《课程:AI 大模型系统实战课三期》
《课程:AI 大模型系统课程 (2026 年 2 月开课 持续更新)》
《课程:AI 大模型全阶课程 (2025 年 12 月开课 2026 年 6 月结课)》
《课程:AI 大模型工程师全阶课程 (2025 年 10 月开课 2026 年 4 月结课)》
《课程:2026 年最新大模型 Agent 开发系统课 (持续更新)》
《课程:LLM 多模态视觉大模型系统课》
《课程:大模型 AI 应用开发企业级项目实战课 (2026 年 1 月开课)》
《课程:大模型智能体线上速成班 V2.0》
《课程:Java+AI 大模型智能应用开发全阶课》
《课程:Python+AI 大模型实战视频教程》
《书籍:软件工程 3.0: 大模型驱动的研发新范式.pdf》
《课程:人工智能大模型系统课 (2026 年 1 月底完结版)》
《课程:AI 大模型零基础到商业实战全栈课第五期》
《课程:Vue3.5+Electron + 大模型跨平台 AI 桌面聊天应用实战 (2025)》
《课程:AI 大模型实战训练营 从入门到实战轻松上手》
《课程:2026 年 AI 大模型 RAG 与 Agent 智能体项目实战开发课》
《课程:大模型训练营配套补充资料》

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐