跳到主要内容

Swarms — Agent 原子

本章讲清楚:一个 Agent 到底怎么跑起来。这是整个框架的地基,上层所有 Swarm 都在反复调用它的 run()

1. Agent 是什么

Agent 是 Swarms 的唯一基本积木——一个封装了“LLM + 工具 + 记忆 + 循环控制”的自治实体。它定义在 swarms/structs/agent.py(Agent.__init__,structs/agent.py:331),是全仓库最重的一个类(6000+ 行)。

它的四件套:

组件作用由哪个参数/字段承载
模型实际产生文字的 LLMmodel_name(经 LiteLLM 调用)
工具让模型能“动手”的 Python 函数tools=[...]
记忆存对话历史、可跨进程持久化short_memory(一个 Conversation)
循环让它自己多想几轮max_loops(整数或 "auto")

关键默认值(读自 Agent.__init__,structs/agent.py:331-419):

参数默认含义
agent_name"swarm-worker-01"名字;也是 MEMORY.md 落盘目录的 key
max_loops1跑几轮就返回;"auto" = 自主到自认为完成
temperature0.5采样温度
max_tokens4096单次调用最大生成 token
output_type"str-all-except-first"返回时如何格式化对话
context_compressionTrue接近上下文上限时自动压缩记忆
persistent_memoryTrue跨会话读写 MEMORY.md
reasoning_prompt_onTrue多轮时自动注入“当前第几轮”的推理提示

2. 一次 run:主循环怎么转

这节讲什么: 追一次 agent.run("...") 从入口到返回,看清那个“思考循环”。

入口 run()(structs/agent.py:4690)先做分流:max_loops == "auto" 走自主结构(见第 4 节),否则进 _run()(structs/agent.py:4800-4816)跑固定轮数的主循环。

主循环的直觉: 一轮 = 让模型看着“到目前为止的全部对话”再说一段话;如果这段话里带了工具调用,就把工具跑掉、把结果也写进对话;然后进入下一轮。轮数由 max_loops 控制。

agent.run(task)
│ (max_loops == "auto"? → 自主结构;否则 ↓)

把 task 写进 short_memory (对话记忆)

┌──────────► 一轮 loop (循环 max_loops 次) ─────────┐
│ ① 把整段对话历史拼成 prompt │
│ ② call_llm → 模型回一段话(可能含工具调用) │
│ ③ parse_llm_output 解析,写回 short_memory │
│ ④ 若回答里有 handoff / 工具调用 → 执行,结果写回 │
│ ⑤ 检查停止条件 / 交互模式 / 自动保存 │
└────────────────────────────────────────────────────┘

按 output_type 格式化对话 → 返回

真实实现的骨架(Agent._run,structs/agent.py:1594-1814):

  • 循环条件:while self.max_loops == "auto" or loop_count < self.max_loops(structs/agent.py:1616)。
  • 每轮先把历史拼成字符串:task_prompt = self.short_memory.return_history_as_string()(structs/agent.py:1668)。
  • 调模型:response = self.call_llm(task=task_prompt, current_loop=loop_count, ...)(structs/agent.py:1701)。
  • 解析并写回记忆:response = self.parse_llm_output(response)self.short_memory.add(role=self.agent_name, content=response)(structs/agent.py:1717-1722)。
  • 若回答是工具调用列表 → self.tool_execution_retry(response, loop_count)(structs/agent.py:1788)。

一个容易忽略的细节:重试是内层循环。 每一轮的模型调用外面还套了一层 while attempt < self.retry_attempts and not success(structs/agent.py:1674),模型/网络出错会自动重试(默认 retry_attempts=3),重试用尽再考虑 fallback 模型。

3. 记忆:Conversation 与 MEMORY.md

这节讲什么: Agent 的“记事本”长什么样、怎么跨重启还记得事。

Agent 的 short_memory 是一个 Conversation 对象,在 short_memory_init(structs/agent.py:1021)里创建。它同时承担两个角色:

  1. 本轮上下文 —— 每轮把 system_prompt + 历史 拼给模型。
  2. 持久记忆 —— 当 persistent_memory=True(默认),它把交互追加写到磁盘上的 MEMORY.md

MEMORY.md 的路径按 agent_name 落盘(structs/agent.py:1042-1044):

{WORKSPACE_DIR}/agents/{agent_name}/MEMORY.md

源码注释点破了一个设计取舍:key 只用 agent_name不带 self.id——因为 id 每次进程启动都是新 uuid,若带上就会每次生成一个空的新 MEMORY.md(structs/agent.py:1033-1035)。这意味着:同名 Agent 会共享同一份持久记忆;给多个 Agent 起同一个 agent_name 会让它们互相污染记忆(第 4 章“边界”会再提)。

压缩:上下文快满时自动瘦身。 构造时若 context_compression=True,会挂一个 ContextCompressor(threshold=0.9)(structs/agent.py:527-529);主循环每轮开头调 self._context_compressor.maybe_compress(self)(structs/agent.py:1624-1625),token 用量越过 90% 时压缩/改写记忆,避免撞上下文墙。

4. 工具调用:Python 函数怎么变成“手脚”

这节讲什么: 你传一个普通 Python 函数,模型怎么就能“调用”它。

思路。 大模型的“函数调用”能力需要一份 JSON schema(函数名、参数、类型)。Swarms 帮你从函数签名 + docstring 自动生成这份 schema,再在模型说“我要调 X(参数=…)”时,把它路由回真正的 Python 函数执行。

三步链路:

你的函数 def get_price(ticker: str) -> str: """..."""
│ ① tool_handling: 签名+docstring → OpenAI 函数 schema

tools_list_dictionary ──② 随 call_llm 一起发给模型──► 模型
▲ │ 回:调用 get_price(ticker="AAPL")
│ ③ execute_tools: 按名字找回真函数并执行 │
└─────────────── 结果写回对话(role="Tool Executor") ◄┘

① 生成 schema(Agent.tool_handling,structs/agent.py:933):调 convert_multiple_functions_to_openai_function_schema(self.tools)(structs/agent.py:994-996)把每个函数转成 {"type":"function","function":{name, description, parameters}},存进 tools_list_dictionary

② 发给模型:llm_handling(structs/agent.py:1065)把 tools_list_dictionary 塞进 LiteLLM 配置;有 ≥2 个工具或配了 MCP 时开启并行工具调用 parallel_tool_calls=True(structs/agent.py:1092-1099)。

③ 执行(Agent.execute_tools,structs/agent.py:6038):把模型返回的工具调用交给 self.tool_struct.execute_function_calls_from_api_response(response)(structs/agent.py:6169),执行结果以 role="Tool Executor" 写回对话(structs/agent.py:6182-6185)——这样下一轮模型就“看得到”工具的输出。真正的执行器 BaseTool.execute_function_calls_from_api_response(swarms/tools/base_tool.py:2194)兼容 OpenAI/Anthropic 两种响应格式,默认用线程池并行执行多个调用(max_workers=4)。

教学示例:一个真能用的工具 Agent(示意,非源码):

from swarms import Agent

def get_stock_price(ticker: str) -> str:
"""查询某股票代码的当前价格。

Args:
ticker: 股票代码,例如 'AAPL'。
"""
# 这里放你的真实实现
return f"{ticker}: $123.45"

agent = Agent(
agent_name="StockAnalyst",
model_name="gpt-4o-mini",
tools=[get_stock_price], # 只需传函数,schema 自动生成
max_loops=3, # 允许它“调工具→看结果→再答”多轮
)
print(agent.run("苹果和微软现在多少钱?"))

重点看:你从头到尾没写任何 JSON schema——tool_handling 自动从 get_stock_price 的类型注解和 docstring 生成了它。

5. 自主模式:max_loops="auto" 的 plan→execute→summary

这节讲什么: 最有意思的一档。设 max_loops="auto",Agent 不再跑固定轮数,而是切换成一个“先规划、再逐子任务执行、最后总结”的自主结构。

run()max_loops == "auto" 时不走普通 _run,而是走 _run_autonomous_loop(structs/agent.py:2105,由 run()structs/agent.py:4800-4808 分流)。它是三阶段:

Phase 1 规划 create_plan 工具 → 把任务拆成带依赖/优先级的子任务列表


Phase 2 执行 逐个子任务:think(思考) → 调工具(动作) → observe(观察)
│ 防呆:连续 think 不超过 max_consecutive_thinks(=2)
▼ 每个子任务有迭代上限,总执行也有上限
Phase 3 总结 complete_task → 生成综合最终答复

它自动获得一批“内置工具”。 进入自主模式时,get_autonomous_planning_tools() 提供的规划工具会被注入,并注册对应处理器(structs/agent.py:2263-2302):

工具用途
create_plan生成子任务计划
think显式思考一步(若设了 thinking_tokens 则移除,避免与原生扩展思考重复,structs/agent.py:2213-2218)
subtask_done / complete_task标记子任务/整体完成
create_file / update_file / read_file / list_directory / delete_file文件读写
run_bash / grep跑 shell、搜文件
create_sub_agent / assign_task / check_sub_agent_status动态派生子 Agent 并派活

直觉: 固定 max_loops=N 像“想 N 步就交卷”;"auto" 像“给你纸笔和工具,自己列提纲、一条条做、做完再交”。适合开放式研究这类步数事先不知道的任务;代价是可能循环较久,生产环境更推荐显式的整数 max_loops

6. 关键细节 / 坑

  • context_length 会被构造函数强制改成 16000。 你在 Agent(context_length=...) 传的值,以及初始化中途的赋值,都会在 structs/agent.py:514 被一句 self.context_length = 16000 覆盖(上一行还留着基于模型算 max tokens 的注释代码)。所以别指望这个参数按你传的生效。
  • tools_list_dictionary 默认是可变的 [] 默认参数写成 tools_list_dictionary: Optional[...] = [](structs/agent.py:388),是 Python 里“可变默认参数”这一经典写法;框架内部会先做 None 检查再 extend,实际使用中通常在实例上重新赋值,但读代码时要留意这一点。
  • retry_attempts 是每轮模型调用级别的重试,不是整个 run 的重试;fallback 模型在所有重试失败后才登场(run()except 分支,structs/agent.py:4826-4846)。

下一章看这些 Agent 是怎么被“串起来”的 → 02-orchestration-engine.md