章末自测

题目 1:为什么 Agent 不能只依赖局部变量?

假设 Main Loop 中使用 messages、step、finished、last_error、recent_calls 五个独立变量,并且后面又加入 Human Approval 和 Flask API。请说明这种写法为什么会逐渐失控,以及统一 AgentState 能解决哪些问题。

局部变量在简单 Demo 中没有问题,但功能变多后,它们会散落在不同函数与分支里,导致状态难以整体查看、序列化和恢复。Human Approval 需要暂停后继续,Flask 服务需要把当前状态返回给前端,Tracing 需要记录每一步状态变化,这些都要求一个统一运行对象。AgentState 可以把 Goal、Step、Status、Tool History、Error 等运行事实集中管理,使 Loop、API、Storage、Tracing 都通过同一结构读取和更新状态,从而减少隐式依赖。

题目 2:判断题——只要 Tool 调用失败,Agent 就应该进入 FAILED,对吗?

请判断,并分别举一个“可恢复错误”和“不可恢复错误”的例子。

不对。很多 Tool Error 本身就是有价值的 Observation。例如 read_file("app.py") 返回 FileNotFoundError,Agent 可以先 list_files 再找真实入口,这是典型可恢复错误。不可恢复错误则可能是超过最大步数、用户明确取消任务、权限规则禁止且无替代路径,或者模型服务长期不可用。在这些情况下,任务才更适合进入 FAILED。关键不是“有没有错误”,而是“这个错误是否还存在合理恢复路径”。

题目 3:代码阅读——为什么 running=True、waiting=True 这种状态设计有风险?

请从状态冲突和可读性两个角度说明,并给出更合理的状态表示方式。

多个布尔值很容易形成互相矛盾的组合,例如 running=True、waiting=True、finished=True 同时出现时,很难判断系统真实状态。随着状态越来越多,组合数量会迅速增加,代码中的 if/else 也会越来越复杂。更合理的方式是使用单一主状态,例如 AgentStatus.WAITING_APPROVAL,并通过 Enum 约束取值。这样一个时刻只有一个主要生命周期状态,前端、日志和恢复逻辑都更容易理解。

题目 4:编程实现——用 dataclass 实现一个 AgentState

请写一个完整可运行 Python 程序,定义 AgentStatus 枚举和 AgentState 数据类,至少包含 goal、messages、step、tool_history、status、last_error 六个字段,并演示创建状态、增加 step、记录一次 Tool Call、修改状态。查看参考答案

from dataclasses import dataclass, field
from enum import Enum
from typing import Optional


class AgentStatus(str, Enum):
    READY = "READY"
    THINKING = "THINKING"
    TOOL_CALLING = "TOOL_CALLING"
    OBSERVING = "OBSERVING"
    COMPLETED = "COMPLETED"
    FAILED = "FAILED"


@dataclass
class AgentState:
    goal: str
    messages: list[dict] = field(default_factory=list)
    step: int = 0
    tool_history: list[dict] = field(default_factory=list)
    status: AgentStatus = AgentStatus.READY
    last_error: Optional[str] = None


if __name__ == "__main__":
    state = AgentState(
        goal="检查项目"
    )

    state.status = AgentStatus.THINKING

    state.tool_history.append({
        "tool": "list_files",
        "arguments": {}
    })

    state.step += 1
    state.status = AgentStatus.OBSERVING

    print(state)

这个程序已经具备基础 Agent State 结构。真实项目中我们还会加入当前 Tool Call、Final Answer、时间戳、Run ID 等字段,但初期不需要一次性堆太多。

题目 5:综合应用——WAITING_APPROVAL 为什么必须是显式状态?

Coding Agent 准备执行 delete_file("config.py"),系统要求人工确认。请说明如果没有 WAITING_APPROVAL,任务暂停与恢复会遇到什么问题;如果有这个状态,系统应该额外保存哪些信息。

如果没有显式 WAITING_APPROVAL,系统只能通过零散变量判断“是不是在等人”,暂停后很难知道应该恢复到哪一步。使用 WAITING_APPROVAL 后,任务生命周期非常清楚:Run 未完成,也未失败,而是处于等待确认阶段。此时还应保存当前 Tool Call、参数、需要审批的原因、Step、Goal、已有 Observation 等关键信息。用户批准后,Harness 可以从 WAITING_APPROVAL 恢复到 TOOL_CALLING,再继续执行,而不用从头重跑。

题目 6:对比分析——State、Memory、Trace 分别应该保存什么?

请分别为这三个模块列出至少三个典型字段,并解释为什么不能把它们完全混在一起。

State 典型保存 status、step、last_error、current_tool_call,它们直接描述当前 Run 的运行事实。Memory 可以保存用户偏好、项目长期规则、历史决策等跨 Session 仍有价值的信息。Trace 更适合保存每次 Prompt、LLM Response、Tool Call、Tool Result、Latency、Token 等完整历史证据。三者生命周期和用途不同:State 服务当前控制,Memory 服务长期复用,Trace 服务审计与调试。如果全部混在一个对象里,会造成数据体量失控和职责混乱。

题目 7:概念理解——为什么说 Agent = Loop + State?

请从“Loop 推进 State、State 约束 Loop”两个方向解释这句话,而不是只做字面翻译。

Loop 负责让 Agent 一轮一轮执行:调用模型、选择 Tool、获取 Observation、继续决策。在这个过程中,每一轮都会更新 Step、Status、Tool History、Error 等 State。反过来,State 又决定 Loop 是否还能继续,例如达到最大步数、进入 WAITING_APPROVAL、COMPLETED 或 FAILED 时,Loop 必须改变行为或停止。因此两者不是简单并列关系,而是互相作用:Loop 让 State 演进,State 为 Loop 提供当前事实和控制边界。只有两者结合,Agent 才真正成为可持续运行的系统。

进一步阅读

  • Python dataclasses 官方文档:理解如何用类型化数据对象组织 AgentState,而不是长期依赖松散字典。
  • Python enum 官方文档:适合学习如何表达 READY、THINKING、COMPLETED 等有限状态集合。
  • Finite State Machine 相关资料:帮助理解状态、状态转换和非法流转控制。
  • LangGraph State Concepts:学完本节后再看,会更容易理解为什么图式 Agent 框架强调 State。
  • Temporal Workflow 状态与恢复机制资料:适合观察长期运行任务如何处理暂停、失败与恢复。
  • Event Sourcing 相关概念:用于理解为什么有些系统不只保存当前 State,还保存完整状态变化事件。
  • Checkpoint / Persistence 相关资料:为后面 Session、Run、SQLite 持久化做准备。
  • Agent Tracing / Observability 资料:帮助区分“当前状态”和“历史轨迹”的职责边界。

术语表

  • 智能体状态(Agent State):描述某个 Agent Run 当前运行事实的结构化数据集合,通常包含 Goal、Messages、Step、Tool History、Status、Error 等信息,是 Loop 持续推进与恢复执行的基础。
  • 状态机(State Machine):用有限状态和允许的状态转换描述系统生命周期的模型。在 Agent 中可以明确 READY、THINKING、TOOL_CALLING、WAITING_APPROVAL 等阶段,避免非法状态组合。
  • 运行状态(Status):Agent 当前所处的主要生命周期阶段,例如 READY、THINKING、COMPLETED。它用于控制 Loop、前端展示、恢复逻辑与审计,不应只用一个模糊的 running 代替。
  • 工具历史(Tool History):记录 Agent 已调用过哪些 Tool 及参数的结构化历史。它可用于重复动作检测、审计、Context 构建和行为统计,与自然语言 messages 的用途不同。
  • 等待审批(Waiting Approval):Agent 已经产生高风险动作,但在执行前暂停,等待人工确认的状态。它不是失败,而是一种可恢复暂停,是 Human in the Loop 的关键状态。
  • 终止状态(Terminal State):一旦进入后通常不再继续执行的状态,例如 COMPLETED、FAILED。Main Loop 会根据终止状态决定结束 Run,避免继续调用模型或工具。
  • 运行实例(Run):一次具体 Agent 任务的生命周期实体。State 描述 Run 当前状态,Trace 记录 Run 发生过什么,后面 Flask 服务会以 Run 为核心暴露 API。
  • 状态快照(State Snapshot):某一时刻 Agent State 的完整或部分副本,可用于调试、恢复、Checkpoint 和审计。复杂系统中常会在关键步骤保存 Snapshot。

本章总结

这一节我们正式把 Agent 从“一个会循环的程序”推进成“一个有明确运行状态的系统”。

上一节的 Main Loop 解决的是,Agent 怎么一轮一轮继续做事?

这一节的 State 解决的是,Agent 怎么知道自己现在为什么在做、已经做过什么、正处于什么阶段?

所以我们最终得到一个非常重要的认识,Agent = Loop + State。

Loop 负责推进。

State 负责承载当前运行事实。

最基础的 State 可以包含:

goal
messages
step
tool_history
status

进一步还会出现:

last_error
current_tool_call
final_answer
run_id

这些字段不是为了堆结构,而是服务于真实工程问题。

goal 让 Agent 不丢任务目标。

messages 保存模型交互历史。

step 控制执行轮数。

tool_history 防止重复行为并支持审计。

status 则让整个生命周期清晰可见。

我们还进一步把状态从模糊的:

running

拆成:

READY
THINKING
TOOL_CALLING
OBSERVING
WAITING_APPROVAL
COMPLETED
FAILED

这样一来,Agent 不再是“黑盒里一直跑”,而是每个阶段都可以被观察、控制和恢复。

这一节最容易犯的错误,一是把 State 等同于 messages;二是把所有历史信息都塞进 State;三是把一次 Tool Error 直接当成整个任务失败;四是使用大量互相冲突的布尔变量描述状态。

如果你想验证自己已经真正掌握,可以做两件事。

第一,自己手写一个 AgentState dataclass。

第二,把上一节的 Main Loop 改造成:

READY
 ↓
THINKING
 ↓
TOOL_CALLING
 ↓
OBSERVING
 ↓
THINKING

并且在每轮更新 Step、Tool History 和 Error。

如果你能做到这一点,并且能解释为什么 WAITING_APPROVAL 不是 FAILED,为什么 Tool History 不等于 messages,那么 Agent 的“运行骨架”就已经真正建立起来了。

下一节我们会继续解决另一个非常关键的问题,Agent 要不要和具体模型厂商绑定?

如果今天代码里直接写死 OpenAI、Claude 或某个本地模型,后面切换模型会非常痛苦。

所以下一节我们会进入,模型解耦:设计自己的 LLM Provider

把 Agent Engine 和具体模型调用彻底拆开。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像-大赛人网
欢迎您留下宝贵的见解!
提交
头像-大赛人网

昵称

取消
昵称

    请登录后查看评论内容