news 2026/9/1 3:20:15

Agent多轮对话的上下文失控:SKILL.state显式状态管理解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent多轮对话的上下文失控:SKILL.state显式状态管理解析

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 任务里遇到过的上下文失控问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 3:19:45

SpringBoot+微信小程序垃圾分类系统设计与实现全解析

简介:这是一套基于Spring Boot的微信垃圾分类小程序完整项目资料,包含可运行的前后端代码、毕业论文和答辩PPT,适合计算机相关专业学生用于课程设计、毕业设计或小程序开发入门学习。资源共827个文件,压缩包约21.28MB,…

作者头像 李华
网站建设 2026/9/1 3:18:34

从录播到成品:阿萨Aza《Simon》歌切完整制作流程

最近在整理阿萨Aza直播歌切素材时,发现不少朋友对“歌切”制作流程感兴趣,尤其是《Simon》这类歌曲,现场状态和录音棚版本区别很大,切片处理得好不好,几乎直接决定投稿的听感。但网上关于歌切的教程大多是成品展示&…

作者头像 李华
网站建设 2026/9/1 3:18:24

在 Unity 中复刻 Unreal EQS:从零实现 AI 环境查询系统

如果 AI 角色只会“朝玩家直线冲过去”,你很快会发现游戏关卡里的 AI 行为漏洞百出:它不会找掩体、不会拉开距离、不会选一个视野盲区再靠近目标。Unreal 的 EQS(Environment Query System,环境查询系统)正是为了解决这…

作者头像 李华
网站建设 2026/9/1 3:17:11

【计算机毕业设计单片机案例】基于单片机的多参数水体状态感知与声光报警系统设计 基于 STM32 或 51 单片机的按键可调阈值水质监测设备设计(021605)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/1 3:17:04

百度网盘AI大赛水印消除第二名:数据合成与训练全解析

简介:本资源是百度网盘AI大赛‘水印智能消除赛’亚军方案的完整开源实现,面向图像处理、计算机视觉方向的Python开发者与深度学习实践者,聚焦真实场景下复杂水印的鲁棒性去除问题。压缩包共21个文件,总计87.89MB,包含7…

作者头像 李华