ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

当下即是最优时机:用TaoToken统一Key跑通大语言智能体在线测试时训练(aTTT)最小闭环

当下即是最优时机:用TaoToken统一Key跑通大语言智能体在线测试时训练(aTTT)最小闭环 1. 为什么长轨迹智能体总在“原地打转”如果你做过多轮工具调用的智能体大概率见过这种画面前几步还挺聪明走到十几步之后开始反复访问同一个页面、重复提交同样的参数、把已经确认过的结论再确认一遍。任务不是不会做而是做着做着就“漂”了。这个现象在 ALFWorld 这类居家交互任务、SWE-bench Lite 这类软件工程修复任务里都很典型——轨迹越长策略越容易偏离原本有效的决策路径。传统解法各有短板。提示工程类方法比如 Reflexion只能在整段 episode 结束后改提示词单轮交互内部没法自适应加长上下文窗口只是让模型“能读到”历史但读得到不等于改得动策略解码阶段的重复惩罚只压输出表面的重复 token底层策略该循环还是循环。静态测试时训练qTTT、In-Place TTT 这类只在任务开始前基于固定输入做一次性微调交互过程中参数不动实时产生的轨迹反馈全浪费了。aTTTAgentic Test-Time Training智能体式在线测试时训练要解决的就是这件事在推理服务侧用 LoRA 做增量更新让智能体在一条 episode 内部持续微调自己。它的关键洞察是——智能体轨迹本身是动态增长的训练数据源每一步策略输出都会变成后续训练样本形成内生自训练循环。全新观测多的时候迭代能强化有效决策一旦陷入循环训练文本高度重复梯度会不断放大无效行为把探索空间锁死。所以 aTTT 不整段丢弃重复文本而是做 token 层级损失加权对历史重复 n-gram 对应的 token 降权全新 token 保留完整梯度。这篇就带你用 TaoToken 统一 Key 把这条链路跑通一份可复现的最小闭环包含 config.toml 与 settings.json 骨架、在线更新触发条件、回滚策略以及一轮能直接执行的验证动作和观测指标。适合已经在写智能体、想在自己的项目里试测试时训练的开发者。2. TaoToken 前置统一 Key 与 API 通道准备aTTT 的最小闭环里模型调用会出现在三个地方智能体主策略推理、轨迹摘要生成Summary 分支、以及可选的失败模式诊断。如果每个环节各接一套 Key配置会散得到处都是回滚和复现都麻烦。用 TaoToken 的统一 Key 和 API 通道可以把这些调用收敛到一份配置里。TaoToken 在这里的角色是统一的模型接入层一个 Key、一个 base_url就能覆盖对话、编码、Agent 等不同调用场景。对 aTTT 来说最实际的价值是——主策略和摘要模型可以走同一个通道切换模型只改配置不改代码方便你做 Self / Env / Summary 三类训练文本来源的对比实验。你需要准备的东西不多一个 TaoToken 账号拿到 API Key本地能跑 Python 的环境建议 3.10一个可选的 vLLM 服务如果你要复现并发 LoRA 那套只跑单条闭环的话先用 API 通道验证逻辑即可。拿 Key 的入口在控制台的 API Keys 页面创建后复制保存。接入文档里有各语言的最小调用示例建议先照着跑一个 hello world确认通道通了再往下做。注意Key 只存在服务端环境变量或本地未提交的配置文件里别写进会进 git 的代码。aTTT 会频繁调用模型Key 泄露的代价比普通项目更高。3. 可复制配置config.toml 与 settings.json 骨架下面这份配置把“模型通道”和“aTTT 超参”分开config.toml 管接入settings.json 管训练闭环参数。这样你调 LoRA 秩、更新间隔 K 的时候不会碰到 Key 相关的东西。3.1 config.toml统一通道与模型映射# config.toml [taotoken] base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} # 从环境变量读取别硬编码 timeout 120 # 主策略模型智能体推理用 [models.policy] model qwen3.5-9b temperature 0.7 max_tokens 2048 # 摘要模型生成 Summary 分支训练文本 [models.summary] model qwen3.5-9b temperature 0.2 max_tokens 512 # aTTT 训练引擎本地 LoRA 更新不走 API [att] enabled true update_interval_k 5 # 每 5 个环境步更新一次 lora_rank 8 lora_alpha 16 learning_rate 5.0e-4 grad_steps 2 ngram_n 3 w_min 0.05 text_source self # self | env | summary几个参数值得单独说。update_interval_k 5是论文里的默认值K1 更新太密、文本新颖度低K5/10 在收益和重复度之间比较平衡。lora_rank 8也是默认消融显示秩 4~16 性能接近秩 32 反而轻微下滑。w_min 0.05是 token 权重下限保证再重复的 token 也不会被完全清零梯度。3.2 settings.json触发条件与回滚策略{ att: { trigger: { min_steps_before_first_update: 5, update_interval_k: 5, novelty_threshold: 0.5, max_updates_per_episode: 10 }, rollback: { enable: true, kl_divergence_limit: 0.5, repetition_ratio_limit: 0.8, on_trigger: reset_lora_to_episode_start }, observability: { log_repetition_ratio: true, log_token_weights: false, log_kl_every_n_updates: 1 } } }触发条件这块novelty_threshold 0.5对应论文里的序列过滤基线思路当前更新文本的新颖度低于阈值就直接跳过本轮不浪费一次梯度更新。max_updates_per_episode 10是防止单条 episode 无限更新实际跑的时候按任务长度调。回滚策略是这套闭环里最容易被忽略、但上线必须有的部分。论文诊断实验里无过滤的在线 TTT 迭代后 KL 散度会超过 2参数大幅偏移基础模型能力被破坏aTTT 靠 token 加权把 KL 稳定压在 0.5 以下。所以kl_divergence_limit 0.5是个合理的红线一旦越过就把 LoRA 重置回 episode 开始时的状态。repetition_ratio_limit 0.8对应失败 episode 中后期重复度持续 80% 以上的观测触顶就回滚。3.3 更新文本的三类来源怎么选论文给了三个分支配置里用text_source切换来源取值特点适用Selfself智能体自身推理与动作自循环最强9B 级别模型优先Envenv环境原生观测无模型自生成内容4B 级别模型优先Summarysummary独立 LLM 压缩轨迹摘要有额外推理开销12B 级别、长轨迹任务实测下来最优来源跟模型规模强相关没有一刀切。建议先用self跑通再按你的模型换env或summary对比。4. 跑通最小闭环从轨迹采集到 LoRA 更新这一节给的是可执行骨架重点在流程而不是完整工程。核心是四步采集轨迹文本、算重复度与 token 权重、加权损失更新 LoRA、把更新后的适配器挂回推理。4.1 采集更新文本# att_buffer.py from dataclasses import dataclass, field dataclass class TrajectoryBuffer: steps: list field(default_factorylist) update_texts: list field(default_factorylist) def add_step(self, obs: str, action: str, thought: str ): self.steps.append({obs: obs, action: action, thought: thought}) def get_update_text(self, source: str self) - str: last self.steps[-1] if source self: return f{last[thought]}\n{last[action]} if source env: return last[obs] raise ValueError(summary 分支需单独调用摘要模型)论文里只取最近一步文本做更新避免重复训练全部历史轨迹。LoRA 适配器在单条 episode 内持续生效跨 episode 重置——这点在配置的on_trigger里已经体现。4.2 计算重复度与 token 权重# att_weight.py def jaccard(a: set, b: set) - float: if not a or not b: return 0.0 return len(a b) / len(a | b) def repetition_ratio(current: str, history: list[str]) - float: cur_words set(current.split()) if not history: return 0.0 return max(jaccard(cur_words, set(h.split())) for h in history) def token_weights(tokens: list[str], history_tokens: list[str], n: int 3, w_min: float 0.05) - list[float]: weights [] for j in range(len(tokens)): max_freq 0 for start in range(max(0, j - n 1), j 1): gram .join(tokens[start:start n]) if len(tokens[start:start n]) n: continue freq sum(1 for i in range(len(history_tokens) - n 1) if .join(history_tokens[i:i n]) gram) max_freq max(max_freq, freq) weights.append(max(w_min, 1.0 / (1.0 max_freq))) return weights这段对应论文 3.3 的公式w_{k,j} max(w_min, 1/(1f_k(j)))重复越高权重越低。novelty 1 - repetition_ratio低于novelty_threshold就跳过本轮。4.3 加权损失更新 LoRA# att_train.py import torch def attt_loss(logits, labels, weights): # logits: [L, vocab], labels: [L], weights: [L] ce torch.nn.functional.cross_entropy( logits, labels, reductionnone ) w torch.tensor(weights, dtypece.dtype, devicece.device) return (w * ce).sum() / w.sum() def update_lora(model, optimizer, tokens, labels, weights, grad_steps2): for _ in range(grad_steps): logits model(tokens) loss attt_loss(logits, labels, weights) loss.backward() optimizer.step() optimizer.zero_grad() return loss.item()重复 token 的梯度被抑制全新 token 完整参与更新——这就是 aTTT 和“整段丢弃重复文本”的本质区别。论文消融里随机丢弃同等比例的样本没有性能提升说明真正有害的是重复内容本身不是更新次数。4.4 挂回推理与回滚# att_runtime.py def maybe_update(buffer, model, optimizer, cfg, episode_state): if len(buffer.steps) % cfg[update_interval_k] ! 0: return if episode_state[updates] cfg[max_updates_per_episode]: return text buffer.get_update_text(cfg[text_source]) ratio repetition_ratio(text, buffer.update_texts) if 1 - ratio cfg[novelty_threshold]: return # 新颖度不足跳过 tokens text.split() hist_tokens .join(buffer.update_texts).split() weights token_weights(tokens, hist_tokens, cfg[ngram_n], cfg[w_min]) loss update_lora(model, optimizer, tokens, tokens, weights, cfg[grad_steps]) buffer.update_texts.append(text) episode_state[updates] 1 # 回滚检查 kl estimate_kl(model, episode_state[base_logits]) if kl cfg[kl_divergence_limit] or ratio cfg[repetition_ratio_limit]: reset_lora(model, episode_state[episode_start_state]) episode_state[rollbacks] 1estimate_kl和reset_lora按你的框架实现核心是KL 或重复度触顶就回到 episode 起点状态。这一步不做长 episode 跑久了基础能力会被拖垮。5. 验证请求与观测指标配置和代码就位后跑一轮验证。目标不是刷榜而是确认闭环真的在动、指标真的可观测。5.1 一轮可执行的验证动作先确认通道通curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: qwen3.5-9b, messages: [{role: user, content: 回复 ok}], max_tokens: 8 }返回里有choices[0].message.content就说明 Key 和通道没问题。然后跑一条短 episode打开log_repetition_ratio观察三件事每 5 步是否触发一次更新看updates计数重复度曲线是走低还是走高成功轨迹应稳步下降失败轨迹中后期会飙到 80% 以上KL 是否稳定在 0.5 以下。5.2 观测指标对照指标健康区间异常含义更新文本重复度成功轨迹持续走低持续 0.8 说明陷入循环KL 散度 0.5 2 说明参数大幅偏移需回滚每 episode 更新次数3~10触顶说明轨迹过长或阈值过松任务完成步数明显低于步数上限论文里 aTTT 成功样本平均 26.3 步基线耗尽 50 步论文里 aTTT 在 ALFWorld 最高提升 5.0 个百分点、SWE-bench Lite 最高 4.9 个百分点增益集中在“模型本身有任务能力、但长轨迹下持续漂移”的场景。所以验证时别期待从零学会新技能重点看长轨迹末段的成功率有没有回来。6. 本篇常见错排查更新不触发。先看len(buffer.steps) % update_interval_k步数没到 K 的倍数不会更新再看新颖度1 - ratio novelty_threshold会直接跳过。日志里把这两个值打出来一眼能定位。KL 一路涨到 2 以上。大概率是没开 token 加权或者w_min设得太高比如 0.5重复 token 梯度没被压住。检查token_weights是否真的按 n-gram 频次算的别退化成均匀权重。LoRA 更新后推理变慢或报错。适配器热插拔的时机要对更新完再挂回别在推理中途换。并发场景下每条 episode 用独立 LoRA 插槽别共用。Summary 分支调用失败。Summary 需要额外调一次摘要模型走的是models.summary配置。如果报 401检查 Key 是否被环境变量正确注入如果报超时把timeout调大。跨 episode 没重置。论文明确 LoRA 在单 episode 内持续、跨 episode 重置。如果发现第二条 episode 一开始就表现异常检查reset_lora是否在 episode 结束时被调用。重复度算出来恒为 0。多半是history为空或分词方式不一致。W(x)按空格分词中文场景要换成合适的分词器否则 Jaccard 相似度会失真。7. 下一步把闭环接到你的项目里跑通最小闭环之后最值得做的对比实验是换text_source同一模型分别用self、env、summary各跑一组看哪类文本在你的任务上收益最大。论文里 4B 选 Env、9B 优先 Self、12B Summary 最佳这个分化很可能是模型规模与自生成文本质量的权衡你的场景未必一样。如果你要把这套东西长期跑在编码类 Agent 上调用量会比单条验证大得多可以看看 Coding Plan 这类面向长期编码场景的方案把主策略和摘要的调用成本压下来。接入细节和参数说明都在接入文档里模型对话的调试入口在模型对话页面Key 管理在 API Keys。先把单条 episode 的重复度曲线跑出来再谈并发和调参——顺序反了容易在噪声里打转。
返回列表