@athleticKoder: https://x.com/athleticKoder/status/2057091692235481560
摘要
一篇技术博文,从基本原理出发解释如何构建智能体训练系统,以文本转图表智能体为例,涵盖环境定义、教师轨迹生成、学生微调以及强化学习。
查看缓存全文
缓存时间: 2026/05/21 08:23
从第一性原理构建智能体
如何定义环境、生成教师轨迹、微调学生模型,并通过强化学习改进它。
作者: Anshuman Mishra & GPT 5.5 2026年5月20日
[编者注:论点由我提供;写作和结构由GPT 5.5优化。这在一定程度上是一个实验,测试如何利用AI从粗略笔记快速撰写技术研究博客,同时保持真实的口味、方向和主张由人掌控。]
关于后训练(post training)的教程往往直接从很高的抽象层开始。它们从一个框架入手:安装这个库,定义这个奖励函数,运行这个训练器,观察奖励曲线变化。当你已经理解发生了什么时,这种方法确实有用。但当你试图构建整个系统的心理模型时,它就不太有帮助了。
我发现从更底层开始更有帮助。在训练器之前,先有环境。在强化学习之前,先有动作空间。在智能体之前,先有产生动作、改变世界状态的策略。
这篇文章试图从第一性原理构建这幅图景。
例子将故意做得很小:一个文本到图表的智能体。用户要求一个简单的图表,模型输出结构化的JSON动作,在画布上创建形状。你可以把它想象成一个小型的tldraw风格智能体。模型不是在一个真正的编辑器中点击,而是发出诸如“创建一个矩形”、“添加一个标签”和“连接这两个节点”之类的动作。
目标不是构建世界上最棒的图表智能体。目标是理解智能体训练本身的形状。
在高层,循环是:
提示 -> 模型动作 -> 环境 -> 奖励 -> 梯度更新
几乎每个智能体训练系统都是这个循环的放大版。浏览器智能体、编码智能体、电子表格智能体、机器人规划器、数学求解器和图表智能体都具有相同的基本结构。它们的区别在于环境、动作空间和奖励函数。
这是使用框架时容易忽略的部分。像TRL、Unsloth、PRIME-RL、verl、OpenRLHF或自定义内部训练器这样的库并非魔法。它们主要是围绕这个循环的基础设施:批处理、轨迹生成、分布式推理、奖励计算、日志记录、参考模型、裁剪、检查点存储和扩展。
概念核心要小得多。
智能体是环境内部的策略
语言模型是序列上的概率分布。当我们将其用作智能体时,我们是要求这个分布产生动作,而不是普通散文。
给定一个观察,模型发出一个动作。在聊天模型中,观察是对话,动作是助手消息。在浏览器智能体中,观察可能是DOM,动作可能是点击或按键。在编码智能体中,观察可能是仓库状态,动作可能是补丁。在图表智能体中,观察是用户请求,动作是描述形状和连接的JSON对象。
所以第一个问题不是“我应该使用哪个RL训练器?”
第一个问题是:环境是什么?
环境定义了哪些动作有效,执行这些动作时会发生什么,以及如何衡量成功。在普通的监督微调中,这个环境通常是隐含的。我们向模型展示良好行为的例子,并要求它模仿。在强化学习中,环境变得明确。模型尝试某些东西,环境做出响应,奖励函数决定这次尝试是否良好。
对于图表智能体而言,一个完成之所以好,不仅仅因为它听起来合理。它好是因为JSON能解析、模式有效、画布接受动作、请求的对象出现、箭头连接正确的节点、最终布局清晰可理解。
这就是普通聊天微调与智能体训练的核心区别。智能体训练将模型的输出扎根在一个可执行的世界中。
动作语言
让我们从可能的最小动作空间开始。
模型必须返回一个带有动作数组的JSON。每个动作要么创建一个形状,要么连接两个形状。一个有效的完成可能如下所示:
{
"actions": [
{"type": "create", "shape": "rectangle", "id": "box1", "x": 10, "y": 20, "width": 100, "height": 60},
{"type": "create", "shape": "text", "id": "label1", "x": 15, "y": 25, "text": "用户数据"},
{"type": "connect", "from": "box1", "to": "box2"}
]
}
这看起来简单,但它已经包含了工具使用的基本结构。模型不再只是生成文本。它是在生成另一个系统将执行的指令。
这改变了训练问题。模型不仅要学会说什么,还要学会环境会接受什么。
它必须在连接形状之前创建形状。它必须使用稳定的ID。它必须避免重复的ID。它必须保持坐标有限。它必须避免无效的形状类型。它必须发出可解析的JSON。这些不是哲学细节。它们决定了策略是否能够进入动作空间的有效区域。
这就是为什么在RL之前通常需要SFT。在模型能够优化奖励之前,它必须学会环境的语言。
没有框架的微型环境
一个环境只需要三样东西:一个输入提示,一个动作格式,和一个奖励函数。
这里是一个极简的纯Python画布环境。它支持矩形、椭圆、菱形、文本块和箭头。重点不在于这个画布有多复杂。重点在于它为我们提供了一个确定性的世界,在这个世界里,模型输出可以成功或失败。
class Canvas:
def __init__(self):
self.shapes = {}
self.connections = []
def create_shape(self, id, shape_type, **kwargs):
if id in self.shapes:
raise ValueError(f"重复的ID: {id}")
self.shapes[id] = {"type": shape_type, **kwargs}
def connect(self, from_id, to_id):
if from_id not in self.shapes or to_id not in self.shapes:
raise ValueError("连接中使用的ID不存在")
self.connections.append((from_id, to_id))
def validate(self):
# 检查重叠、有效类型、有限坐标等
return errors, is_valid
这已经给了我们一个验证器环境的核心。模型发出文本。环境解析该文本,执行动作序列,如果出错则返回错误。
框架可以让它更具可扩展性。但不会让它有概念上的本质区别。
奖励是任务所在的地方
一旦环境能够执行动作,我们需要定义成功。
这是大部分真正困难所在。训练器只能优化你给它的奖励。如果奖励主要是语法层面的,模型将学习语法。如果奖励衡量任务满意度,模型才有机会学习有用的行为。如果奖励很脆弱,模型最终会找到漏洞。
对于玩具图表智能体,我们可以结合几个信号。我们可以奖励解析成功的输出、验证通过的动作、不重叠的布局、存在的标签、连接对象的箭头,以及覆盖用户请求中重要单词的标签。
def reward_fn(output, user_request, canvas):
score = 0.0
# 基础解析奖励
score += parse_bonus(output)
# 动作验证奖励
score += validation_bonus(canvas)
# 布局奖励:避免重叠
score -= overlap_penalty(canvas)
# 标签覆盖奖励
score += label_coverage_bonus(canvas, user_request)
# 箭头连接奖励
score += connection_bonus(canvas)
return score
这个奖励有意不完美。它会遗漏人类关心的许多东西。它可能高估标签。可能低估美学。可能在同义词上失败。可能奖励包含正确单词但结构错误的图表。
对于玩具环境来说,这没问题。事实上,它很有用,因为它暴露了核心问题。
智能体RL的难点通常不在于策略梯度方程。难点在于构建一个环境,使得奖励与你真正想要的行为相关。
这个任务的弱奖励可能是:
如果JSON解析则奖励1,否则0
这教模型保持有效性,但不有用。
更强的奖励可能看起来像这样:
对于真正的tldraw风格智能体,其中一些可以用代码检查。你可以验证模式,在真实编辑器中执行动作,检查最终形状,检查箭头绑定,计算重叠,并导出截图。其他部分可能需要一个评判模型。你可能要求VLM或LLM评判截图是否满足用户意图。
但即便如此,评判模型应被视为环境中的噪声组件,而不是神谕。记录它的判断。检查失败。与人工审查比较。添加负面测试。假设模型最终会利用最容易利用的任何奖励信号。
为什么教师轨迹先于RL出现
如果我们从一个无法产生有效动作的模型开始RL,几乎每次轨迹都获得零奖励。
这不仅仅是优化上的不便。这是一个状态分布问题。在SFT之前,模型的策略将其大部分概率质量放在环境有效区域之外。它写解释、markdown、格式错误的JSON、无效ID、不可能的连接,或者看起来合理但画布拒绝的动作。环境返回零。梯度几乎没有有用信息。
这就是为什么教师轨迹很重要。
一个更强的模型,比如Gemini,可以生成动作语言的例子。我们可以采样多个完成,验证它们,保留有效的那些,并将它们变成SFT数据集。在单轮环境中,一个轨迹就是:
观察:用户提示 动作:JSON动作 奖励:验证分数
在多轮环境中,它变成:
obs_0 -> action_0 -> obs_1 -> action_1 -> obs_2 -> 奖励
对于更丰富的tldraw风格设置,轨迹可能包含用户请求、可见的画布状态、选中的形状、模型的动作批次、验证器输出、最终截图和奖励。
关键点是教师生成不是魔法。它是从更强的策略中采样,在环境中执行输出,并保留那些能存活的痕迹。
这是一个使用Gemini结构化输出的最小草图。
response = client.models.generate_content(
model="gemini-2.5-pro",
contents=f"为'{user_request}'生成图表动作",
config={
"response_mime_type": "application/json",
"response_schema": ActionSequence,
},
)
validated = validate_in_canvas(response.parsed)
这产生了第一阶段所需的数据集。教师将学生移动到有效的动作流形中。它没有解决整个任务。它给了RL一个有用的起点。
SFT买到了语法
监督微调就是模仿学习。
给定一个提示和一个教师动作,模型被训练为增加该动作的概率。对于一个聊天模型,每一行看起来像这样:
{"prompt": "画一个显示数据库和API的图表", "completion": "{"actions": [...]}""}
重要的是要注意SFT做了什么和没做什么。
SFT没有问这个完成是否比另一个完成更好。它没有直接优化奖励。它没有探索环境。它是在将学生拉向演示过的轨迹。
对于智能体训练来说,这仍然非常有用。SFT教会模型动作空间的语法。它教会模型返回JSON、使用正确的模式、在连接之前创建形状,以及避免明显无效的动作序列。
在这个意义上,SFT买到了语法。它将策略从一个几乎每次轨迹都失败的区域移动到一个某些轨迹足够有效、使得强化学习变得有意义的区域。
一个最小的TRL SFT脚本看起来像这样:
from trl import SFTTrainer
trainer = SFTTrainer(
model="llama-3.2-3b",
train_dataset=dataset,
formatting_func=format_chat,
)
trainer.train()
用以下方式运行:
python train_sft.py --dataset_path teacher_trajectories.jsonl
如果你愿意,可以添加LoRA或QLoRA。你可以扩展数据集。你可以使用多个教师。这些是重要的工程选择,但它们不会改变这一阶段的概念角色。
在SFT之后,模型应该更频繁地产生语法有效的动作序列。这足以解锁下一阶段。
RL买到了优化
如果SFT教会模型模仿好的轨迹,为什么还要做RL?
因为教师轨迹是静态的。它们展示了教师在我们生成的提示上做了什么。它们没有直接优化我们关心的指标。
SFT说:让这个教师的答案更可能。
RL说:让高奖励的答案更可能。
这个差异很重要。
假设教师创建了一个有效的图表,但布局很拥挤。SFT会复制拥挤的布局。如果奖励重视间距,RL可以发现更干净的布局。假设教师将节点标记为“DB”,但奖励对“数据库”给予更高的语义覆盖率。RL可以朝着“数据库”移动。假设同一个提示有很多有效图表。SFT会坍缩到教师的风格。RL可以强化那些在环境下得分更高的变体。
这是基本的劳动分工:
SFT买到了语法。 RL买到了优化。
因此通常的流程是:
基础模型 -> 在教师轨迹上进行SFT -> 在环境奖励上进行RL
SFT检查点不是最终模型。它是使在策略探索富有成效的初始化。
从第一性原理理解RL
最简单的LLM在线RL循环并不复杂。
采样一批提示。为每个提示生成多个完成。用环境为每个完成打分。将这些分数转换为优势。增加比兄弟完成做得更好的完成的概率。减少,或者至少停止增加,做得更差的完成的概率。
这是像GRPO这样的群组相对方法的直觉。对于提示x,模型采样一组完成:
y_1, y_2, …, y_G
环境为它们打分:
r_1, r_2, …, r_G
然后我们计算一个群组相对优势:
A_i = (r_i - mean(r)) / (std(r) + eps)
如果一个完成比它的兄弟好,它的优势是正的。如果更差,优势是负的。
简化后的损失是:
loss = - advantage * logprob(完成令牌)
真实的实现会添加裁剪、KL惩罚、参考模型、令牌掩码、分布式轨迹生成、vLLM推理、缓存和许多稳定性技巧。但核心思想仍然是:从当前策略采样,用环境评估,并将概率质量移向更好的样本。
这是一个故意简陋的训练器草图。它不是生产代码。它旨在揭示机制。
for prompt in batch:
completions = []
for _ in range(G):
completion = model.generate(prompt)
reward = environment.score(prompt, completion)
completions.append((completion, reward))
rewards = [r for (_, r) in completions]
mean_r = sum(rewards) / len(rewards)
std_r = (sum((r - mean_r)**2 for r in rewards) / len(rewards))**0.5
for completion, reward in completions:
advantage = (reward - mean_r) / (std_r + 1e-8)
logprobs = compute_logprobs(model, prompt, completion)
loss = -advantage * logprobs.sum()
loss.backward()
optimizer.step()
这个训练器有很多缺陷。它低效地计算对数概率。它没有使用冻结的参考模型。它没有使用PPO或GRPO裁剪。它没有将生成与训练分开。它没有处理分布式训练。它使用了一个非常简化的令牌目标。
但它展示了循环。
模型为同一个提示采样多个图表。环境为它们打分。损失将模型推向更好的图表。
这就是LLM智能体RL背后的基本机制。
为什么RL训练器感觉比SFT训练器更复杂
SFT数据是固定的。RL数据是由模型在训练过程中产生的。
这一个差异解释了很多复杂性。
在SFT中,数据集在优化开始之前就存在。你可以打乱它、分批它、在上面训练。模型不影响接下来出现哪个例子。
在RL中,策略决定训练数据。随着模型改变,采样的完成也改变。随着完成改变,奖励分布改变。随着奖励分布改变,梯度改变。生成和优化变得耦合。
这就是为什么RL训练器需要护栏。
一个获得高奖励的完成可能被过于激进地强化。一个低奖励的完成可能被过于强烈地惩罚。模型可能偏离一般的语言质量。它可能利用奖励。它可能坍缩为短小、安全的完成。它可能过拟合验证器的怪癖。
现代RL训练器通常引入冻结参考模型、KL惩罚、比率裁剪、奖励归一化、长度掩码和完成掩码的某种组合。
参考模型通常是SFT检查点。策略模型是被更新的模型。如果策略开始比旧模型或参考模型对某个完成分配高得多的概率,则更新可以被裁剪或惩罚。
一个简化的PPO风格比率如下:
ratio = exp(logprob_policy - logprob_old)
裁剪后的目标防止策略在单步中移动太远:
min(ratio * advantage, clip(ratio, 1 - eps, 1 + eps) * advantage)
一个教育版会存储来自生成完成的模型的日志概率:
old_logp = completion_logprob(model, tokenizer, prompt, completion).detach()
然后,在优化期间:
new_logp = completion_logprob(policy_model, tokenizer, prompt, completion)
ratio = exp(new_logp - old_logp)
clipped_ratio = clamp(ratio, 1-eps, 1+eps)
loss = -min(ratio * advantage, clipped_ratio * advantage)
你也可以添加一个参考模型KL项:
kl = exp(ref_logp - new_logp) - (ref_logp - new_logp) - 1
loss = loss + beta * kl
这仍然是简化的,但它解释了为什么PPO和GRPO实现看起来比SFT实现更复杂。它们不仅仅是在拟合一个静态数据集。它们是在控制策略的移动,而策略也在生成自己的训练数据。
在策略视角
思考这个问题最有用的方式是通过状态分布。
在SFT中,模型在
相似文章
@hwchase17: https://x.com/hwchase17/status/2053157547985834227
文章概述了一个系统的“智能体开发生命周期”(构建、测试、部署、监控),以有效创建和管理 AI 智能体,重点介绍了 LangChain、LangGraph 和 CrewAI 等关键框架。
@tom_doerr: 教授从头构建AI代理 https://github.com/pguso/ai-agents-from-scratch…
一个GitHub仓库,教授如何使用本地LLM和node-llama-cpp从头构建AI代理,从基本的LLM交互逐步发展到像ReAct这样的完整代理架构。
@omarsar0: https://x.com/omarsar0/status/2057114824467792189
本文介绍了使用 Fireworks Agent 自动化微调一个小型开放权重模型,以生成维基百科风格的摘要,从而形成一个自我改进的智能体循环,使得模型训练成为一个可调用的步骤。
@yoheinakajima:非常棒的文章,主要聚焦于 coding agents,但个人认为也适用于其他领域。与我之前的许多想法不谋而合:- agent…
该推文总结了构建 agent systems 的关键原则,着重强调了 scaffolding、memory 与可复用工具,内容基于 Yohei Nakajima 的一篇文章。
@sairahul1: https://x.com/sairahul1/status/2058464422306443766
一份关于AI智能体的全面指南,涵盖基础知识、ReAct循环、任务分解、上下文工程以及自主性光谱,面向初学者和构建生产系统的人员。