跳到主要内容

第 2 章 · agent 主循环

本章讲 agent 怎么「动」:谁来跑、一步里做什么、何时继续、何时停。重点对比 v3 极简循环与老 MemGPT 的心跳模型。

2.1 谁来跑:AgentLoop 工厂按类型分发

一个 agent 该用哪种循环实现,由 AgentLoop.load() 这个工厂按 agent_type 决定(letta/agents/agent_loop.py:18):

# 真实源码节选,agent_loop.py:20 —— 按类型选实现
if agent_state.agent_type in [AgentType.letta_v1_agent, AgentType.sleeptime_agent]:
if agent_state.enable_sleeptime:
return SleeptimeMultiAgentV4(...) # 带后台「睡眠时间」记忆 agent
return LettaAgentV3(...) # 当前主力实现
elif agent_state.enable_sleeptime and ...:
return SleeptimeMultiAgentV3(...)
else:
return LettaAgentV2(...) # 旧实现

要点:

  • LettaAgentV3 是当前主力(letta_v1_agent 类型),继承自 LettaAgentV2,把循环「简化 + 通用化」(agents/letta_agent_v3.py:100-110)。
  • sleeptime 系列是「多 agent」变体:在你正常对话之外,有一个后台记忆 agent在「睡眠时间」整理记忆(见 2.5)。
  • Letta 同时保留了 v2/v3 和多种 agent 类型枚举(schemas/enums.py:81:memgpt_agentmemgpt_v2_agentletta_v1_agentreact_agentworkflow_agentvoice_* 等)——这是个还在演进、向后兼容多代实现的活项目。

2.2 v3 的循环规则:一句话——「调工具就继续」

老 MemGPT 的循环靠「心跳(heartbeat)」:模型调函数时可以请求一个心跳,函数跑完后系统再唤醒它,从而把多次函数调用串起来。老 prompt 里就明说了这点(prompts/system_prompts/memgpt_v2_chat.py:22-23):「你的大脑会定时心跳运行……调用函数时还能请求心跳事件,函数完成后再次运行你的程序」。

v3 把这套砍掉了,换成极简规则。看 _decide_continuation 的 docstring(agents/letta_agent_v3.py:1977-1985):

1. 没调工具? 循环结束。
2. 调了工具? 循环继续(无论工具成功/失败/被规则拦截)。

类比:「想继续就再调一个工具,想交回控制权就直接回话(不调工具)。」 系统提示里也是这么告诉模型的(prompts/system_prompts/letta_v1.py:22):

Continue executing and calling tools until the current task is complete or you need user input. To continue: call another tool. To yield control: end your response without calling a tool.

所以 send_message(给用户发话)本身不带「继续」语义——发完话又没调别的工具,循环自然停。这比心跳模型干净得多。

2.3 主循环骨架:step() 跑 max_steps 步

外层 step()(agents/letta_agent_v3.py:222)就是一个跑最多 max_steps(默认 DEFAULT_MAX_STEPS = 50,constants.py:75)步的 for 循环,每步调一次内层 _step():

# 真实源码精简(letta_agent_v3.py:328-395,略去额度检查/取消两个分支;break 在 :386,max_steps 收尾在 :394)
for i in range(max_steps):
response = self._step(
messages=list(self.in_context_messages + input_messages_to_persist),
input_messages_to_persist=input_messages_to_persist,
llm_adapter=llm_adapter, run_id=run_id, ...
)
async for chunk in response:
response_letta_messages.append(chunk)
if not self.should_continue: # _decide_continuation 设的标志
break
if i == max_steps - 1 and self.stop_reason is None:
self.stop_reason = LettaStopReason(stop_reason=StopReasonType.max_steps.value)

重点看: 循环是否继续,完全由 self.should_continue 控制,而它由每步末尾的 _decide_continuation 设置。跑满 50 步还没停,就以 max_steps 作为停止原因收尾。

2.4 一步内部(_step)做了什么

_step()(agents/letta_agent_v3.py:895)是真正干活的地方,一步内大致是:

组装上下文 调模型 执行工具 + 决定续步 持久化
┌───────────┐ ┌───────────┐ ┌──────────────────────┐ ┌──────────┐
│ 重建系统提示 │ ──▶ │ build req │ ──▶ │ _handle_ai_response │──▶ │ checkpoint│
│ (只在变化时)│ │ + invoke │ │ 执行工具/审批/续步判断 │ │ 写消息 │
└───────────┘ └───────────┘ └──────────────────────┘ └──────────┘
│ 若 ContextWindowExceeded │
└──────────────▶ compact 压缩后重试 ───────────┘

几个值得记住的点:

  • 并行工具调用会按 provider 和「有没有 tool rule」动态开关:无 tool rule 且 config 允许时才放开并行(letta_agent_v3.py:1112-1147,对 anthropic/openai/gemini 各有处理)。
  • 工具返回截断。 单个工具返回过长会被截断,上限按「约 20% 上下文窗口 × 4 字符/token,最少 5000 字符」算(_compute_tool_return_truncation_chars,letta_agent_v3.py:143-153)——防一个工具结果吃掉整个上下文。
  • 上下文超限即压缩重试。 调模型若抛 ContextWindowExceededError,会触发 compact_messages 压缩后重试(letta_agent_v3.py:1218-1247),详见第 4 章。

2.5 续步判断的完整逻辑(_decide_continuation)

虽然规则一句话能说清,但 _decide_continuation(letta_agent_v3.py:1967)还要处理 tool rules(工具调用约束)。要点:

情况结果
没调工具,但有「退出前必须调」的工具还没调继续,并给模型一句 ToolRuleViolated 提示(:1994-1997)
没调工具,无未尽工具结束(end_turn);若是模型撞了 max_tokens 则记 max_tokens_exceeded(:2000-2002)
调了终止工具(terminal tool)停,停止原因 tool_rule(:2010-2012)
调了普通工具继续(:2014-2020 处理 child / continue 规则)
已是最后一步强制停,记 max_steps(:2023-2025)

Tool rules 是 Letta 的一个有用机制:可以声明「某工具是终止工具」「调了 A 必须接着调 B(child)」「某工具必须在退出前至少调一次」等。审批类工具(requires_approval)则会在 _handle_ai_response 里被拦下,返回 requires_approval 停止原因、等用户批准(letta_agent_v3.py:1681-1709)。

2.6 sleeptime:后台整理记忆的「第二个 agent」

enable_sleeptime=True,主力换成 SleeptimeMultiAgentV4(agent_loop.py:35)。直觉:你和「对话 agent」聊天,另一个「记忆 agent」在后台(对话之外的「睡眠时间」)整理 core memory / 归档,让主对话 agent 不必在对话中频繁停下来自我整理。SleeptimeMultiAgentV3 断言它绑定的 group 必须是 ManagerType.sleeptime 类型(groups/sleeptime_multi_agent_v3.py:33)。本库未深读其内部 step 逻辑,这里只点出概念。

→ 继续 03-system-prompt-assembly.md

代码地图

主题文件符号
按类型选循环实现letta/agents/agent_loop.pyAgentLoop.load
v3 主力实现letta/agents/letta_agent_v3.pyLettaAgentV3
外层多步循环letta/agents/letta_agent_v3.pystep(for i in range(max_steps))
单步执行letta/agents/letta_agent_v3.py_step
工具执行 + 审批letta/agents/letta_agent_v3.py_handle_ai_response
续步规则letta/agents/letta_agent_v3.py_decide_continuation
agent 类型枚举letta/schemas/enums.pyAgentType
基座系统提示(v1)letta/prompts/system_prompts/letta_v1.pyPROMPT
老心跳模型(对比)letta/prompts/system_prompts/memgpt_v2_chat.py(prompt 文本)