让 Agent 边跑边学:一套在线强化学习训练架构
本文只讨论通用的工程方法与架构取舍,不涉及任何具体业务、代码、数据与效果指标。
一、为什么要走到"训练"这一步
Agent 项目的早期形态几乎都一样:拿一个通用大模型,靠 Prompt Engineering 把业务逻辑塞进去。这个选择在早期是完全正确的——不需要标注数据,不需要训练资源,改一版 prompt 半小时就能上线,迭代速度是压倒性优势。
但这条路会撞上三堵墙:
- 稳定性与时延不由自己掌控。通用模型服务的可用性和 P99 时延是外部变量,要保证用户体验就得靠预留算力兜底,成本随规模线性上涨。
- 效果上限由基座模型决定。Agent 的效果本质上是基座模型指令遵循能力和 function call 能力的函数。prompt 调到某个程度就会进入平台期,再怎么改也只是在天花板下面挪动。
- 框架重、链路长。一次任务往往要多次调用大模型,每一跳都有时延。如果能用一个针对性训练过的小模型替代通用大模型,反而可能同时拿到更好的效果和更短的链路。
所以自研 Agent 模型不是"更酷",而是"必要"——它是把上限、成本、稳定性三个变量重新拿回自己手里的唯一方式。
二、真正的难点不是训练,是闭环
想清楚要训练之后,痛点立刻换了一批:
- 局部最优陷阱。Agent 的不同阶段(规划、检索、生成……)通常各自挂一个模型分别调优,每个环节都调到最好,端到端不一定最好。真正要优化的目标是"用户最终看到的那个回复好不好",而不是"规划节点的输出像不像标注答案"。
- 框架异构。开源 Agent 框架五花八门,业务自研的框架更是各家一套。训练框架如果要求 Agent 按它的接口重写,那这个方案在落地第一天就死了。
- 离线训练成本高、数据组织复杂。人工攒数据集、离线跑训练、人工评估、人工上线,一个循环走完,业务需求已经变了。
第三点是最关键的:如果模型更新不能自动化,训练就永远是一次性的"项目",而不是持续生效的"能力"。目标应该是让模型能从日常的真实使用和环境反馈中自主学习,形成流式的自动更新。
三、核心抽象:把 Agent 当黑盒
这个问题上,微软的 Agent Lightning(Train ANY AI Agents with Reinforcement Learning)给了一个非常漂亮的解法,开源实现在 GitHub。

它的核心洞察只有一句话:
Agent 完成一次任务会多次调用大模型。把每次调用的输入输出抽出来,配上这次任务整体拿到的 reward,就得到了一组
(input, output, reward)——这就是 RL 训练数据。
这一句话解开了"框架异构"的死结。训练框架不需要理解 Agent 的内部结构,不关心它是 ReAct 还是多智能体编排,不关心它用什么语言写的。它只需要在模型调用这一层做拦截,Agent 对训练框架来说是一个完全的黑盒。
对应的训练流程被拆成 Server / Client 两侧:
Train Server Train Client
│ ①加载训练集 [input]
│ ②分 batch 下发 ──────────▶ ③把 input 喂进真实 Agent
│ 得到 [input, output]
│ ④送 Reward Model 打分
│ ⑥输出更新后的 model ◀────────── ⑤回传 [input, output, reward]
Server 管训练,Client 管"驱动真实 Agent 跑一遍"。两者之间只有数据契约,没有框架耦合。这个边界划得非常干净——它意味着 Agent 团队和训练团队可以并行演进,各自的技术选型互不污染。
四、落地时的工程架构
论文给的是骨架,把它接到一个已经在跑的线上系统上,需要补足四段链路。
整体数据流:实线为主链路,虚线为埋点采样与模型路由;编号对应下文各小节
4.1 数据采集:从线上真实流量里长出训练集
训练数据的来源不应该是人工构造的题库,而应该是线上真实发生过的请求。这就要求在两个位置埋点:
- 模型代理层(LLM Proxy):所有对大模型的调用统一收口到一个代理,代理按比例采样,记录
[input, output, trace_id]。 - 对话/编排层:任务结束时(流式场景是流结束时)上报端到端的
[request, response, trace_id]。
trace_id 是把这两份数据串起来的唯一钥匙——它让"用户问了什么、最终看到了什么"和"过程中某个模型节点具体输入输出是什么"能被关联到同一次决策过程上。
先落到在线库,再由流式同步任务实时写入离线数仓。这里刻意分成两跳:在线库承担写入和按 id 随机读取,数仓承担大规模筛选和分析,各司其职。
采集表的 schema 大致是这样的(已泛化):
| 字段 | 类型 | 说明 |
|---|---|---|
id |
bigint | 主键 |
app |
varchar | 业务线标识,用于数据筛选 |
trace_id |
varchar | 串联一次完整决策过程 |
process_node |
varchar | 决策链路上的节点名,如 planner / retriever / end2end |
model_name |
varchar | 被调用的模型名 |
input / output |
mediumtext | JSON 序列化的模型输入输出 |
reward_score |
double | Reward Model 给出的奖励分 |
attributes |
mediumtext | 预留的扩展字段,JSON |
request_time / response_time |
varchar | 请求与响应时间 |
process_node 和 app 两个字段是可扩展性的关键:有了它们,同一套采集链路可以同时服务多条业务线、多个待训练节点,筛数据时一个 where 条件就能切出想要的子集。attributes 这种预留字段容易被批评为"不够规范",但在这种早期快速演进的系统里,它是避免频繁改表的必要妥协。
4.2 数据准备
策略同学从数仓用 SQL 筛出自己想要的数据,写成 parquet 放到分布式存储,形成训练集和验证集。注意这里落盘的只有 [id, input]——完整的上下文不需要提前物化,因为 rollout 阶段会用 id 回查在线库把原始请求捞回来。这样训练集本身很轻。
4.3 Rollout:让训练跑在真实链路上
这是整套架构里最有意思的部分。
Train Client 拿到一条 [id, input] 之后,不是在沙盒里模拟,而是用 id 回查到当时的原始请求,重新向线上 Agent 发起一次真实调用,把整条 trajectory 跑出来。请求里额外带两个参数:
model参数:模型代理识别到它之后,把这一跳的请求转发到 Train Server 上正在训练的那个模型,而不是线上默认模型。invariant参数:保证这次 rollout 中,不属于当前训练目标的其他模型节点,输出与原始请求保持不变。
第二个参数是精髓。Agent 链路上通常有多个模型节点,如果 rollout 时每一跳都重新随机采样,那么最终 reward 的变化到底来自"被训练的这个模型变好了"还是"别的节点这次运气好"就无法归因了。把其他变量钉死,reward 的差异才干净地归因到当前被训练的模型上——这本质上是把线上链路改造成了一个可控变量的实验环境。
为此,Agent 服务需要提供一个 rollout 接口。接口设计上有个原则值得强调:用一个透明的 string 承载原始请求,而不是把原始请求的结构展开进 rollout 的接口定义。
RolloutRequest {
originRequest : string // 原始请求的序列化结果
trainMeta : string // 训练侧透传的元信息
}
RolloutResponse {
originResponse : string // 原始响应的序列化结果
}
这样 rollout 接口和业务接口是解耦的,业务请求结构演进时不需要动 rollout 这一层。但代价是:业务接口必须保证向前兼容——新版本服务要能正确处理历史版本的请求结构,否则昨天采集的数据今天就 rollout 不了了。这个约束应该写进接口规范,而不是靠自觉。
4.4 评估与上线:闭环的最后一环
训练出新模型不等于可以上线。需要一个独立的 Judge Server:
- 从评估平台拉取固定的高质量测试集;
- 把 input 打给新模型拿到 output;
- 送 Reward Model 打分,与基线对比;
- 达标才推送到模型服务平台上线。
这一步是整个"自动更新"叙事能否成立的关键。没有自动化的评估门禁,所谓自动训练只是把风险自动化了。
4.5 Reward 设计
Reward 决定模型往哪个方向走,是整套系统里唯一无法工程化解决、必须靠业务理解的部分。通常两条路并用:
- LLM 生成 reward:用一个更强的模型做 judge,适合开放式、主观的质量评价;
- 规则函数生成 reward:适合有客观标准的场景,如格式是否合法、工具调用参数是否正确、结果是否匹配 ground truth。
我的判断是:能用规则的地方尽量用规则。LLM judge 自身的噪声会直接变成训练信号里的噪声,而且它的偏好会被模型学去——你最终得到的是一个"擅长讨好 judge 的模型",而不一定是"用户觉得好的模型"。
五、已知的取舍与遗留问题
一个诚实的方案设计必须写清楚它现在还不能做什么:
- 训练期间线上不能随意变更。因为 rollout 跑在真实链路上,训练过程中线上代码或配置一变,采样分布就漂了。当前只能靠流程约束(训练期间冻结变更),正确的做法是用预发环境做隔离——这是明确要补的一课。
- 同时只能训练一个模型。当前架构是串行的,多模型并行训练需要在数据、调度、代理路由几层都做改造。前面 schema 里预留
app/process_node字段,就是为这一天做的铺垫。 - 回放不等于在线。用历史请求重放,本质仍是 off-policy 的近似,与真正的在线交互式学习还有距离。
六、写在最后
回头看,这套架构真正的价值不在于用了 RL,而在于它把"模型效果"从一个靠人推动的项目变成了一条自我运转的流水线:线上流量自动变成训练数据,训练自动产出新模型,评估自动决定是否上线。人的位置从"每一环都要推一把",退到"定义 reward 和评估标准"。
这也是我认为这个方案里最长期正确的一点——它没有试图去优化某一次的模型效果,而是把优化能力本身做成了基础设施。前者是一次性收益,后者是持续复利。