2025 年做 Agent 应用,很多人已经不敢把“多轮对话能力”当成卖点了。原因很简单:对话轮数一多,模型就会开始“犯迷糊”——前面说过的约束记不住,工具调用的中间结果被冲散,Token 成本却一路飙升。你问它为什么重复调用同一个接口,它自己也不知道。
Google 研究者最近提出的 SKILL.state,恰好戳中了这个痛点。它的核心思路非常直接:不要继续往对话历史里塞信息,把 Agent 的执行状态显式地存下来。这个“状态”不是一段文字描述,而是结构化的、可读取、可回滚、可复用的执行现场。
这篇文章我先把 SKILL.state 解决的真实问题讲透,再拆解它和传统对话历史方案的机制差异,然后给出一个可以在自己项目里验证的最小示例。无论你是做 LLM 应用开发,还是正在研究 Agent 架构,这篇文章都值得读完再收藏。
1. 智能体对话历史正在成为系统瓶颈
先看一个非常常见的 Agent 工作流:一个客服机器人,需要理解用户诉求,调用订单查询接口,再根据返回结果生成回答。
用户:帮我查一下 7 月份的订单。 Agent:好的,我来查询您的订单。 Agent(调用查询接口,返回 12 条订单记录):您 7 月共有 12 条订单。 用户:金额最大的一笔是多少? Agent:我查一下……在传统实现里,每一次工具调用、每一次模型输出都被拼接到对话历史里。第 3 轮、第 5 轮还能应对,到第 20 轮、第 50 轮,问题就出现了:
- 上下文长度被中间结果占满,真正重要的用户意图反而被稀释;
- 模型需要从大量历史文本中“回忆”当前任务的目标,容易出现遗漏和幻觉;
- API 费用按 Token 计算,上下文越长,单轮成本越高;
- 系统无法从历史中恢复某个中间状态,一旦某一轮出错,只能从头再来。
这是一个典型的工程问题:对话历史把“状态”隐式地编码在文本流里。模型每生成一次回复,都要重新从这堆文本里推断当前进度、已完成任务、下一步该做什么。这种设计在小规模场景中可用,一旦任务复杂度上升,就成了系统瓶颈。
SKILL.state 想改变的是这个底层假设:状态不应该靠模型从文本里“猜”,而应该被显式地写出来。
2. 显式执行状态的核心思想
要理解 SKILL.state,先要理解“隐式状态”和“显式状态”的区别。
隐式状态就是对话历史本身。比如“用户已经确认了收货地址”“我已经查到了物流单号”,这些信息并没有被单独存储,而是作为自然语言混在聊天记录里。模型每次推理都需要重新理解这些信息。
显式状态则完全不同。它把 Agent 在某一个时刻的完整运行情况,用结构化数据保存下来,包括:
- 当前任务的目标和子任务列表;
- 已经完成和正在执行的步骤;
- 从工具和环境获取的关键数据;
- 中间变量、步骤编号、执行时间;
- 标记当前所在的 Skill(技能)和执行位置。
从论文标题可以判断,SKILL.state 的落点是把状态写入代码文件本身。也就是说,Agent 执行到某个阶段时,当前的进度会被写进一个可读的状态文件。下一次模型继续执行时,不再需要重新阅读整段对话历史,只需要读取这个状态文件。
这个设计背后的关键洞察是:对话历史是“流水账”,而执行状态是“账本”。流水账记录了每个字,但无法直接告诉你“现在进行到哪一步”;账本则直接给出当前结论。
从工程角度看,这个转换带来了几个直接收益:
- 上下文长度不再随执行轮数线性增长;
- 状态文件可以被人类审查、修改、恢复,而不是只能靠模型理解;
- 系统崩溃或任务中断后,可以从最近的显式状态继续执行,而不是从头开始。
理解了这个核心差异,再看 SKILL.state 的机制就会轻松很多。
3. SKILL.state 的工作机制拆解
虽然论文的完整方法细节需要等正式版本,但从命名和思路可以合理推断其工作机制。SKILL.state 的“SKILL”延续了此前 Google 研究者提出的“写代码技能”思路,state 则是其关键补充。
3.1 状态文件的存储结构
执行状态最自然的载体是结构化文件。以 YAML 或 JSON 格式存储,既能被模型读取,也能被程序解析。
一个典型的状态文件可能长这样:
# skill_state.yaml skill_name: order_query current_step: 3 total_steps: 5 goal: "查询用户7月订单并返回金额最大的一笔" completed_steps: - 1. 获取用户身份信息 - 2. 调用订单查询接口 - 3. 筛选金额最大的订单 current_data: max_order_id: "ORD-202507-0088" max_amount: 3299.00 order_count: 12 variables: user_id: "U123456" query_month: "2025-07" next_action: "生成最终回复并附带订单编号"这个文件保存的是“Agent 执行到这一步时,已经确定了什么、还需要做什么”。模型拿到它后,不需要翻找对话历史里的订单查询结果,所有关键数据都直接可见。
3.2 状态更新与恢复流程
SKILL.state 的完整执行循环可以概括为:
初始化状态 -> 执行步骤 -> 更新状态文件 -> 判断是否完成 -> 继续或退出每一步执行完毕后,Agent 都要同步更新状态文件。因此,在任意时刻打断执行,下一次都能从状态文件恢复。
这里有一个被很多人忽略的细节:状态文件本身还承担了“计划检核”的作用。每一步执行前,模型都要查看状态文件中的当前步骤和下一步计划,而不是从对话历史里去推断“我该干什么了”。这大大降低了模型跑偏的概率。
3.3 状态与代码的融合
SKILL.state 的另一层含义是“Skill 与状态绑定”。当 Agent 正在执行某一个 Skill(比如“订单查询”)时,状态文件中记录的是这个 Skill 专属的字段;当 Skill 切换到“售后处理”时,状态文件也会随之切换。
这种“技能+状态”的设计,比单一的长对话历史更接近人类的执行方式——我不是靠回忆前面说了什么来干活,而是靠一份随时更新的任务清单。
4. 和主流方案对比:为什么不继续堆上下文
要评估 SKILL.state 的价值,需要把它放到几个已有的技术路线中对比。目前业界应对长任务 Agent 的方案主要有三种。
4.1 对话历史拼接
最朴素的方式,把全部历史消息都塞给模型。优点是实现简单,缺点是上下文长度不可控、成本高、模型注意力分散。对需要精确记忆的任务,轮数超过一定阈值后性能会明显下滑。
4.2 向量检索 + 记忆摘要
用向量数据库或摘要压缩历史信息,只在需要时检索相关片段。这套方案的问题是:摘要会丢失关键数字和步骤细节。比如订单金额、日期、编号这类精确信息,一旦在摘要阶段被略过,后续就无法找回。
4.3 外部数据库存储业务状态
许多成熟的 Agent 框架会把业务数据存到数据库,但“Agent 的执行状态”(当前执行到哪个步骤、下一步做什么)仍然放在对话历史里。这相当于只解决了数据存放问题,没有解决执行状态管理问题。
4.4 SKILL.state 的定位
SKILL.state 填补的正是“Agent 执行状态”这一层。它不排斥对话历史,不排斥检索,也不排斥数据库。它改变的是:Agent 的“现在进行到哪一步”被显式记录了,而不是让模型从文本流里反复推断。
| 维度 | 对话历史拼接 | 向量记忆/摘要 | 业务状态入库 | SKILL.state |
|---|---|---|---|---|
| 状态保存载体 | 对话消息数组 | 向量库/摘要文本 | 业务数据库 | 结构化状态文件 |
| 状态可读性 | 差,需模型推断 | 一般,依赖检索质量 | 好,但仅限业务数据 | 好,执行状态与业务状态都保留 |
| 上下文占用 | 线性增长 | 压缩后较小 | 小 | 小,只读状态文件 |
| 恢复能力 | 弱,出错需重试 | 一般 | 强,可恢复业务数据 | 强,可恢复完整执行现场 |
| 人工干预 | 困难 | 困难 | 可操作数据库 | 可直接编辑状态文件 |
从这个表格能看出来,SKILL.state 的独特价值不是“多了一种存储”,而是让 Agent 的整个执行过程变得可观测、可干预、可恢复。这对生产环境的意义非常大。
5. 从一个最小示例看状态管理的好处
概念讲完了,直接做验证。即使 SKILL.state 论文还没放出官方开源实现,我们完全可以在自己的 Agent 项目里用这个思路设计状态管理模块。下面用一个订单查询任务做最小示例,验证显式状态的收益。
5.1 项目准备
# 创建项目目录 mkdir skill-state-demo && cd skill-state-demo # 建议使用 Python 3.10+ python3 -m venv venv && source venv/bin/activate # 安装依赖:OpenAI SDK(如果使用国产模型请换为对应 SDK) pip install openai pyyaml说明:这里的重点是验证状态管理机制,不是引入特定模型。实际项目里可以替换为你正在使用的任意 LLM SDK。
5.2 定义状态结构
我们先定义一套简单的状态类:
# state.py from dataclasses import dataclass, field, asdict from typing import Any import yaml @dataclass class AgentState: skill_name: str goal: str current_step: int = 0 total_steps: int = 0 completed_steps: list = field(default_factory=list) variables: dict = field(default_factory=dict) current_data: dict = field(default_factory=dict) next_action: str = "" def to_yaml(self) -> str: return yaml.dump(asdict(self), allow_unicode=True, sort_keys=False) def save(self, file_path: str = "agent_state.yaml"): with open(file_path, "w", encoding="utf-8") as f: f.write(self.to_yaml()) @classmethod def load(cls, file_path: str = "agent_state.yaml") -> "AgentState": with open(file_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) return cls(**data)这个类的核心价值是:无论 Agent 执行到哪里,状态都可以序列化成 YAML 文件,也可以从 YAML 文件精确恢复。这就构成了“显式状态”的基础。
5.3 Agent 主循环
接下来模拟一个 Agent 主循环。注意,这个循环假设所有步骤都通过显式状态来驱动,而不是通过对话历史来驱动:
# agent.py import time from state import AgentState def step_get_user_info(state: AgentState): state.current_data["user_id"] = "U123456" state.completed_steps.append("1. 获取用户身份信息") state.next_action = "调用订单查询接口" def step_query_orders(state: AgentState): # 模拟工具调用返回 state.current_data["order_count"] = 12 state.current_data["orders"] = [ {"order_id": "ORD-202507-001", "amount": 899.00}, {"order_id": "ORD-202507-0088", "amount": 3299.00}, # ... 省略其他订单 ] state.completed_steps.append("2. 调用订单查询接口") state.next_action = "筛选金额最大的订单" def step_find_max_order(state: AgentState): orders = state.current_data.get("orders", []) max_order = max(orders, key=lambda x: x["amount"]) state.current_data["max_order_id"] = max_order["order_id"] state.current_data["max_amount"] = max_order["amount"] state.completed_steps.append("3. 筛选金额最大的订单") state.next_action = "生成最终回复" def run_agent(): state = AgentState( skill_name="order_query", goal="查询用户7月订单并返回金额最大的一笔", total_steps=5 ) steps = [step_get_user_info, step_query_orders, step_find_max_order] for func in steps: print(f"[执行] {func.__name__}") func(state) state.current_step += 1 state.save() print(f"[状态已保存] 当前步骤={state.current_step}") # 模拟真实 Agent 在这里调用 LLM 决定下一步 # 当上下文极长时,LLM 可能需要重新阅读大量历史 # 而在 state 方案中,LLM 只读状态文件即可 time.sleep(0.5) print("\n===== 最终状态 =====") print(state.to_yaml()) if __name__ == "__main__": run_agent()运行结果:
python agent.py预期输出关键信息:
[执行] step_get_user_info [状态已保存] 当前步骤=1 [执行] step_query_orders [状态已保存] 当前步骤=2 [执行] step_find_max_order [状态已保存] 当前步骤=3 ===== 最终状态 ===== skill_name: order_query goal: 查询用户7月订单并返回金额最大的一笔 current_step: 3 total_steps: 5 completed_steps: - 1. 获取用户身份信息 - 2. 调用订单查询接口 - 3. 筛选金额最大的订单 current_data: user_id: U123456 order_count: 12 orders: - order_id: ORD-202507-001 amount: 899.0 - order_id: ORD-202507-0088 amount: 3299.0 next_action: 生成最终回复这就是 SKILL.state 的最小验证:Agent 每一步都更新状态文件,无论何时中断,只读这个文件就能知道“我进行到哪一步、下一步做什么、关键数据是什么”。
5.4 从状态恢复执行
模拟中断后恢复:
# resume_demo.py from state import AgentState def resume_from_state(): state = AgentState.load("agent_state.yaml") print(f"恢复执行,当前步骤: {state.current_step}") print(f"下一步动作: {state.next_action}") print(f"已完成: {state.completed_steps}") # 根据状态决定下一步,而不需要回溯对话历史 if state.next_action == "生成最终回复": print(f"最终回复: 您的最大订单是 {state.current_data['max_order_id']}," f"金额 {state.current_data['max_amount']} 元") if __name__ == "__main__": resume_from_state()这个恢复能力在真实项目中意味着:Agent 任务执行到一半时如果出现 API 超时、进程崩溃、模型上下文溢出,都可以从最近一个状态文件恢复,而不是让用户重新描述需求。
6. 如何验证状态设计得好不好
如果要在自己的项目中引入显式状态,可以从四个维度验证方案是否有效。
6.1 上下文长度变化
统计同一个任务在使用状态文件前后,发送给模型的 Token 数变化。理想情况下,随着任务复杂度上升,显式状态方案的 Token 消耗应远低于纯对话历史方案。
6.2 任务恢复成功率
人为制造中断(进程 kill、API 模拟超时),统计从状态恢复后继续完成任务的成功率。对比纯对话历史方案从历史中“重试”的成功率。
6.3 状态文件可读性
把状态文件交给另一个开发者阅读,看他能否在 30 秒内说出“Agent 当前在做什么、下一步做什么”。如果做不到,说明状态文件的结构设计还有问题。
6.4 状态粒度是否合适
状态文件并非越细越好。如果每个临时变量都写入状态文件,文件会变得冗长,反而增加模型读取成本。更合理的做法是只保留对后续决策有影响的关键信息。
7. 适合用显式状态的场景与不适合的场景
任何架构都不是银弹,SKILL.state 也一样。写清楚适用边界,比盲目跟风更重要。
7.1 适合的场景
- 多步骤工具调用型 Agent:Agent 需要依次调用多个工具,后一步依赖前一步的精确输出;
- 长时间运行的任务:耗时的数据处理、异步审批流、定时任务等;
- 需要人工审查和干预的场景:金融、运维、法务等领域,开发或操作人员需要查看 Agent 的执行依据;
- 资源受限的场景:上下文窗口有限,无法承载长对话历史的模型;
- 需要可恢复性的生产系统:任何不允许“从头再来”的业务场景。
7.2 不适合的场景
- 简单问答型对话:查个天气、讲个笑话,根本不需要状态文件;
- 需要完整对话氛围的场景:情感陪伴类、闲聊类应用,用户要的是“聊天的感觉”,显式状态反而破坏对话流畅性;
- 极端追求低延迟的场景:每次读写状态文件会引入额外 IO 开销。
这里要强调一点:SKILL.state 不是要替代对话历史,而是让对话历史回归“对话”本身。用户说了什么、语气如何、情绪怎样,这些仍需要历史记录;但任务的执行细节、步骤进度、中间结果,应该交给状态文件。
8. 落地到工程的最佳实践
如果你决定在项目中引入显式执行状态,下面这些实践建议值得参考。
8.1 状态文件必须版本化
既然状态文件是“执行现场”,就必须支持回滚。建议状态文件名带上步骤序号或时间戳:
agent_state_step_01.yaml agent_state_step_02.yaml agent_state_step_03.yaml这样任何一步出错,都可以回滚到前一个状态节点。
8.2 状态中不保存敏感明文
状态文件会落到磁盘,甚至进入版本库。如果中间结果包含用户手机号、身份证号、Token 密钥,必须先脱敏或加密再写入状态文件:
# 示例:简单的字段过滤 SENSITIVE_FIELDS = {"access_token", "user_phone", "id_card"} def sanitize_state(data: dict) -> dict: return {k: ("***" if k in SENSITIVE_FIELDS else v) for k, v in data.items()}8.3 状态更新必须只做增量
不要让 Agent 每次把全部状态重写一遍。正确做法是每次只更新变更字段,保留历史状态文件作为审计痕迹。这既能减少 IO,也能在出问题时定位是哪一步写入了异常数据。
8.4 给状态文件设置最大保留时间
长期运行的系统会产生大量状态文件。建议设置保留策略,比如仅保留最近 N 个步骤状态,或按时间清理。清理逻辑要保证至少保留一个可恢复的检查点。
8.5 状态文件结构要能被程序自动校验
定义一个 JSON Schema 或 Pydantic 模型来校验状态文件结构。如果状态文件被写坏,Agent 应该立即停止,而不是拿着半截状态继续跑。
# 示例:Pydantic 校验(需安装 pydantic) from pydantic import BaseModel, Field from typing import List, Dict class AgentStateModel(BaseModel): skill_name: str goal: str current_step: int = Field(ge=0) total_steps: int = Field(ge=0) completed_steps: List[str] = [] variables: Dict[str, str] = {} current_data: Dict = {} next_action: str = ""8.6 状态切换要显式声明
当 Agent 从一个 Skill 切换到另一个 Skill 时,不要只改状态字段,应该显式地记录切换事件:
skill_transition: from: order_query to: after_sales reason: "用户要求申请退款" timestamp: "2025-07-01T10:30:00Z"这样在排查问题时,可以准确还原 Agent 的决策路径。
9. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 状态文件过大 | 把临时变量和完整工具输出都写入了状态 | 查看状态文件大小和各字段占比 | 只保留关键字段,长文本放外部存储 |
| 恢复后 Agent 行为异常 | 状态文件中缺少必要上下文 | 对比正常执行和异常执行的状态差异 | 补充缺失字段,增加状态校验 |
| 状态文件被写坏 | 并发写入或多个 Agent 共享同一状态文件 | 检查写入日志和文件锁 | 每个任务使用独立状态文件,加文件锁 |
| 状态更新滞后 | Agent 先调用模型再更新状态 | 检查执行日志中的步骤顺序 | 先更新状态再让模型决策下一步 |
| 上下文 Token 未下降 | 状态文件仍然被完整拼接到提示词中 | 查看发送给模型的 Prompt 结构和 Token 分布 | 只提取当前步骤和下一步所需字段 |
| 敏感信息泄露 | 状态文件未脱敏 | 扫描状态文件内容 | 增加脱敏过滤和访问权限控制 |
10. 局限性分析与后续方向
SKILL.state 提供了很好的思路,但从工程落地角度看,还有一些问题需要关注。
第一,状态结构的设计依赖具体的业务场景。order_query的状态字段和code_generation的状态字段完全不同。如果 Agent 经常跨领域切换,状态文件的 schema 设计就会成为新的维护成本。
第二,状态文件本身仍然需要“被模型理解”。虽然不再是长篇对话历史,但模型还是要从 YAML 或 JSON 中提取状态信息。字段命名是否清晰、层级是否合理,会直接影响模型的理解正确率。
第三,论文未明确覆盖“多 Agent 协作”场景。多个 Agent 共享状态时的并发控制、状态冲突解决、跨 Agent 状态引用,还需要更完整的方案。
后续值得关注的方向包括:状态文件与向量检索结合,在状态文件中只保存指针而把详细内容放到外部存储;状态 schema 的自动生成与演进;以及基于显式状态的 Agent 执行可视化调试工具。这些方向如果落地顺利,Agent 开发会变得越来越像传统软件开发——可调试、可测试、可回滚,而不是依赖不可控的文本生成。
对于开发者来说,现在最值得做的不是等论文开源,而是先在自己现有的 Agent 项目中引入状态文件,哪怕只是一个简单的state.yaml,记录当前步骤和关键字段。用不了太久,你就会感受到显式状态带来的确定性和掌控感。
如果能把这个思路用在你的下一个 Agent 项目里,这篇文章就算有价值了。建议收藏备用,也欢迎在评论里聊聊你在多轮 Agent 任务里遇到过的上下文失控问题。