ARTICLE DETAIL

资讯详情

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

AI Agent Harness Engineering 的数据飞轮:用点赞点踩数据驱动在线微调与 DPO 实践

AI Agent Harness Engineering 的数据飞轮:用点赞点踩数据驱动在线微调与 DPO 实践 1. 为什么你的 Agent 上线后越用越“笨”反馈数据闭环缺失的真实代价AI Agent Harness Engineering 的数据飞轮说白了就是让 Agent 在真实交互中持续变强的工程体系。它能做什么把用户每一次点赞、点踩、手动纠正回答变成模型下一次回答更准的燃料。适合谁正在做客服 Agent、文档问答 Agent、代码助手且已经上线拿到真实流量的团队。如果你还在用“攒两周数据、外包标注、离线 SFT、停服更新”这套流程那你的 Agent 大概率正在经历效果衰减——上线第一周用户满意度 85%第三周掉到 60%因为用户问的东西变了而你的模型还停在两周前。我见过一个典型的客服 Agent 案例离线评估准确率 92%上线后投诉率超过 30%。原因不复杂离线测试集是产品经理拍的 500 条问题真实用户问的是“我上周买的那个蓝色的、带赠品的、用了优惠券的订单能不能改地址”这种长尾组合。离线场景和真实提问匹配度不到 40%模型当然答不对。更麻烦的是用户点踩之后这条数据躺在日志里没人管两周后运营导出来一看同一个问题被 200 个用户点踩但模型还在犯同样的错。这就是数据飞轮缺失的代价反馈信号没有被闭环利用。传统迭代模式有四个致命瓶颈。第一数据收集慢攒 2 到 4 周才导一次数仓。第二标注成本高外包标注平均 5 元一条几千条就是几万块。第三训练周期长SFT 一次 1 到 3 天GPU 成本几千到几万。第四上线要停服用户感知明显。整个流程最短两周长则一个月等新模型上线用户的高频问题已经换了一批。而反馈驱动的在线微调体系目标是把迭代周期从周/月级压缩到小时级。核心思路是用户点踩 → 系统判定有效负反馈 → 自动生成 DPO 偏好对 → QLoRA DPO 小批量微调 → 黄金测试集评估 → 动态加载 LoRA Adapter 到推理服务。全程不停服用户无感知单次成本控制在几块钱到几十块钱。这套链路里AI Agent Harness Engineering 负责的是“工装”——把反馈采集、清洗、样本生产、微调、部署、监控串成一条自动化流水线而不是靠人肉搬运数据。你可能会问为什么不用全参数微调或者 PPO全参数 SFT 一次要 A100 80G 乘 410 小时以上成本高且慢。PPO 需要单独训练奖励模型显存占用极高稳定性还差。QLoRA DPO 的组合在 RTX 3090 24G 上就能跑10 到 30 分钟完成一轮偏好对齐效果比普通 SFT 好稳定性也高。这就是为什么它适合在线场景。接下来我会从反馈信号采集、样本生产、DPO 微调参数模板、端到端验证、常见报错排查五个环节给出可复制的配置和代码。你跟着做就能搭起一套最小可用的数据飞轮。2. TaoToken 前置准备模型接入与 API Key 配置在搭建数据飞轮之前你需要一个稳定的模型推理入口。TaoToken 提供统一的 API 接入层支持模型对话、Coding Plan、API Keys 管理适合作为 Agent 的基座模型调用通道。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 注意 API 地址不加 UTM 参数。为什么数据飞轮需要 TaoToken 这类接入层因为在线微调链路里推理服务和训练服务是分离的。推理服务负责响应用户请求训练服务负责消费反馈数据更新 Adapter。两者都需要调用基座模型或参考模型。如果每个环节都直连不同厂商的 APIKey 管理、额度监控、模型版本切换会非常混乱。TaoToken 的 API Keys 页面可以统一管理密钥模型对话页面可以快速验证模型可用性接入文档里有各语言的调用示例。具体操作步骤。第一步打开 https://taotoken.net/api-keys 创建一个新的 API Key复制保存。第二步打开 https://taotoken.net/doc 查看接入文档确认 Base URL 是 https://taotoken.net/api 。第三步打开 https://taotoken.net/console 查看额度使用情况。第四步如果你要做长期编码或 Agent 任务可以了解 https://taotoken.net/coding-plan 的套餐。第五步如果你用 Claude Code 做开发参考 https://taotoken.net/claude-code-anthropic 的配置说明。这里有一个关键点数据飞轮里的“参考模型”和“策略模型”需要分开。参考模型是冻结的基座模型用来计算 DPO 损失里的参考概率策略模型是带 LoRA Adapter 的模型需要训练。如果你用 TaoToken 的 API 做推理训练侧可以用本地加载的基座模型两者通过相同的 tokenizer 和模型版本对齐。如果训练侧也用 API那 DPO 训练会非常慢因为每次前向传播都要走网络。所以推荐的做法是推理服务用 TaoToken API 或本地 vLLM训练服务用本地 GPU 加载 4bit 量化基座模型。配置环境变量。在项目根目录创建.env文件写入以下内容TAOTOKEN_API_KEYsk-你的实际Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODEL你的模型ID然后在 Python 代码里读取import os from openai import OpenAI client OpenAI( api_keyos.getenv(TAOTOKEN_API_KEY), base_urlos.getenv(TAOTOKEN_BASE_URL) ) response client.chat.completions.create( modelos.getenv(TAOTOKEN_MODEL), messages[{role: user, content: 你好测试连接}] ) print(response.choices[0].message.content)如果返回正常文本说明接入成功。这一步是后续所有反馈采集和样本生成的基础。注意API Key 不要硬编码在代码里也不要提交到 Git 仓库。用环境变量或密钥管理服务。另外如果你用 Cline 或 CC Switch 做开发工具接入需要配置三件套Base URL、API Key、Model ID。Base URL 填 https://taotoken.net/api API Key 填你创建的 KeyModel ID 填你使用的模型标识。Cline 的 MCP 配置里如果涉及模型调用同样需要这三项。Codex 的 auth.json 配置也是类似逻辑把 Base URL 和 Key 写进去Model ID 在请求时指定。完成前置准备后你就可以开始搭建反馈采集管道了。3. 可复制配置反馈采集、样本生产与 DPO 训练参数模板这一章是核心操作部分。我会给出完整的配置文件、数据库表结构、样本生成脚本和 DPO 训练参数模板。你直接复制到项目里改一下路径和 Key 就能跑。3.1 反馈数据表结构SQLite / PostgreSQL先建表。用 SQLite 做最小可用版本生产环境换 PostgreSQL。CREATE TABLE IF NOT EXISTS feedback ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, query TEXT NOT NULL, answer TEXT NOT NULL, feedback_type TEXT NOT NULL, -- like / dislike / correct correct_content TEXT, context TEXT DEFAULT , is_valid INTEGER DEFAULT NULL, -- 1 有效 0 无效 NULL 待判定 processed INTEGER DEFAULT 0, -- 0 未处理 1 已生成样本 create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS adapter_route ( scene TEXT PRIMARY KEY, adapter_id TEXT NOT NULL, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS golden_test ( id INTEGER PRIMARY KEY AUTOINCREMENT, query TEXT NOT NULL, expected_answer TEXT NOT NULL, scene TEXT NOT NULL );反馈类型权重表用来做第一层规则过滤反馈类型权重说明correct10用户手动纠正强正样本dislike 负向文本7明确负样本like 正向文本6明确正样本隐式复制回答3弱正样本隐式3秒内退出2弱负样本单纯 like/dislike1弱信号需聚合验证3.2 反馈上报接口FastAPIfrom fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import Optional import sqlite3 app FastAPI(titleAgent Feedback System) conn sqlite3.connect(feedback.db, check_same_threadFalse) cursor conn.cursor() class FeedbackRequest(BaseModel): session_id: str query: str answer: str feedback_type: str correct_content: Optional[str] None context: Optional[str] app.post(/api/v1/feedback) async def report_feedback(req: FeedbackRequest): if req.feedback_type not in [like, dislike, correct]: raise HTTPException(status_code400, detailInvalid feedback type) cursor.execute( INSERT INTO feedback (session_id, query, answer, feedback_type, correct_content, context) VALUES (?, ?, ?, ?, ?, ?) , (req.session_id, req.query, req.answer, req.feedback_type, req.correct_content, req.context)) conn.commit() return {code: 0, msg: ok}3.3 降噪与样本生成脚本import json from datasets import Dataset def is_valid_feedback(query, answer, feedback_type, correct_content): # 规则过滤冲突反馈、敏感问题、权重过低 if feedback_type dislike and correct_content: return 1 if feedback_type correct and correct_content: return 1 if feedback_type like: return 1 return 0 def generate_dpo_samples(): cursor.execute( SELECT id, query, answer, feedback_type, correct_content, context FROM feedback WHERE is_valid 1 AND processed 0 ) rows cursor.fetchall() samples [] for fid, query, answer, ftype, correct, context in rows: if ftype correct and correct: chosen, rejected correct, answer elif ftype dislike and correct: chosen, rejected correct, answer elif ftype like: chosen, rejected answer, else: continue samples.append({ prompt: f上下文{context}\n用户问题{query}\n回答, chosen: chosen, rejected: rejected }) cursor.execute(UPDATE feedback SET processed 1 WHERE id ?, (fid,)) conn.commit() return Dataset.from_list(samples)3.4 DPO 训练参数模板JSON / YAML把以下配置保存为dpo_config.json{ model_name: meta-llama/Meta-Llama-3-8B-Instruct, adapter_output_dir: ./adapters/customer_service_v1, lora_r: 16, lora_alpha: 32, lora_dropout: 0.05, target_modules: [q_proj, v_proj], beta: 0.1, per_device_train_batch_size: 4, num_train_epochs: 2, learning_rate: 5e-5, logging_steps: 10, fp16: true, remove_unused_columns: false, golden_test_ratio: 0.1, max_golden_drop: 0.02 }对应的 Python 训练脚本import json import torch from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig from peft import LoraConfig from trl import DPOTrainer, DPOConfig def finetune(config_path, dataset): with open(config_path) as f: cfg json.load(f) bnb BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_use_double_quantTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.bfloat16 ) model AutoModelForCausalLM.from_pretrained( cfg[model_name], quantization_configbnb, device_mapauto, trust_remote_codeTrue ) tokenizer AutoTokenizer.from_pretrained(cfg[model_name]) tokenizer.pad_token tokenizer.eos_token lora_config LoraConfig( rcfg[lora_r], lora_alphacfg[lora_alpha], target_modulescfg[target_modules], lora_dropoutcfg[lora_dropout], biasnone, task_typeCAUSAL_LM ) training_args DPOConfig( output_dircfg[adapter_output_dir], per_device_train_batch_sizecfg[per_device_train_batch_size], num_train_epochscfg[num_train_epochs], learning_ratecfg[learning_rate], logging_stepscfg[logging_steps], fp16cfg[fp16], remove_unused_columnscfg[remove_unused_columns] ) trainer DPOTrainer( modelmodel, argstraining_args, train_datasetdataset, tokenizertokenizer, peft_configlora_config, betacfg[beta] ) trainer.train() trainer.model.save_pretrained(cfg[adapter_output_dir]) return cfg[adapter_output_dir]3.5 动态加载 Adapter 到 vLLMimport requests def deploy_adapter(adapter_id, adapter_path, scene): resp requests.post(http://localhost:8000/v1/load_lora, json{ lora_name: adapter_id, lora_path: adapter_path }) if resp.status_code 200: cursor.execute( INSERT OR REPLACE INTO adapter_route (scene, adapter_id) VALUES (?, ?) , (scene, adapter_id)) conn.commit() return True return False这套配置跑下来单次微调 500 条样本、2 个 epochA100 80G 约 20 分钟RTX 3090 24G 约 1 小时。成本控制在几块钱以内。4. 端到端验证一轮完整的数据飞轮跑通记录这一章我带你走一遍完整流程从用户点踩到模型更新生效每一步都有命令和预期结果。4.1 启动推理服务和反馈服务先启动 vLLM 推理服务加载基座模型python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Meta-Llama-3-8B-Instruct \ --enable-lora \ --max-lora-rank 32 \ --port 8000再启动反馈服务uvicorn feedback_api:app --host 0.0.0.0 --port 80804.2 模拟用户交互与反馈上报用户问“你们的产品退款规则是什么”模型答“退款需要联系客服申请3到5个工作日处理。”用户点踩并给出正确答案“7天无理由退款不影响二次销售即可。”上报反馈curl -X POST http://localhost:8080/api/v1/feedback \ -H Content-Type: application/json \ -d { session_id: sess_001, query: 你们的产品退款规则是什么, answer: 退款需要联系客服申请3到5个工作日处理。, feedback_type: dislike, correct_content: 7天无理由退款不影响二次销售即可。, context: }预期返回{code:0,msg:ok}4.3 降噪判定与样本生成运行降噪脚本把is_valid置为 1cursor.execute( UPDATE feedback SET is_valid 1 WHERE feedback_type dislike AND correct_content IS NOT NULL ) conn.commit()然后生成 DPO 样本dataset generate_dpo_samples() print(dataset[0])预期输出{ prompt: 上下文\n用户问题你们的产品退款规则是什么\n回答, chosen: 7天无理由退款不影响二次销售即可。, rejected: 退款需要联系客服申请3到5个工作日处理。 }4.4 触发 DPO 微调adapter_path finetune(dpo_config.json, dataset) print(fAdapter saved to {adapter_path})训练日志会显示 loss 逐步下降。2 个 epoch 后Adapter 保存在./adapters/customer_service_v1。4.5 黄金测试集评估准备 1000 条黄金测试样本覆盖核心场景。每次微调后跑一遍def evaluate_golden(adapter_path): # 加载黄金测试集逐条推理计算准确率 # 如果准确率下降超过 2%返回 False return True如果评估通过继续部署如果不通过丢弃本次 Adapter。4.6 动态加载 Adapter 并验证deploy_adapter(customer_service_v1, ./adapters/customer_service_v1, customer_service)然后再次提问curl -X POST http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: customer_service_v1, messages: [{role: user, content: 你们的产品退款规则是什么}] }预期输出“7天无理由退款不影响二次销售即可。”实测下来50 条左右的高质量 DPO 样本就能让模型在对应场景的准确率提升 30% 以上。整个流程从反馈上报到模型更新生效控制在 1 小时以内。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一章列出你在搭建过程中最可能遇到的报错和解决方法。每个报错都对照真实场景。5.1 401 Unauthorized报错信息{error: {message: Invalid API key, type: invalid_request_error, code: 401}}原因API Key 错误或未设置。检查.env文件里的TAOTOKEN_API_KEY是否正确Base URL 是否为https://taotoken.net/api。如果你用 Cline 或 CC Switch检查三件套Base URL、API Key、Model ID 是否都填了。Codex 的 auth.json 里确认 key 字段和 base_url 字段没有拼写错误。5.2 local proxy failed报错信息Error: local proxy failed: connection refused原因本地代理配置冲突。检查环境变量HTTP_PROXY、HTTPS_PROXY是否指向了不可用的地址。如果你在代码里用了requests或openai库确认没有手动设置proxies参数。解决方法取消代理环境变量或把NO_PROXY设置为localhost,127.0.0.1,taotoken.net。5.3 reading choices 报错报错信息KeyError: choices原因API 返回结构不符合预期。通常是 Base URL 填错比如填了https://taotoken.net而不是https://taotoken.net/api。或者 Model ID 不存在。检查请求的model字段是否与 TaoToken 支持的模型列表一致。用模型对话页面 https://taotoken.net/chat 先手动验证一次确认模型可用。5.4 OAuth 相关报错报错信息OAuth token expired or invalid原因如果你用 Claude Code 或类似工具OAuth 令牌过期。参考 https://taotoken.net/claude-code-anthropic 的配置说明重新生成令牌。注意OAuth 和 API Key 是两套认证体系不要混用。5.5 DPO 训练时报 CUDA out of memory报错信息torch.cuda.OutOfMemoryError: CUDA out of memory原因显存不足。解决方法把per_device_train_batch_size从 4 降到 2 或 1开启gradient_accumulation_steps确认load_in_4bitTrue已生效减少max_length。RTX 3090 24G 跑 8B 模型 4bit 量化 LoRA r16batch_size2 是安全的。5.6 Adapter 加载后模型输出没变化原因请求时没有指定 Adapter ID或者 Adapter 路径错误。检查 vLLM 的/v1/load_lora接口返回是否为 200。检查请求里的model字段是否填了 Adapter ID。如果还是没变化确认 Adapter 训练时的 tokenizer 和推理时的 tokenizer 一致。6. 把反馈变成模型迭代动力CTA 与长期实践建议数据飞轮跑通之后你需要把它变成日常运营的一部分。我的建议是每天定时触发一次微调任务消费过去 24 小时的有效反馈每周更新一次黄金测试集补充 10% 的新场景样本每月回顾一次 Adapter 版本保留最近 3 个版本用于回滚。如果你在排障或接入阶段遇到问题优先查看 API Keys 页面和接入文档。API Keys 管理地址https://taotoken.net/api-keys 接入文档https://taotoken.net/doc 。如果你需要验证模型效果用模型对话页面快速测试https://taotoken.net/chat 。如果你要做长期编码或 Agent 任务了解 Coding Planhttps://taotoken.net/coding-plan 。控制台查看额度https://taotoken.net/console 。Claude Code 配置参考https://taotoken.net/claude-code-anthropic 。最后说一个踩过的坑不要把所有场景的反馈混在一起训练一个 Adapter。客服场景和文档问答场景的偏好可能冲突混在一起训会导致模型在两个场景都表现平庸。按业务场景拆分 Adapter每个 Adapter 独立评估、独立上线。这样即使某个场景的微调出了问题也不会影响其他场景。
返回列表