这篇重点学什么
本篇重点:按步骤记录观察、动作、奖励和结束原因,区分任务真正结束与预算用尽的截断,再决定哪些内容作为监督训练标签。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
工具调用与轨迹
共同基础模型、工具、观察与行动的交互结构相通。
另一条路线补充什么?
先规定允许调用哪些工具(白名单),再记录调用 ID,处理重复与冲突,并设置执行预算和停止条件。
读完这篇,你能做什么
运行三步走廊环境,区分成功、提前结束和预算用尽;用 Java 审计器拒绝伪造观测与任务结束后的动作。
学习理由、准备要求与知识点
为什么学它
Agent 自称成功,不代表环境里的任务已经完成。环境和轨迹格式要能检查,记录的数据才能继续用于策略训练。
先读这些内容
知识点清单
- 观察与真实状态
- 动作空间与解析
- 决策摘要与环境证据
- 轨迹事件结构
- 任务终止与预算截断
- 可回放环境版本
- 动作掩码与训练标签
- 可执行成功判定
运行前先确认ReAct论文v3、Gymnasium v1.2.0接口基线;Java17算法与Python3.14标准库确定环境。未执行语言模型、GPU或真实外部工具。
01先弄懂它是什么
研究 Agent 是否会做事,要看它实际怎样与环境一步步交互。例如在时间 t,策略收到观测 o_t,根据已有历史选择动作 a_t,环境执行后再返回新观测、奖励和结束信息。这样从开始到结束的记录叫轨迹。真实状态 s_t 还可能包含它看不见的网页内部信息或任务目标;如果把完整答案直接放进观测,就等于提前泄露了本该通过任务获得的信息。
ReAct 把语言推理和任务动作交替安排,让模型收到外部反馈后调整下一步。实验中可以记录简短决策摘要、结构化动作和环境返回,但摘要只是记录方式,不能当作模型内部计算过程的可靠证明。例如说“查到了资料”,应有工具实际返回作证;说“任务完成”,应检查最终环境状态,不能让策略自己写一个 success=true 就算过关。
一条训练记录至少区分任务标识、环境版本、初始条件、动作、观测、奖励和终止原因。若将工具返回文本和模型动作全部拼成无角色长字符串,后续做模仿学习或策略梯度时容易把环境输出当成策略生成标签。先建立可执行的小环境,能够在引入语言模型前验证这些数据边界。
文中这几个词,先认一认
- 观测
- 环境允许策略看到的内容,可能只是完整状态的一部分。
- 轨迹
- 一次任务从初始条件出发,到完成、终止或被截断之间的一连串交互记录。
- 截断
- 因为时间或预算限制而停止,即使任务本身还没有自然结束。
02看看它怎么工作
本篇环境是一条状态0、1、2的走廊,初始在0,动作只有left、right和finish。移动在边界处停留,每个合法动作消耗一步;仅在位置2执行finish才奖励1。到达位置2却没有提交finish仍不是成功,这使“状态接近目标”和“按协议完成”成为两个可以单独测试的条件。
环境返回 observation、reward、terminated、truncated 和 info。要分清“任务自然结束”和“因为限制被迫停止”:本实验提前 finish 算自然终止但失败,预算耗尽仍未 finish 算截断,并约定两标志互斥、任务结束优先。真实框架可能允许两者同时为真,应按具体环境解释。训练时要不要估计末状态后续价值,也取决于任务定义,不能把所有停止都当作后续价值为零。
轨迹审计器从已知初始状态重算每步转移,再对照记录的观测与奖励。如果日志声称从0向右一步就到2,或者结束后仍执行动作,审计立即失败。对真正随机环境需要固定随机数流或保存足够事件证据;仅记录一个种子不总能跨实现重建结果。这里使用确定性环境,是为了把回放契约与模型能力分开验证。
先跑通一个最小例子
Python标准库实验,执行 python experiment.py。Corridor是原创确定环境,不继承Gymnasium,不访问网络,也不训练模型。
展开收起代码 · experiment.py
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
class Corridor:
def __init__(self, budget=3):
if type(budget) is not int or budget<1:
raise ValueError("positive budget")
self.budget=budget
self.reset()
def reset(self):
self.state=0;self.used=0;self.closed=False
return self.state, {"version":"corridor-v1"}
def step(self, action):
if self.closed:
raise RuntimeError("reset required")
if action not in ("left","right","finish"):
raise ValueError("unknown action")
if action=="left": self.state=max(0,self.state-1)
if action=="right": self.state=min(2,self.state+1)
self.used+=1 # 每个合法动作消耗一次预算,包括finish。
terminated=action=="finish"
truncated=not terminated and self.used>=self.budget
reward=int(terminated and self.state==2) # 由环境状态判定,不信任动作说明。
self.closed=terminated or truncated
return self.state,reward,terminated,truncated,{"used":self.used}
env=Corridor()
trace=[]
for action in ["right","right","finish"]:
trace.append((action,env.step(action)))
assert trace[-1][1][1:4]==(1,True,False)
try: env.step("right")
except RuntimeError: pass
else: raise AssertionError("acted after end")
assert env.reset()[0]==0
assert Corridor().step("finish")[1:4]==(0,True,False)
short=Corridor(1)
assert short.step("right")[1:4]==(0,False,True)
edge=Corridor()
assert edge.step("left")[0]==0
try: edge.step("teleport")
except ValueError: pass
else: raise AssertionError("unknown action accepted")
assert edge.used==1 # 非法动作拒绝前没有改变状态和预算。
print("success=1 steps=3; early_finish=0; budget_end=truncated")
print("PASS 8 checks; deterministic environment, no model call")
核对运行结果输出success=1 steps=3、early_finish=0、budget_end=truncated,并通过8项检查。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
N0["定义可观察状态与允许动作"]
N1["重置环境并记录版本"]
N2["验证动作后执行状态转移"]
N3["分别记录奖励与停止原因"]
N4["回放审计后形成训练轨迹"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4按顺序看,每一步做了什么
- 定义可观察状态与允许动作
- 重置环境并记录版本
- 验证动作后执行状态转移
- 分别记录奖励与停止原因
- 回放审计后形成训练轨迹
03对照实现代码
Gymnasium v1.2.0;ReAct arXiv:2210.03629v3 · Env.step / Env.reset;ReAct第2节交互定义
原创调度片段,validate_action与record为应用占位;不是框架源码。独立Python实验无需安装Gymnasium,也不调用语言模型。
def controlled_step(env, proposed_action):
action = validate_action(proposed_action) # 自定义解析器先检查允许动作与参数。
observation, reward, terminated, truncated, info = env.step(action) # 五项来自环境。
record(action, observation, reward, terminated, truncated, info) # 轨迹保留各字段身份。
return observation, terminated or truncated # 调度器停止后须按契约重置。
Gymnasium核心Env类规定step返回五元组,reset返回观测与info。基类并未替每种业务实现状态转移,具体环境需要自己定义动作与观测空间。本文Corridor类借用这一返回形状,但没有继承Gymnasium,也没有通过其环境检查器;它是可运行的标准库教学环境。
ReAct论文第2节把策略条件写成交互历史,并扩展语言推理与环境动作的组织方式。原论文的若干任务有各自的搜索、查找或导航动作,不能把本文三个走廊动作当作论文原始API。我们保留的是行动产生外部观测、观测影响后续决策的闭环结构。
终止与截断拆分是为了保留停止原因,尤其关系到价值自举与评测分母。日志接口应原样保留两种标志及错误类型,不能为了方便显示统一写成done后丢掉原始信息。超时、格式错误与自然失败可能有不同恢复策略,也可能暴露不同模型弱点。
短代码里的validate_action和record是应用上下文占位,不是Gymnasium真实函数。完整实验给出这类验证的具体小实现,Java部分进一步用环境规则审计轨迹。这样读者既能找到真实框架契约,也能识别哪些部分是研究者必须自己设计和测试的职责。
代码先调用谁,再调用谁
- 策略读取允许观测
- 动作解析与预算检查
- 具体
Env.step执行 - 记录五元组与来源
- 结束或将新观测交回策略
04动手写一个例子
确定性轨迹重放审计
输入走廊动作日志与预算,返回成功、失败、截断或部分轨迹;拒绝伪造观测、奖励、终止标记和越预算动作。
- 输入 1
right,right,finish,预算3- 输出 1
SUCCESS
- 输入 2
right,right,预算2- 输出 2
TRUNCATED
审计从可信初始状态开始,而不是把日志第一条观测直接当真。每个动作经固定规则更新状态,再算奖励与终止值,对照记录。这个顺序保证待审数据只能被检查,不能反过来决定检查器认为什么状态应该出现。
循环不变量是当前state恰好等于已验证动作前缀的真实执行结果。一旦进入终止状态,后续任何动作都非法。预算在入口检查总步数,并在未终止时区分完整预算和不足预算,避免把尚在采集的部分日志误判为失败任务。
算法只做一次扫描,不搜索所有可能动作,也不判断决策是否最优。合法但绕路的轨迹可以通过审计,最后可能截断;这正是数据真实性与策略质量的区别。对于非确定环境,不能直接复用状态重算结论,需要记录随机结果或引入独立可核验事件。
为什么这样写是对的state由已验证动作前缀唯一决定;结束后没有后续动作,所有观测与奖励均与规则一致。
逐步重算常数大小状态,输入日志空间另计。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
static void near(double expected,double actual) {
if (!Double.isFinite(actual) || Math.abs(expected-actual)>1e-9*Math.max(1,Math.abs(expected)))
throw new AssertionError(expected+" != "+actual);
}
static void check(boolean ok) { if(!ok) throw new AssertionError(); }
static void bad(Runnable r) {
try { r.run(); } catch(IllegalArgumentException e) { return; }
throw new AssertionError("invalid input accepted");
}
enum Action { LEFT, RIGHT, FINISH }
record Step(Action action,int observation,int reward,boolean terminal) {}
static String audit(List<Step> steps,int budget) {
if(steps==null||budget<1||steps.size()>budget) throw new IllegalArgumentException("budget");
int state=0;boolean ended=false;boolean success=false;
for(Step s:steps) {
if(s==null||s.action()==null||ended) throw new IllegalArgumentException("after end");
if(s.action()==Action.LEFT) state=Math.max(0,state-1);
if(s.action()==Action.RIGHT) state=Math.min(2,state+1); // 环境重算状态,不信任日志自述。
ended=s.action()==Action.FINISH;
success=ended&&state==2;
int reward=success?1:0; // 只有在目标处提交结束才算成功。
if(s.observation()!=state||s.reward()!=reward||s.terminal()!=ended)
throw new IllegalArgumentException("forged transition");
}
if(ended) return success?"SUCCESS":"FAILED";
return steps.size()==budget?"TRUNCATED":"PARTIAL"; // 预算耗尽与正常终止分开。
}
public static void main(String[] args) {
Step r1=new Step(Action.RIGHT,1,0,false),r2=new Step(Action.RIGHT,2,0,false);
check(audit(List.of(r1,r2,new Step(Action.FINISH,2,1,true)),3).equals("SUCCESS"));
check(audit(List.of(new Step(Action.FINISH,0,0,true)),3).equals("FAILED"));
check(audit(List.of(r1,r2),2).equals("TRUNCATED"));
check(audit(List.of(),3).equals("PARTIAL"));
bad(()->audit(List.of(new Step(Action.RIGHT,2,0,false)),3));
bad(()->audit(List.of(new Step(Action.FINISH,0,0,true),r1),3));
bad(()->audit(List.of(r1,r2),1));
check(audit(List.of(new Step(Action.LEFT,0,0,false)),2).equals("PARTIAL"));
System.out.println("PASS 8 cases");
}
}
这些特殊情况也要试一试
- 边界移动不越界
- 提前finish合法但失败
- 空日志是PARTIAL
- 预算耗尽不是自动成功
进阶选读 · 部分可观测环境的数据协议
场景网页或工具环境隐藏内部状态且版本持续变化。
做法保存可见观测、动作参数、工具版本和事件ID,建立沙箱重放或可核验快照;明确哪些状态无法恢复,不把模拟重放等同真实外部副作用。
复杂度轨迹存储O(T×观测大小),去重和快照恢复成本取决于环境。
好处与代价完整记录提升诊断性,却增加存储与隐私管理成本。
05项目里怎么用
把环境成功条件写成独立函数并在训练前固定。若任务是创建工单,应查最终工单状态及归属,不能只检查回答里出现“已创建”。同样,检索任务应验证引用是否支持结论,而不是只计工具调用次数。可执行成功条件决定了之后奖励学习和统计比较的意义。
采集失败轨迹时保留错误原貌,同时做必要脱敏。提示注入、工具异常和权限拒绝应有独立标签,不要把环境的拒绝指令混入模型示范动作。共享训练数据前检查个人信息和环境凭据;实验版本变化时重新评估旧轨迹是否仍合法。
本文标准库实验没有调用外部模型,也没有训练策略,因此成功走到目标只证明环境与固定动作序列一致。后续可以替换成规则策略、模仿学习策略或语言模型,在相同任务与预算下比较。先有可重复基线,再谈ReAct提示或训练方法带来的实际增益。
容易踩的坑
- 让模型自述代替环境成功判定
- 把工具观测也作为生成标签
- 丢掉终止与截断原始字段
- 环境版本变化却复用旧结果
- 只保存成功轨迹隐藏失败分布
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 确定性小环境 | 容易回放与单步定位 | 不能代表真实复杂任务 | 接口与算法起步 |
| 沙箱真实环境 | 保留更多真实交互 | 启动与重置成本较高 | 策略端到端评测 |
| 线上只读回放 | 贴近真实资料分布 | 难重建所有外部状态 | 有明确证据边界的诊断 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01到目标为何仍需finish?
展开参考答案与推导
任务契约要求显式提交,可区分状态达成和流程完成。
02ReAct轨迹是否证明内部推理真实?
展开参考答案与推导
不证明;可记录文本只是外部输出,事实依赖环境证据。
03截断为何独立记录?
展开参考答案与推导
预算结束与自然终止有不同学习和诊断含义。
换个情况,还会处理吗
01工具返回成功但数据库没有变化怎么办?
展开参考答案与推导
以独立业务状态验证为准,检查工具适配、事务和观测是否伪成功。
02更换环境版本后旧轨迹不能回放怎么办?
展开参考答案与推导
保留版本并迁移或重新采集,不能默默修改旧记录使其通过。
03只训练成功轨迹有哪些风险?
展开参考答案与推导
缺少失败恢复状态与错误边界,策略部署后可能无法从偏离中恢复。
写代码试一试
01审计三步成功轨迹。
- 输入 / 条件
right,right,finish- 输出 / 预期
SUCCESS
提示 从状态0重算
展开参考答案与推导
每步对照观测和奖励,末步必须终止。
02检测伪造观测。
- 输入 / 条件
0向right后记录2- 输出 / 预期
抛异常
提示 规则实际只能到1
展开参考答案与推导
先执行可信规则再比较日志。
03区分停止原因。
- 输入 / 条件
预算1执行right- 输出 / 预期
TRUNCATED
提示 没有finish
展开参考答案与推导
截断标志为真、任务终止为假。
读完之后,独立完成
课后作业:自己做一遍
本篇共 12 道作业,先完成 9 道基础与综合任务,再按前置知识挑战进阶题。其中包含 2 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
运行三步走廊环境,区分成功、提前结束和预算用尽;用 Java 审计器拒绝伪造观测与任务结束后的动作。
本篇重点加练
研究轨迹必须保留信息可见性、停止语义与训练标签,重点审计从执行记录到学习数据的转换。
- 观察与真实状态
- 任务终止与预算截断
- 动作掩码与训练标签
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
设计一个两房间取物环境,分别列出真实状态、可见观察、合法动作和决策记录;举出观察相同但真实状态不同的两种情况。
- 提交内容
- 环境契约与状态对照
- 怎样算完成
- 观察和状态不混用
- 决策文本不充当环境证据
练到的知识点观察与真实状态 · 动作空间与解析 · 决策摘要与环境证据
- 作业 02代码推演
自建一条包含一次无效动作、一次有效动作和预算结束的轨迹,逐事件标注奖励、观察、停止原因与成功判定依据。
- 提交内容
- 完整事件账本
- 怎样算完成
- 终止截断可区分
- 成功由环境状态判断
练到的知识点轨迹事件结构 · 任务终止与预算截断 · 可执行成功判定
- 作业 03原理问答
两条轨迹的模型可见观察相同,但日志含不同隐藏环境状态。设计行为克隆输入字段白名单,说明哪些字段只可用于评估而不能进入策略输入。
- 提交内容
- 信息分层及训练字段契约
- 怎样算完成
- 可见时点明确
- 监督字段与隐藏答案分开
练到的知识点观察与真实状态 · 动作掩码与训练标签
- 作业 04代码推演
同一任务分别在 3 步和 6 步预算下执行,构造前 3 步相同的轨迹,比较第 3 步事件能支持的状态与成功结论。
- 提交内容
- 双预算轨迹及结论表
- 怎样算完成
- 成功判据与预算独立
- 未观察后续保持未知
练到的知识点观察与真实状态 · 任务终止与预算截断
综合应用
- 作业 05编程实践
实现 CPU 确定性环境回放器,输入初始快照、种子、动作事件和环境版本;测试篡改观察、遗漏事件与不兼容版本。
- 提交内容
- 回放器及审计结果
- 怎样算完成
- 回放身份完整
- 不一致位置可定位
练到的知识点轨迹事件结构 · 可回放环境版本 · 可执行成功判定
- 作业 06排错实验
构造含工具观察和助手动作的训练序列,分别注入错误动作标签、观察被当策略目标、预算结束被当成功三类问题。
- 提交内容
- 标签审计器及故障样例
- 怎样算完成
- 每个标签归属可检查
- 训练目标和任务结果分开
练到的知识点动作空间与解析 · 任务终止与预算截断 · 动作掩码与训练标签
- 作业 07面试场景
同一轨迹文本在新版环境失败。设计研究数据集的可复现协议,列出需要冻结的环境内容及不能由文字推理补足的证据。
- 提交内容
- 数据集身份及回放协议
- 怎样算完成
- 环境变化可追溯
- 不伪造缺失执行证据
练到的知识点决策摘要与环境证据 · 可回放环境版本 · 可执行成功判定
- 作业 08编程实践
实现 CPU 轨迹切片器,把长度六的轨迹切成训练窗口,保留动作位置和终止/截断身份;审计切片边缘是否产生伪造成功标签。
- 提交内容
- 切片程序与位置对账
- 怎样算完成
- 原事件身份保留
- 窗口结束不自动当任务终止
练到的知识点任务终止与预算截断 · 动作掩码与训练标签
- 作业 09排错实验
故意把工具返回时间之后才出现的证据拼入之前的策略训练输入,写时间一致性检查并在一条自建轨迹上定位泄漏。
- 提交内容
- 事件时间审计器
- 怎样算完成
- 事件发生与可见时间区分
- 泄漏位置有具体依据
练到的知识点观察与真实状态 · 动作掩码与训练标签
进阶挑战 · 掌握前置后选做
- 作业 10设计评审
比较仅成功轨迹与成功失败混合轨迹用于动作预测的泛化表现,用 CPU 小分类器和保留初始状态实验;预先定义样本预算。
- 提交内容
- 研究协议、程序与报告
- 怎样算完成
- 学习目标明确
- 保留状态不参与训练
练到的知识点观察与真实状态 · 任务终止与预算截断 · 动作掩码与训练标签
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 11思维拓展 · 反例构造
“轨迹文本完全相同就代表同一决策问题”是否成立?改变隐藏环境状态或终止规则但保持已见文本,构造两种任务后续不同的例子。
- 提交内容
- 双环境反例及可见性分析
- 怎样算完成
- 当前观察确实一致
- 规则差异可执行验证
练到的知识点观察与真实状态 · 任务终止与预算截断
- 作业 12思维拓展 · 跨域迁移
把轨迹窗口迁移到视频动作学习:窗口边界与动作结束不一致时,如何定义标签和未观测未来?给出不依赖视频模型的事件序列例子。
- 提交内容
- 序列映射与边界契约
- 怎样算完成
- 采样截止与事件终止分开
- 标签不使用不可见未来
练到的知识点任务终止与预算截断 · 动作掩码与训练标签
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
ReAct v3 (新标签页)↗
阅读第2节与交互轨迹,理解动作和外部反馈。
- 实践参考文档英文
Gymnasium v1.2.0核心接口 (新标签页)↗
核对step和reset返回契约。
- 深入参考文档英文
Gymnasium时间限制说明 (新标签页)↗
区分终止、截断与价值自举。
- 实践参考文档英文
ReAct作者仓库 (新标签页)↗
查看各任务动作与提示示例,避免把统一概念误当统一工具API。