4640 字
23 分钟
页面浏览--访问次数--
接着聊 Swarm:到底谁在干活,以及两种「记忆」是不是一回事

上一篇我们用 Swarm 把多 Agent 协作的最小骨架过了一遍:Agent、Handoff、Context、run() 这几个抽象,以及一次运行里消息是怎么流转的。

写完以后我又回去把那张时序图盯了很久,越看越觉得有个地方不对劲:智能体对象(Agent)在整张图里,好像从头到尾没给谁传过任何东西。

顺着这个疑问往下想,问题就一个个冒出来了:

  • Agent 一直”被获取”,那它自己到底干不干活?
  • 指令函数(instructions)和工具函数(Tool),不都是我自己写的 Python 函数吗,它们是一回事?
  • 决定”转交给哪个 Agent 专家”的,是模型的算力,还是我写死的代码?
  • 用户之前说过的话被共享给了多个 Agent,我关掉网页再刷新进来,它还记得吗?

这几个问题问得有点杂,但最后都指向同一件事:在 Swarm 里,到底谁在干活。

Swarm Context Variables 与 LangChain Memory 的总览对比

图:两种「记忆」各自负责的部分。

第一部分 先把几个人认清楚#

Swarm 这套东西里其实有五个角色:

角色是什么在哪
Agent 对象配置表:我是谁、我怎么说话、我能用什么工具、我用哪个模型你的 Python 进程内存里
instructions 函数Agent 配置的一部分,用来生成系统提示词你写的代码里
Tool / 功能函数Agent 配置的一部分,真正执行动作(查天气、查账户)你写的代码里
AI 模型推理引擎,算力的来源人家的服务器上
Swarm 框架调度器:读配置、组请求、执行工具、处理交接你的 Python 进程内存里
Swarm 内部角色分工图

图:几者之间的数据流。

1. 智能体对象:它不干活,只被查#

可能你会想:智能体对象夹在 Swarm 和指令函数中间,到底是干嘛的?

它什么都不干,只被读取。

把时序图放大看,Agent 只出现在两个动作里:

Swarm框架 → 获取agent.instructions → 智能体对象
→ 获取agent.model → 智能体对象

注意箭头方向。是 Swarm 主动去取 Agent 的属性,而不是 Agent 把什么东西推给 Swarm。

agent = Agent(
name="客服",
instructions=instructions, # 字符串 或 函数
model="gpt-4o", # 用哪个模型
functions=[...], # 挂了哪些工具
)

对象创建完之后就一直放在那里,等 Swarm 来读。Agent 本身不承担任何计算,作用只是把配置存住。

至于”它全程没传递东西”,这个观察是对的,而且这正是设计意图。Agent 是无状态的配置对象,可以预先创建、反复复用。如果让 Agent 自己往外推数据,它就得维护状态,还得知道当前在跟谁说话、消息发到哪了,那这部分工作就和 Swarm 重叠了。

2. 指令函数和工具函数,是两条路#

可能你会想:指令函数能不能当成自己写的 Tool 用?反正都是我写的 Python 函数。

不能。它们都是你写的函数,但走的是两条完全不同的路。

instructions 函数与 Tool 的两条路径

图:两种函数的调用路径。

instructions 函数Tool / 功能函数
谁调用它Swarm 框架AI 模型决定,Swarm 代执行
什么时候每次准备 AI 请求之前AI 返回 tool_calls 之后
传什么参数context_variablesAI 提取的参数 + context_variables
返回值给谁返回给 Swarm,拼进系统提示词作为 tool 消息塞回对话历史
AI 知道它存在吗不知道,只看到它生成后的文字知道,看到它的 JSON schema

指令函数由 Swarm 调用,返回的内容会被拼进系统提示词。工具函数由 AI 决定是否调用,Swarm 负责执行,返回的内容作为 tool 消息回到对话历史。

所以指令函数不碰数据库和 API,只负责根据上下文拼一段提示词。真正干活的是工具函数。

2.1 那”很多段动态 prompt”是怎么选的#

可能你会想:一个 Agent 可以准备很多段动态 prompt,Swarm 怎么确认这次用哪一段?靠 AI 判断吗?

不靠 AI,而且这里没有”从多个 prompt 里挑一个”这个动作。

Swarm 做的事很朴素:

if callable(agent.instructions):
instructions = agent.instructions(context_variables) # 是函数 → 调用它
else:
instructions = agent.instructions # 是字符串 → 直接用

callable() 是 Python 内置函数,用来判断一个对象能不能加括号执行。函数返回 True,字符串返回 False。

所谓的”很多段动态 prompt”,其实是同一个函数根据不同的 context_variables 返回不同的字符串:

def instructions(context_variables):
if context_variables.get("vip"):
return "你是一位 VIP 专属客服,语气要更热情。"
return "你是一位普通客服,语气专业即可。"

这里没有什么智能的成分。哪段提示词被拼出来,完全取决于你写在函数里的判断。Swarm 在准备调用 AI 之前就把这段提示词定好了,之后才连同对话历史、工具 schema 一起发给模型,AI 看到的只是最后那段文字。

2.2 顺带一个细节:Tool 怎么拿到上下文#

AI 在决定调用工具时,只能从用户说的话里提取参数,比如 location="Paris"。context_variables 不在它的视野里,那是框架自己维护的状态。

Swarm 在中间补了一手:

if __CTX_VARS_NAME__ in func.__code__.co_varnames:
args[__CTX_VARS_NAME__] = context_variables # 向功能注入上下文
raw_result = func(**args) # 执行功能

工具函数如果需要上下文,在参数里写上 context_variables 就行,Swarm 会自动传进去。

3. 模型不是 Swarm 挑的#

可能你会想:Swarm 拿到提示词和工具之后,会判断该用哪个模型吧?

不会。模型名是 Agent 自己配好的,Swarm 只是把它读出来。

agent = Agent(model="gpt-4o", ...) # 这里就定死了
# Swarm 内部: chat.completions.create(model=agent.model, ...)

Swarm 把 agent.model 直接填进请求里,中间没有挑选的动作。

模型唯一会变的情况是 handoff:当前 Agent 把对话交给另一个 Agent,Swarm 更新 active_agent,下一轮循环重新去读新 Agent 的 agent.model。变的原因只是换了 Agent。

3.1 “没有算力,Agent 靠什么告诉 Swarm”#

Agent 不需要算力判断调用吗?没有算力,它怎么告诉 Swarm 该干嘛,难道靠程序员写的逻辑?

答案是,这两件事要分开看。

程序员写的逻辑负责的是”声明”:Agent 叫什么、用哪个模型、挂哪些函数、instructions 是字符串还是函数。这些写代码的时候就定好了,不需要算力。

真正需要算力的部分,是判断用户这句话该不该触发某个函数调用。是调退款函数还是天气函数,还是什么都不调直接回复,这些是模型算出来的。

谁负责需要算力吗
有哪些 Agent、有哪些工具、用哪个模型程序员(声明)不需要
这次该不该调工具、调哪一个AI 模型需要
调完之后怎么执行、状态怎么更新Swarm不需要

顺便说一下,Agent 对象里并没有”装”着 AI 模型,它存的只是模型名字,比如 "gpt-4o"。真正的模型在别人的服务器上。

写成分式的话,Agent 对象差不多等于 instructions、functions 这些外置内容,再加上一个”用哪个模型”的指针。

4. 转交给哪个 Agent,谁说了算#

可能你会想:AI 模型和智能体对象,谁才是 Agent?又是谁判断什么时候调用哪个专家?

智能体对象才是 Agent。AI 模型是它调用的推理引擎。

第二问没有单一答案,因为这件事被拆成了三段:

handoff 的三段分工

图:转交过程中的三个环节。

环节谁负责靠什么
要不要触发转交AI 模型算力判断:用户说「我要退款」,该调 transfer_to_refund_agent
转交给谁程序员写死的函数返回值:def transfer_to_refund_agent(): return refund_agent
转交动作本身Swarm 框架执行函数 → 更新 active_agent → 下一轮用新 Agent 的配置

AI 模型看不到”会转到哪个 Agent”。它能看到的只是这个函数的名字和文档字符串,所以它决定的是调不调,至于调完之后去哪,不归它管。

这点很容易说糊涂。我第一次讲的时候直接说”是 AI 决定找哪个专家”,后来发现不对。AI 模型决定要不要触发转交,程序员决定转交给谁。

如果程序员不写那个函数,AI 模型就永远转交不了,因为它的工具列表里没有这个选项。

第二部分 context_variables 到底是个什么东西#

我感觉还说先把结论给你们,下面的定义有点太多了 如果是LangChain的多轮对话记忆 你只是想让 Chatbot 记住刚才聊了什么,实现多轮连续对话。 你的需求是标准的“记住聊天记录”,不需要在对话历史之外维护额外的业务变量。 作为快速原型,先用一行代码验证对话记忆的效果。#

如果需要操作“上下文变量”(自定义 State)的场景: 多步骤任务:Agent 需要在运行中记录中间结果(如先搜资料 A,再结合资料 B 写报告),这些中间变量必须存在 State 中,供后续步骤读取 动态行为:你需要根据用户是否登录、对话轮数等变量,动态调整系统提示词或可用工具。 工具交互:工具函数需要读取或写入会话级别的特定变量(如当前任务 ID),而不仅仅是收发消息。#

1. 你传进去的字典,Swarm 会先复制一份#

def run(...):
active_agent = agent
context_variables = copy.deepcopy(context_variables) # ← 这里
while ...:
completion = self.get_chat_completion(
agent=active_agent,
context_variables=context_variables, # 传的是副本
...
)
if message.tool_calls:
context_variables.update(partial_response.context_variables)
return Response(context_variables=context_variables) # 返回被改过的副本

你传进去的那个字典不会被改动。Swarm 在 run 开头做了 deepcopy,整个循环里传的都是副本;工具函数如果写回了新值,改的也是副本。循环结束时返回给你的,是这份被改过的副本。

想持久化这次运行产生的新数据,得自己从 response.context_variables 里取出来存。

2. 它只活在一次 run 里#

它就是个普通 Python 字典,client.run() 开始时创建,运行一结束就没了。框架不负责持久化。

可能你会想:用户之前说过一些信息,已经被多 Agent 共享了,我现在关掉网页、刷新重新进来,Agent 还记得吗?

不记得,刷新之后共享状态就清零了。

context_variables 只活在单次运行的内存里。刷新网页意味着原来的进程结束了,内存里的字典被回收;新会话调用 client.run() 时你不主动传,Swarm 拿到的就是空字典。

想要”还记得”,这些动作都得自己做:

# 存:上次运行结束
response = client.run(...)
db.save(conversation_id, response.context_variables)
# 取:新会话开始
context = db.load(conversation_id) # {"name": "小明", "user_id": 456}
# 传:重新塞回去
response = client.run(
agent=agent,
messages=load_messages(conversation_id), # 对话历史也要一起恢复
context_variables=context, # 关键:手动传回
)

这里 messages 和 context_variables 是两回事。如果你把 messages 恢复了,AI 能从文本里看到之前聊过什么,但那是 AI 在读文本,不代表 context_variables 被恢复了。

第三部分 那两种「记忆」的区别#

1. 它们负责的事不一样#

LangChain 的 Memory 关心的是数据存在哪里、怎么持久化。Swarm 的 Context Variables 关心的是同一次运行里,不同组件怎么拿到同一份数据。

Swarm 单次运行内的完整交互流程

图:单次 client.run() 里,用户代码、Swarm 框架、AI 模型、功能代码之间如何传递消息和 context_variables。

平时大家爱把这两件事概括成”一个管传,一个管存”。这么说确实好记,但容易让人以为 context_variables 不存东西。它其实是存的,context_variables = {"user_id": 456} 就是把 456 存了起来。

所以要真正分清楚,还是得看这份数据能活多久、谁能看见它,以及运行结束后由谁负责把它留下来。

维度Swarm · Context VariablesLangChain · Memory / ChatMemory
核心定位单次运行内的共享状态容器跨轮次 / 跨会话的对话历史容器
作用域一次 client.run() 调用跨轮次、跨会话
数据形态Python dict,几 KB 的结构化字段messages 序列,存对话全文
生命周期运行结束即消失,框架不负责落盘默认在内存;配了后端才能持久
多 Agent 共享handoff 时自动跟着走框架层面不为此设计,要共享得自己搭桥
两种记忆的生命周期对比

图:两种「记忆」的存活范围。

2. 纠错(I’m Sorry)#

我前面说”LangChain 持久、Swarm 临时”,这个说法不对。

LangChain 的默认实现 ConversationBufferMemory 把数据存在内存里,进程重启、刷新页面同样会丢,默认并不持久。只有当它挂上持久化后端(RedisChatMessageHistory、PostgresChatMessageHistory 这类)之后,数据才写进外部存储,才能跨会话恢复。

“可以持久”和”默认持久”是两回事。真实的区别在于框架有没有给你现成的方案:

LangChain MemorySwarm context_variables
默认存哪内存内存
刷新后默认记得吗不记得不记得
框架提供持久化后端吗提供,换个 Memory 类就能接数据库不提供,全靠自己写
要实现持久得写多少代码配置一下自己写「存 / 取 / 传」三套逻辑
主要存什么对话消息历史(messages)结构化共享字段

3. 那到底该用哪个#

“既然都要自己持久化,那我直接用 LangChain 的 ChatMemory 不就行了”。这个判断其实不太成立,因为它们解决的不是同一个层面的问题。

如果你要的是对话历史的持久化,比如刷新后还能看到之前聊过什么,那 LangChain 的 Memory 加持久化后端确实更省事。如果你要的是多 Agent 之间共享结构化状态,比如 user_id、账户类型这种字段,又不想把它们塞进对话文本里,那 context_variables 是原生设计,LangChain 反而没有直接对应的机制。

两者不是替代关系。你可以在 LangChain 里也维护一份类似的共享字典,或者在 Swarm 外面套一层 LangChain 的持久化消息历史。

4. 什么该进 messages,什么该进 context_variables#

可能你会想:假设用户一口气说了”我叫小明,我在 A 高中上学,周末要和小美去约会,晚上还得写作业,帮我找个近点的好吃的店”,这段东西我该往 context_variables 里塞什么?

这段文本本身,什么都不用塞。

messages 与 context_variables 的职责边界

图:两种数据的去向。

把完整对话交给模型,它自己就能读出用户叫小明、在 A 高中、赶时间。这些信息不需要你提前结构化进字典。只有当某个信息需要被跨 Agent、跨函数显式传递时,才需要把它放进 context_variables。

这里最关键的一点是,“提取”这个动作是开发者做的,Swarm 不参与。它不会替你从原文里挑字段,只负责把字典传进 run()、handoff 时带着它走、把工具函数写回的值合并进去。

字段的来源通常有几种:你自己写规则提取、调一次 LLM 做结构化抽取,或者让工具函数通过 Result 写回。

5. 两边的边界都在哪#

Context Variables 这边不太能指望的:

  • 不自动提取,里面放什么完全由你和你的函数决定;
  • 只适合预设场景,你得提前知道有哪些字段要共享,比如客服、订票、账户查询这类流程固定的业务;
  • 不适合理解开放文本,用户输入完全开放时提前定义字段是徒劳的,这种场景交给 messages;
  • 不持久化,运行结束就没了;
  • 容量小,官方定位是 KB 级,塞大段文本会拖慢执行,因为它会被传给 instructions 函数和所有工具函数。

Memory / ChatMemory 这边不太能指望的:

  • 持久化需要配置,不是自带的;
  • 不负责跨 Agent 共享,LangChain 的多 Agent 通常走工具调用和任务分发,各子 Agent 有自己的上下文,要共享得自己在应用层搭桥;
  • 存全文会膨胀,对话越长,token 成本和延迟越高,通常还需要自己裁剪、摘要或检索;
  • 它管”说过什么”,不管”关键字段是什么”,想要一个可靠的 user_id,还是得自己结构化出来。

6. 实际项目里怎么搭#

# 1) 取出「说过什么」和「关键字段」,这两份都由你自己持久化
messages = load_messages_from_db(conversation_id)
context = load_context_from_db(conversation_id) # {"user_id": 123, "name": "张三"}
# 2) 关键字段交给 Swarm,让它在本次运行里自动传给各 Agent / 函数
response = client.run(agent=agent, messages=messages, context_variables=context)
# 3) 运行结束,两份分别存回去
save_messages_to_db(response.messages) # 对话全文 → ChatMemory 的职责
save_context_to_db(response.context_variables) # 关键字段 → 自己持久化

最后#

Swarm 这套东西里,Agent 负责声明,AI 模型负责判断,Swarm 负责执行。调不调工具由模型决定,调完之后去哪由程序员的代码决定,具体执行交给 Swarm。

context_variables 只在单次运行内有效,随 handoff 流动,运行完就没了。它不会替你提取字段,想持久化得自己存、取、传。LangChain 的 Memory 默认也在内存里,刷新同样会丢,它比 Swarm 多的是现成的持久化插件。开放文本的理解交给 messages,只有已经确定、并且需要跨组件显式共享的字段,才放进 context_variables。

接着聊 Swarm:到底谁在干活,以及两种「记忆」是不是一回事
https://cnyicheng.top/posts/context-variables-vs-chat-memory/
作者
伊橙
发布于
2026-09-20
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取