这次我们来看 Google Research 团队提出的一篇新工作,名字叫 SKILL.state。它的目标非常直接:解决智能体(Agent)在长任务执行中,对话历史越攒越长、越跑越偏的问题。简单说,就是把“靠聊天记录猜状态”改成“用显式执行状态记录进度”。
如果你最近在搭智能体,或者被上下文超长、token 成本飙升、任务做着做着就忘了前面步骤这些问题折磨过,这篇文章值得收藏。下面我会先讲清楚 SKILL.state 到底做什么、和传统对话历史方案的核心差异,再给出一套技术上可以参考的实现与验证思路,最后整理常见坑和工程建议。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目来源 | Google Research 团队提出,论文标题中提到用显式执行状态替代不断增长的智能体对话历史 |
| 核心思想 | 将智能体运行过程中的“对话历史”压缩为结构化的显式执行状态,按需读取、按需更新 |
| 主要功能 | 提升长任务稳定性、降低 token 消耗、减少上下文漂移、便于调试与回放 |
| 适用对象 | 大模型 Agent、多智能体编排、工具调用、任务规划、代码生成与执行 |
| 硬件门槛 | 取决于底层大模型;SKILL.state 本身是一种推理与状态管理方法,不单独要求特定显卡 |
| 支持平台 | 与模型框架无关,可适配主流 Agent 框架,如 LangChain、Dify、自研 Agent 服务 |
| API 能力 | 可以采用 API 方式暴露状态查询与更新接口,方便外部系统接入 |
| 批量任务 | 适合批量任务队列管理,每个任务维护独立状态对象 |
| 主要优势 | 对话历史不再无限增长,状态可查询、可校验、可回放 |
| 潜在局限 | 状态定义需要针对任务设计,不同任务可能有不同的状态字段 |
需要注意的是,论文标题中的“SKILL.state”并不是一个可以直接 pip install 的现成工具名。它更像一种方法论的命名,后续是否有配套开源代码,需要以 Google 官方仓库为准。本文讨论的核心是它背后的思路,以及我们怎么在现有 Agent 工程里落地类似设计。
2. 为什么“对话历史”会成为智能体瓶颈
现在的大模型智能体,最常见的架构是“循环读取历史 + 调用工具 + 继续推理”。每执行一步,就会把用户指令、模型推理、工具返回、中间结果全部追加到上下文里。短任务时没问题,一旦任务链路变长,问题就非常明显。
第一个问题是 token 爆炸。每一步工具返回可能是一大段 JSON、代码日志、文件内容。假设平均一步消耗 2000 token,执行 50 步就是 10 万 token。成本翻倍还是小事,很多模型的上下文窗口直接不够用。
第二个问题是信息稀释。模型需要从越来越长的历史里找到“现在进行到哪一步”“下一步该做什么”。当历史里充满了过时的中间结果、失败尝试、冗余日志,模型很容易被无关信息带偏。
第三个问题是状态不一致。对话历史只是文本,不是结构化数据。模型在读取历史时,可能会对某些字段产生误解。比如某个文件已经重命名,但历史里还有旧路径,模型就会继续引用旧路径。这种问题靠提示词很难根治。
SKILL.state 的思路,就是不再让模型每次翻聊天记录,而是维护一份“当前任务到底处于什么状态”的结构化快照。这个快照不随时间无限膨胀,只保存关键执行信息。
3. SKILL.state 核心思路拆解
从论文标题和摘要来看,SKILL.state 的核心是把智能体执行过程中的“状态”显式建模。它和传统“对话历史”的本质区别在于:
- 对话历史是线性的、增长的、无结构的文本流。
- 执行状态是结构化的、动态更新的、可查询的键值集合。
一个典型的显式执行状态可能包含:
| 状态字段 | 说明 | 示例 |
|---|---|---|
| task_goal | 当前任务的最终目标 | 将用户上传的 CSV 数据清洗并生成可视化报告 |
| current_step | 当前已经执行到第几步 | 第 3 步:数据清洗 |
| completed_steps | 已经完成的关键步骤列表 | 文件读取、字段映射 |
| pending_steps | 待执行的步骤列表 | 生成图表、导出 PDF |
| active_tool | 当前正在使用的工具 | code_interpreter |
| key_variables | 任务运行中需要长期保留的变量 | 数据文件路径、维度字段名 |
| last_error | 最近一次失败的错误信息 | 内存不足,已重试 1 次 |
| constraints | 用户指定的硬性约束 | 输出文件必须为 PDF |
模型在每一步决策前,不再读取整段历史,而是读取这个结构化的状态对象。需要更详细的中间过程时,再按需从日志或存储中取。
这样带来的直接收益是:
- 上下文长度可控,不会随着步数线性增长;
- 模型每次看到的都是当前任务的“摘要帧”,信息密度更高;
- 状态可以被外部系统读取,方便调试、监控、人工干预;
- 任务中断后可以基于状态恢复,而不是从头再来。
4. 显式执行状态 vs 对话历史:对比与权衡
4.1 信息完整性
对话历史保留了所有原始信息,但噪声也多。显式执行状态只保留关键信息,前提是状态设计者知道哪些字段重要。对于探索性任务,状态字段可能覆盖不全,需要设计成“关键字段 + 可扩展索引”。
4.2 上下文成本
| 维度 | 对话历史方案 | 显式执行状态方案 |
|---|---|---|
| 上下文长度 | 随步数线性增长 | 基本稳定,取决于状态字段数量 |
| token 成本 | 高 | 低 |
| 长任务稳定性 | 越往后越容易漂移 | 相对稳定 |
| 调试难度 | 需要翻完整日志 | 直接看状态即可 |
| 实现复杂度 | 低,追加消息即可 | 高,需要状态定义与更新机制 |
4.3 状态一直保持最新吗
这是最关键的问题。显式执行状态如果更新不及时,模型看到的就是过期状态,反而比对话历史更危险。所以状态更新必须和工具调用结果强绑定:每次工具返回后,先更新状态,再让模型基于新状态决策。
4.4 什么时候该用,什么时候不该用
如果任务只有一两轮交互,直接走对话历史更简单。但如果任务是“写代码 -> 跑测试 -> 修 bug -> 再跑测试 -> 部署”,或者“读取多个文件 -> 汇总 -> 生成报告”,每一步都依赖前面产生的新信息,显式执行状态就非常值得引入。
5. 适用场景与使用边界
SKILL.state 的思想可以广泛应用在以下场景:
- 长链路代码生成与执行:Agent 需要反复修改代码、执行、读取报错、再修改。
- 数据分析任务:先读取数据,再清洗,再建模,再出图,每一步产出物要被后续步骤引用。
- 多智能体协作:不同 Agent 负责不同模块,通过共享状态对象同步进度。
- 自动化运维:执行安装、检查、补丁、验证等有明确前后依赖的运维流程。
- 批量任务处理:每个任务维护独立状态,失败后单独恢复。
使用边界方面要特别注意:
- 涉及用户隐私数据时,状态对象不能把敏感字段明文写入日志;
- 工具返回结果可能包含版权内容时,需要先过滤再入库;
- 状态恢复机制不能无限重试,避免对业务系统造成重复操作;
- 不能因为状态压缩导致关键失败信息丢失,至少保留错误索引。
安全合规方面,任何智能体在执行文件操作、网络请求、代码执行等功能时,都必须严格校验权限,并在测试环境验证后再接入生产数据。
6. 技术实现思路与伪代码示例
虽然 SKILL.state 是论文方法,但我们完全可以在现有 Agent 框架里实现类似的显式状态管理。下面给出一套通用的实现思路,代码为示意模板,需要结合具体框架调整。
6.1 定义状态结构
from dataclasses import dataclass, field from typing import Any, Dict, List, Optional @dataclass class AgentState: task_id: str task_goal: str current_step: int = 0 completed_steps: List[str] = field(default_factory=list) pending_steps: List[str] = field(default_factory=list) key_variables: Dict[str, Any] = field(default_factory=dict) last_error: Optional[str] = None error_count: int = 0 status: str = "running" # running / success / failed def update_after_step(self, step_name: str, result: Dict[str, Any]) -> None: self.completed_steps.append(step_name) if step_name in self.pending_steps: self.pending_steps.remove(step_name) self.key_variables.update(result) self.current_step += 1 def to_prompt_block(self) -> str: """将状态转换为供模型读取的紧凑文本。""" lines = [ f"任务目标: {self.task_goal}", f"当前步骤: {self.current_step}", f"已完成: {', '.join(self.completed_steps)}", f"待完成: {', '.join(self.pending_steps)}", f"关键变量: {self.key_variables}", ] if self.last_error: lines.append(f"最近错误: {self.last_error}") return "\n".join(lines)这段代码定义了一个最小的状态对象。实际任务可以按业务需要增加字段,比如当前使用的工具名称、依赖缓存路径、用户授权范围等。
6.2 状态更新与主循环
def run_agent_with_state(user_goal: str, executor) -> AgentState: state = AgentState( task_id="task_001", task_goal=user_goal, pending_steps=["parse_input", "execute_tool", "verify_result"], ) while state.status == "running" and state.current_step < 10: # 构建本次推理的上下文:状态块 + 最新事件,而不是全部历史 prompt = build_prompt_with_state(state) # 模型决策:选择工具和参数 action = executor.llm_decide(prompt) # 执行工具 try: result = executor.call_tool(action.tool, action.args) state.update_after_step(action.tool, {"last_result": result}) except Exception as e: state.last_error = str(e) state.error_count += 1 if state.error_count >= 3: state.status = "failed" break # 判断是否完成 if executor.check_goal_reached(state): state.status = "success" return state这个循环的关键在于prompt每次只包含结构化状态和最新一步的事件,不再拼接全部历史。模型每次决策时看到的上下文较短,且信息集中。
6.3 状态存取与恢复
如果任务中途断掉,可以将状态持久化到 JSON 文件或数据库。
{ "task_id": "task_001", "task_goal": "清洗数据并生成报告", "current_step": 2, "completed_steps": ["parse_input", "execute_tool"], "pending_steps": ["verify_result"], "key_variables": { "file_path": "./data.csv", "schema": {"date": "string", "amount": "float"} }, "status": "running" }恢复时直接加载 JSON 到AgentState,然后从current_step继续执行。这里要注意:恢复前必须确认之前的工具调用不会执行第二次,比如“发送邮件”这类操作不能重复触发。
7. 接口 API 与批量任务设计
状态化的设计对接口和批量任务非常友好。
7.1 状态服务接口
可以单独起一个状态管理服务,提供以下接口:
| 接口 | 方法 | 说明 |
|---|---|---|
| /state/create | POST | 创建任务状态 |
| /state/get | GET | 查询任务状态 |
| /state/update | POST | 更新任务状态 |
| /state/delete | DELETE | 清理任务状态 |
这样 Agent 主服务只负责推理和工具调用,状态由独立服务统一管理。多智能体协作时,每个智能体都可以读取同一个状态对象的不同字段。
7.2 Python 调用示例
import requests base_url = "http://127.0.0.1:8080" # 创建状态 state_payload = { "task_id": "task_002", "task_goal": "批量处理商品图片", "pending_steps": ["download_images", "resize", "upload"] } resp = requests.post(f"{base_url}/state/create", json=state_payload, timeout=10) print(resp.json()) # 查询状态 resp = requests.get(f"{base_url}/state/get", params={"task_id": "task_002"}, timeout=10) print(resp.json())实际项目里建议加权限校验,防止状态被未授权访问。如果是内网部署,也要设置网络访问控制。
7.3 批量任务队列
批量任务场景下,每个任务一个状态对象,用 Redis 或数据库存储。队列消费时只读取状态对象,不读取长对话历史,内存和存储压力会小很多。
一个简单的批量任务设计:
batch_config: input_dir: ./bulk_inputs output_dir: ./bulk_outputs state_store: redis max_retry: 2 on_error: skip_and_log每条任务独立记录失败原因,最后统一汇总。整个队列的并发度可以远高于传统方案,因为单个任务占用的上下文不再随步骤增长。
8. 资源占用观察与性能评估思路
SKILL.state 本身不消耗显存,真正的资源占用取决于底层大模型。但状态化改造会显著影响推理和内存使用,可以从以下维度观察。
8.1 上下文 token 变化
在任务执行过程中,记录每一步传给模型的 token 数量。对比“全量历史方案”和“显式状态方案”的 token 曲线。显式状态方案应该是一条接近水平的线,而全量历史方案是向上增长的斜线。
8.2 推理时延
虽然状态化以后单次输入 token 减少,但状态构建和更新会增加额外代码逻辑。观察平均每步推理延迟,确认收益大于开销。如果状态频繁更新导致额外 I/O,可以考虑批量更新或内存缓存。
8.3 显存占用
显存占用主要看模型参数量、量化方式和 batch size。如果使用同一个基座模型做对比,显存占用不会因为状态化而明显变化。但更大的收益是:由于上下文变短,可以运行更大的模型,或在同样的显卡上支持更高的并发。
8.4 稳定性指标
建议记录以下指标:
- 任务完成率:执行完所有步骤并成功结束的比例。
- 步骤回退率:模型因为状态不一致而被迫回退的步骤比例。
- 无效工具调用率:调用工具参数错误或不符合当前状态的次数。
- 平均每任务 token 消耗。
- 平均每任务执行时长。
通过对比改造前后这些指标,可以量化 SKILL.state 思路的实际价值。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型拿到过期状态 | 状态更新在工具返回之后未及时同步 | 检查工具调用后是否有update_after_step | 强制在每次工具返回后更新状态 |
| 状态字段缺失导致决策失败 | 状态定义不完整,漏掉关键变量 | 查看失败案例中模型缺少的信息 | 补充字段,或设置默认值 |
| token 消耗不降反升 | 状态块过大,或仍拼接了大量历史 | 打印实际送入模型的 prompt | 精简状态字段,历史改成按需检索 |
| 批量任务状态互相污染 | 多个任务共用了同一个状态对象 | 检查任务 ID 隔离 | 每个任务创建独立状态实例 |
| 任务恢复后重复执行操作 | 恢复逻辑没有判断副作用 | 检查恢复路径是否跳过已完成的工具 | 对不可重复操作设置幂等标识 |
| 接口返回状态慢 | 状态存储频繁读写 | 检查数据库/Redis 延迟 | 加缓存,或批量提交更新 |
| 模型在长任务后期忘记目标 | 目标字段被压缩在大量状态中 | 查看状态块中任务目标是否在顶部 | 将 task_goal 固定在 prompt 开头 |
10. 工程落地建议
如果要在自己的 Agent 项目中落地显式执行状态,建议按以下顺序推进。
第一,先找一个小型长任务用例,比如“读取 CSV -> 画图 -> 写结论”。在现有 Agent 代码里加入AgentState,只做状态记录,不改变原有决策逻辑。这一步的目的是观察状态和实际执行的匹配度。
第二,把模型决策的 prompt 改成“状态块 + 当前事件”。先保留全量历史作为兜底,对比几次任务的表现。确认状态化方案更稳定后,再逐步去掉历史拼接。
第三,把状态持久化做好。任务中断恢复是状态化方案最实用的场景之一。建议用数据库存储状态,带上版本号,避免并发更新冲突。
第四,接入批量任务。每个任务独立状态,异步处理,失败单独重试。批量任务队列的代码结构可以复用项目管理里的通用 task runner,降低改动成本。
第五,建立监控和日志。每次状态更新都打日志,方便回放和定位问题。自动记录 token 和耗时,方便对比优化。
这里再强调一次合规边界:如果智能体会执行代码、操作系统文件、访问网络,务必在沙箱环境测试;涉及人脸、声音、隐私数据或版权素材时,必须获得明确授权;批量任务中要设置速率限制和审计日志,避免对第三方系统造成压力。
11. 总结与后续关注重点
SKILL.state 最值得尝试的点,是它提供了一个比“无限拼接对话历史”更可控的Agent记忆方案。它不要求你更换大模型,也不需要重新训练,只需要在业务代码层引入结构化状态管理。
建议最先验证两个方向:一个是长任务稳定性是否提升,另一个是 token 消耗是否下降。如果这两个指标都有改善,就说明这套思路适合当前场景。最容易踩的坑是状态定义过粗或过细,过粗会丢信息,过细会变成另一种形式的长上下文。需要结合具体任务迭代调整。
后续可以继续关注 Google Research 是否放出源码和基准测试结果。如果论文公开了状态 schema 的推荐设计,可以直接借鉴。即使没有开源,我们也可以基于本文的实现思路,在自己项目里建立一套显式状态管理模块。这对长链路 Agent 的工程化来说,是值得投入的方向。