资讯动态

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

发布时间:2026/9/1 3:20:18 来源:尧图企业网站定制
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_factorylist) variables: dict field(default_factorydict) current_data: dict field(default_factorydict) next_action: str def to_yaml(self) - str: return yaml.dump(asdict(self), allow_unicodeTrue, sort_keysFalse) def save(self, file_path: str agent_state.yaml): with open(file_path, w, encodingutf-8) as f: f.write(self.to_yaml()) classmethod def load(cls, file_path: str agent_state.yaml) - AgentState: with open(file_path, r, encodingutf-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, keylambda 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_nameorder_query, goal查询用户7月订单并返回金额最大的一笔, total_steps5 ) 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 适合的场景多步骤工具调用型 AgentAgent 需要依次调用多个工具后一步依赖前一步的精确输出长时间运行的任务耗时的数据处理、异步审批流、定时任务等需要人工审查和干预的场景金融、运维、法务等领域开发或操作人员需要查看 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(ge0) total_steps: int Field(ge0) 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 任务里遇到过的上下文失控问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价