☰
SFT 完全指南:从数据构造到 Agent 微调,用 TaoToken 统一 Key 打通 LLaMA-Factory 训练链路
2026/9/26 18:28:50 网站建设 项目流程

1. 为什么 SFT 训练链路总在 Key 上翻车

SFT(Supervised Fine-Tuning,监督微调)是把预训练模型从"会说话"变成"会干活"的关键一步。但真正跑过 LLaMA-Factory 的人都知道,训练本身不难,难的是训练完之后那一段——你要验证模型有没有学会工具调用,要拿它去跑 Agent 任务,要对比微调前后的行为差异。这时候你会发现,数据构造阶段用的一个 Key、训练脚本里写死的另一个 Key、推理验证时又换一个 Key,三套配置互相打架,改一处忘一处,最后连自己都搞不清哪次请求走的是哪个通道。

我试过最离谱的一次:数据合成脚本里用的是 A 平台的 Key,LLaMA-Factory 的config.toml里写的是 B 平台的 endpoint,推理验证时又临时 export 了 C 平台的变量。结果模型明明微调得不错,Agent 调用却一直报 401,排查了两个小时才发现是环境变量没继承到子进程。这种问题不是技术难点,纯粹是配置割裂带来的隐性成本。

这篇要解决的就是这件事:用 TaoToken 的统一 Key 把"数据构造 → LLaMA-Factory 训练 → Agent 推理验证"整条链路串起来,只维护一份凭证,只改一个地方。适合正在做 Agent 微调、被多套 Key 折腾过的同学,也适合刚接触 LLaMA-Factory、想一次把链路跑通的新手。下面会给可复制的config.toml骨架、数据构造脚本、训练配置和验证动作,每一步都能直接跟做。

2. TaoToken 统一 Key 的前置准备

TaoToken 在这里扮演的角色是"统一入口":它提供一个兼容 OpenAI 接口规范的 endpoint,你的数据合成脚本、LLaMA-Factory 的推理后端、Agent 验证脚本,全部指向同一个 base_url 和同一个 API Key。这样你不需要在三个地方分别配置三家平台的凭证,也不需要担心某个平台的模型突然下线导致链路断掉。

具体要准备的东西只有三样:

第一,一个 TaoToken 账号和对应的 API Key。登录官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,进控制台创建 Key。建议给这个 Key 起个明确的名字,比如sft-agent-pipeline,方便后面区分。

第二,确认你要用的模型名。TaoToken 的模型列表在文档里有,数据合成阶段通常用能力强的模型(比如 Claude 系列或 GPT 系列),训练阶段 LLaMA-Factory 本身不消耗 Key(它是本地训练),但推理验证阶段要用同一个 Key 去调模型。

第三,把 base_url 记牢:https://taotoken.net/api。注意这个地址不带任何查询参数,是纯粹的 API 入口。所有 OpenAI SDK 兼容的调用都指向它。

注意:不要把官网地址和 API 地址搞混。官网是给人看的,API 是给代码调的。写进config.toml和脚本里的必须是https://taotoken.net/api。

环境变量建议这样设,一次设好,后面所有脚本都读它:

export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

如果你在 Windows 上用 PowerShell:

$env:TAOTOKEN_API_KEY="sk-你的key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"

设完之后用echo $TAOTOKEN_API_KEY确认一下,避免设了个空值自己还不知道。

3. 可复制的配置:从数据构造到 LLaMA-Factory

3.1 数据构造脚本(统一 Key 调用)

Agent SFT 的数据核心是"多轮对话 + 工具调用轨迹"。下面这个脚本用 TaoToken 统一 Key 合成轨迹数据,输出成 LLaMA-Factory 能直接吃的 sharegpt 格式。

import os import json from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) SYSTEM_PROMPT = """你是一个可以使用工具的 AI Agent。 可用工具: - web_search(query): 搜索网络信息 - calculator(expr): 计算数学表达式 调用工具时,输出格式为: {"tool": "工具名", "args": {参数字典}} 等待工具返回结果后,再决定下一步或给出最终答案。""" def synthesize_trajectory(task: str, model: str = "claude-sonnet-4-6") -> dict: """用统一 Key 合成一条 Agent 轨迹数据""" messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": task}, ] resp = client.chat.completions.create( model=model, messages=messages, temperature=0.7, ) assistant_reply = resp.choices[0].message.content messages.append({"role": "assistant", "content": assistant_reply}) return {"messages": messages} tasks = [ "帮我查一下北京今天的天气,然后告诉我气温换算成华氏度是多少", "读取 sales.csv 的总销售额,并搜索行业平均水平做对比", ] dataset = [] for t in tasks: dataset.append(synthesize_trajectory(t)) with open("agent_sft_data.jsonl", "w", encoding="utf-8") as f: for item in dataset: f.write(json.dumps(item, ensure_ascii=False) + "\n") print(f"生成 {len(dataset)} 条轨迹数据")

这里的关键点是base_url和api_key都从环境变量读,脚本本身不写死任何凭证。你换 Key 只需要改环境变量,脚本一行不用动。

3.2 数据注册到 LLaMA-Factory

把生成的agent_sft_data.jsonl放到 LLaMA-Factory 的data/目录下,然后在data/dataset_info.json里加一段:

{ "agent_sft": { "file_name": "agent_sft_data.jsonl", "formatting": "sharegpt", "columns": { "messages": "messages" }, "tags": { "role_tag": "role", "content_tag": "content", "user_tag": "user", "assistant_tag": "assistant", "system_tag": "system" } } }

formatting必须是sharegpt,因为我们的数据是 messages 数组格式。tags里把角色标签映射清楚,LLaMA-Factory 才知道哪段是 system、哪段是 assistant。

3.3 config.toml 骨架

LLaMA-Factory 新版支持config.toml,比 yaml 更清晰。下面这份可以直接复制,改模型路径和数据集名就行:

[model] model_name_or_path = "Qwen/Qwen3-7B-Instruct" trust_remote_code = true [finetuning] stage = "sft" do_train = true finetuning_type = "lora" lora_rank = 16 lora_alpha = 32 lora_dropout = 0.05 lora_target = "q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj" [data] dataset = "agent_sft" template = "qwen" cutoff_len = 4096 max_samples = 10000 overwrite_cache = true [training] per_device_train_batch_size = 2 gradient_accumulation_steps = 4 learning_rate = 5.0e-5 num_train_epochs = 3 lr_scheduler_type = "cosine" warmup_ratio = 0.05 bf16 = true logging_steps = 10 save_steps = 200 save_total_limit = 3 output_dir = "./output/agent-qwen3-sft" [inference] # 推理验证阶段走 TaoToken 统一 Key # 这里不写 Key,由环境变量注入

启动训练:

llamafactory-cli train config.toml

多卡的话在[training]里加一行deepspeed = "examples/deepspeed/ds_z3_config.json",然后同样用llamafactory-cli train config.toml启动。

注意:learning_rate对 Agent SFT 建议比普通指令微调低一点。工具调用格式的学习对学习率很敏感,5e-5 是个稳妥起点,如果 loss 震荡就降到 2e-5。

4. 验证请求:微调前后 Agent 调用对比

训练跑完之后,最关键的验证动作是:用同一个 TaoToken Key,分别调微调前的基座模型和微调后的模型,看 Agent 行为有没有变化。

4.1 微调前基线

import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) test_task = "帮我查一下北京今天的天气,然后换算成华氏度" resp = client.chat.completions.create( model="Qwen/Qwen3-7B-Instruct", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": test_task}, ], ) print("微调前输出:") print(resp.choices[0].message.content)

微调前的模型大概率会直接编一段天气描述,或者输出一段自然语言而不是工具调用 JSON。这就是我们要对比的基线。

4.2 微调后验证

LLaMA-Factory 训练完的 LoRA 权重需要合并或加载。合并后用 vLLM 或 LLaMA-Factory 自带的推理接口起一个本地服务,然后把 Agent 验证脚本的 base_url 指向本地服务:

llamafactory-cli export config.toml

导出合并后的模型,然后用 vLLM 起服务:

python -m vllm.entrypoints.openai.api_server \ --model ./output/agent-qwen3-sft/merged \ --port 8000

验证脚本改成指向本地:

client = OpenAI( api_key="dummy", # 本地服务不需要真 Key base_url="http://localhost:8000/v1", ) resp = client.chat.completions.create( model="./output/agent-qwen3-sft/merged", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": test_task}, ], ) print("微调后输出:") print(resp.choices[0].message.content)

理想情况下,微调后的模型应该输出类似:

{"tool": "web_search", "args": {"query": "北京今天天气"}}

而不是一段自然语言。如果它还是输出自然语言,说明训练数据里工具调用格式的样本不够,或者 loss mask 没设对。

4.3 成功结果的判断标准

一次成功的 Agent SFT,验证时应该看到三个信号:模型主动输出工具调用 JSON、JSON 格式合法可解析、多步任务能按顺序调用多个工具。如果只满足第一个,说明格式学会了但逻辑没学会,需要补多步轨迹数据。

5. 本篇常见错排查

5.1 401 报错:Key 没继承到子进程

最常见的问题。你在 shell 里 export 了TAOTOKEN_API_KEY,但 LLaMA-Factory 或 vLLM 是通过 subprocess 启动的,环境变量没传进去。解决方法是启动命令前显式带上:

TAOTOKEN_API_KEY=$TAOTOKEN_API_KEY llamafactory-cli train config.toml

或者在 Python 脚本里用os.environ读取后显式传给 client,不要依赖 shell 继承。

5.2 对话模板用错导致乱输出

LLaMA-Factory 的template字段必须和模型匹配。Qwen3 用qwen,Llama-3 用llama3,用错了模型会输出一堆特殊 token 或者直接乱码。检查方法:训练日志里会打印实际用的 template,对一下模型名。

5.3 Loss Mask 没设对

如果训练时 loss 一直降但推理效果差,很可能是 loss mask 没把 user 和 system 部分屏蔽掉。LLaMA-Factory 默认会按 template 自动 mask,但如果你用的是自定义数据格式,需要确认train_on_inputs设为false。在config.toml的[data]段加:

train_on_inputs = false

5.4 工具调用 JSON 格式错误

Agent 数据里如果混入了格式错误的 JSON,模型会学到错误的格式。训练前跑一遍校验:

import json def validate(path): bad = 0 with open(path, encoding="utf-8") as f: for i, line in enumerate(f): item = json.loads(line) for msg in item["messages"]: if msg["role"] == "assistant" and msg.get("content", "").strip().startswith("{"): try: json.loads(msg["content"]) except json.JSONDecodeError: print(f"第 {i} 行格式错误") bad += 1 print(f"共 {bad} 条格式错误") validate("agent_sft_data.jsonl")

5.5 数据分布不均导致通用能力丢失

如果 SFT 数据全是工具调用,模型微调后会变得"只会调工具",普通对话能力下降。建议在训练集里混入 20% 左右的通用对话数据,保持基础能力。这个比例不是死的,根据你的业务场景调。

6. 把 Key 收口到一处,链路才跑得顺

整条链路跑下来,你会发现真正省事的地方在于:数据构造脚本、LLaMA-Factory 推理验证、Agent 调用测试,全部读同一个TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL。换模型、换 Key、换环境,只改环境变量,代码和配置一行不动。

如果你还在排障阶段,建议先去控制台确认 Key 状态和额度:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,然后对照接入文档检查 base_url 有没有写错:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。验证模型输出是否正常,可以直接在模型对话页面试几条:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。如果你打算长期跑 Agent 微调和编码任务,Coding Plan 会比按量计费更划算:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。

最后留一个实操建议:训练前先用 100 条数据跑一个 epoch 的冒烟测试,确认 loss 正常下降、验证输出格式正确,再上全量数据。这一步能帮你省下大量返工时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询