
最近在尝试让大模型学会使用工具时发现一个普遍痛点模型在训练后期mid-training阶段其工具调用能力往往陷入瓶颈。网上能找到的公开工具使用数据集要么过于简单要么与复杂业务场景脱节导致模型“纸上谈兵”一到真实环境就“掉链子”。本文要介绍的MidTool正是为解决这一难题而生——它是一套专注于Mid-training Data Synthesis中期训练数据合成的方法论与实践框架旨在为Agentic Tool Use智能体工具使用生成高质量、高难度的训练数据。无论你是正在构建企业级AI助手的开发者还是研究智能体工具学习方向的学生本文都将为你提供一套从核心概念到完整代码实现的闭环方案。你将掌握如何利用MidTool的思想为你的大模型“定制”训练数据从而显著提升其在复杂、多步骤场景下的工具调用鲁棒性与准确性。1. 背景与核心概念为什么需要MidTool在深入代码之前我们首先要厘清几个关键概念理解现有方法的局限性以及MidTool要解决的核心问题。1.1 什么是Agentic Tool UseAgentic Tool Use即智能体工具使用指的是大语言模型LLM或更广泛的AI智能体能够理解用户指令并自主选择、调用外部工具如API、函数、数据库查询、计算器等来完成复杂任务的能力。例如用户说“查一下北京明天飞上海的航班并告诉我最便宜的那一班。” 智能体需要调用“航班查询API”和“价格排序”函数。用户说“分析上个月销售数据生成一个趋势图发我邮箱。” 智能体需要调用“数据库查询”、“数据分析”和“邮件发送”等多个工具。这要求模型不仅要有强大的语言理解能力还要具备工具规划、参数理解、结果解析和错误处理等一系列子能力。1.2 传统训练数据的瓶颈目前训练模型使用工具的主流方法是指令微调Instruction Tuning。我们需要一个格式为(指令 工具调用序列 工具返回结果 最终答案)的数据集。然而这类数据集存在明显问题数量与质量不足高质量、标注精准的工具使用对话数据稀缺且构建成本极高。分布偏差公开数据集如ToolBench、Gorilla中的工具调用模式相对固定缺乏长链条、多分支、带噪声的真实业务场景。“中期高原”现象模型在训练初期工具调用能力提升很快但到了中期mid-training使用现有数据继续训练性能提升微乎其微陷入瓶颈。这是因为模型已经“学会”了数据集中所有的模式缺乏更具挑战性的样本来驱动进一步学习。1.3 MidTool的核心思想MidTool的提出正是为了突破“中期高原”。它的核心思想是在模型训练的中期动态地、有针对性地合成新的、高难度的训练数据从而持续刺激模型学习更复杂的工具使用逻辑。与传统数据增强不同MidTool的数据合成是“任务导向”和“能力短板导向”的任务导向针对你想要模型掌握的特定复杂任务如“多条件筛选后生成报告”来生成数据。能力短板导向通过评估发现模型在“参数校验”或“错误处理”上薄弱就专门合成包含相关挑战的数据。简单来说MidTool不是一个固定的数据集而是一个数据合成引擎它可以根据你模型当前的状态和你的目标源源不断地制造出“刚好比模型当前能力高一点”的训练样本。2. 环境准备与版本说明我们将使用Python来实现一个简化版的MidTool数据合成流程。这个示例将展示如何为“天气查询出行建议”工具组合生成训练数据。环境要求操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)。本文示例在Linux环境下开发。Python: 3.8 或 3.9。建议使用3.9以获得更好的兼容性。关键库openai(或其它LLM API客户端): 用于调用大模型作为“数据合成器”。版本0.27.0。pydantic: 用于数据验证和结构化。版本2.0.0。langchain: 可选用于简化智能体流程构建。版本0.1.0。jinja2: 用于模板渲染。版本3.0.0。版本兼容性说明本文重点在于阐述MidTool的流程与架构代码示例中的LLM调用部分使用OpenAI格式的API进行演示。你可以轻松替换为其他兼容的模型如DeepSeek、GLM等。相关库的版本请根据你的项目实际情况调整。项目结构预览midtool_demo/ ├── requirements.txt ├── config.yaml ├── src/ │ ├── __init__.py │ ├── core/ │ │ ├── __init__.py │ │ ├── data_models.py # 数据模型定义 │ │ ├── synthesizer.py # 核心合成器 │ │ └── evaluator.py # 简易评估器 │ ├── tools/ │ │ ├── __init__.py │ │ └── weather_tools.py # 模拟工具定义 │ └── main.py # 主程序入口 └── data/ ├── templates/ # 合成模板 └── synthesized/ # 生成的训练数据3. MidTool核心架构与原理拆解一个完整的MidTool系统包含几个关键模块我们来逐一拆解其原理和实现要点。3.1 模块一工具定义与能力描述要让模型学会使用工具首先必须清晰、结构化地定义工具。这不仅是给模型看的也是给数据合成器看的。核心要素工具名称name唯一标识符。工具描述description用自然语言描述工具的功能这是模型理解工具用途的关键。参数列表parameters每个参数的名称、类型、描述、是否必填。返回结果描述returns说明工具调用成功后会返回什么信息。为什么需要结构化定义对于模型它是调用工具的“说明书”。对于数据合成器它是生成合理工具调用指令和参数的“约束条件”。例如合成器知道get_weather工具需要city参数就不会生成一个询问股票价格的指令来调用它。3.2 模块二数据合成器Synthesizer这是MidTool的大脑。它的输入是工具定义和合成策略输出是高质量的(指令 工具调用序列...)对。合成策略举例基于模板的合成预定义一些指令模板和参数填充规则。优点是可控缺点是多样性有限。示例模板“帮我查一下{city}今天和明天的天气如果明天有雨提醒我带伞。”基于LLM的合成让一个更强大的LLM如GPT-4扮演“指令设计者”根据工具定义自动生成多样、复杂的用户指令和对应的工具调用步骤。这是MidTool发挥威力的关键。提示词Prompt示例“你是一个训练数据生成专家。给定以下工具描述请生成5条用户指令。这些指令应需要组合使用多个工具包含模糊表述、多轮条件等复杂情况。工具描述...”基于对抗的合成用一个“学生模型”尝试执行指令再用一个“评判模型”找出执行中的错误或不足然后针对这些弱点合成新的数据。3.3 模块三流程模拟器Simulator与结果生成生成指令和工具调用序列后我们还需要模拟工具执行产生工具返回结果。在训练阶段我们无法调用真实API所以需要模拟。模拟方法确定性模拟根据调用参数返回预设的合理结果。例如对于get_weather(city“北京”)固定返回{“temperature”: 22, “condition”: “晴”}。随机性模拟在合理范围内随机生成结果增加数据多样性。例如温度在15-30度之间随机天气状况从[“晴”, “多云”, “小雨”]中随机选择。错误注入模拟故意模拟工具调用失败如网络超时、参数错误、权限不足生成包含错误处理的数据训练模型的鲁棒性。3.4 模块四评估与过滤不是所有合成数据都是好数据。我们需要一个评估环节来过滤掉低质量或无效的数据。评估维度指令清晰度用户指令是否自然、无歧义工具调用合理性生成的工具调用序列是否能解决用户问题是否符合逻辑结果一致性模拟的工具返回结果是否与调用匹配难度适中数据是否对当前模型具有挑战性这需要连接模型当前的评估状态4. 完整实战案例构建一个简易MidTool数据合成管道下面我们动手实现一个简化版的MidTool为两个工具生成训练数据。4.1 步骤一定义工具和数据模型首先我们定义两个模拟工具get_weather获取天气和suggest_activity建议活动。# file: src/tools/weather_tools.py from typing import Dict, Any def get_weather(city: str, date: str today) - Dict[str, Any]: 模拟获取城市天气信息的工具。 Args: city: 城市名例如“北京”、“上海”。 date: 日期默认为“today”。也可以是“tomorrow”或具体的日期字符串。 Returns: 一个包含天气信息的字典。 # 这是一个模拟函数真实场景会调用API weather_map { 北京: {today: {temp: 22, condition: 晴}, tomorrow: {temp: 18, condition: 小雨}}, 上海: {today: {temp: 25, condition: 多云}, tomorrow: {temp: 26, condition: 阴}}, } city_data weather_map.get(city, {}) return city_data.get(date, {temp: 20, condition: 未知}) def suggest_activity(weather_condition: str, temperature: int) - str: 根据天气情况建议户外活动。 Args: weather_condition: 天气状况如“晴”、“雨”、“雪”。 temperature: 温度单位摄氏度。 Returns: 活动建议字符串。 if weather_condition 晴 and temperature 20: return 适合进行户外运动如跑步、骑行。 elif weather_condition 雨: return 建议进行室内活动如看电影、逛博物馆。 elif temperature 10: return 天气较冷建议室内活动或注意保暖。 else: return 可以根据个人喜好安排活动。接下来我们用Pydantic定义严格的数据结构确保合成数据的格式正确。# file: src/core/data_models.py from pydantic import BaseModel, Field from typing import List, Dict, Any, Optional class ToolParameter(BaseModel): 工具参数定义 name: str type: str # e.g., string, integer description: str required: bool True class ToolDefinition(BaseModel): 工具定义 name: str description: str parameters: List[ToolParameter] returns: str class ToolCall(BaseModel): 单次工具调用 tool_name: str parameters: Dict[str, Any] Field(default_factorydict) class TrainingExample(BaseModel): 一个完整的训练样本 instruction: str # 用户指令 tool_calls: List[ToolCall] # 工具调用序列 tool_results: List[Dict[str, Any]] # 每次调用的模拟结果 final_answer: str # 模型应给出的最终回答4.2 步骤二实现基于LLM的数据合成器我们使用OpenAI API或兼容API作为“指令设计者”。核心是构造一个有效的提示词Prompt。# file: src/core/synthesizer.py import openai import yaml from typing import List from .data_models import ToolDefinition, TrainingExample, ToolCall import random from jinja2 import Template class LLMBasedSynthesizer: def __init__(self, api_key: str, base_url: str https://api.openai.com/v1, model: str gpt-3.5-turbo): # 初始化客户端base_url允许你指向其他兼容服务 self.client openai.OpenAI(api_keyapi_key, base_urlbase_url) self.model model def synthesize(self, tool_defs: List[ToolDefinition], num_examples: int 3) - List[TrainingExample]: 核心合成方法。 # 1. 构建提示词 prompt self._build_synthesis_prompt(tool_defs, num_examples) # 2. 调用LLM response self._call_llm(prompt) # 3. 解析LLM的回复这里假设LLM返回结构化文本实际可能需要更复杂的解析或使用Function Calling examples_text response.choices[0].message.content parsed_examples self._parse_examples(examples_text, tool_defs) # 4. 为每个示例模拟工具执行并生成最终答案 full_examples [] for example in parsed_examples: full_example self._simulate_and_answer(example, tool_defs) if full_example: full_examples.append(full_example) return full_examples def _build_synthesis_prompt(self, tool_defs: List[ToolDefinition], num_examples: int) - str: 构建指令合成提示词 tools_desc \n.join([f- {td.name}: {td.description} for td in tool_defs]) prompt_template 你是一个AI训练数据生成专家。你的任务是根据提供的工具描述生成高质量、多样化的用户指令和对应的工具调用步骤。 可用工具如下 {{ tools_desc }} 请生成 {{ num_examples }} 条用户指令并给出解决该指令所需的工具调用序列。要求如下 1. **指令需自然、口语化**像真实用户提出的问题。 2. **需要组合使用多个工具**或一个工具被多次调用。 3. **可以包含复杂逻辑**如条件判断如果...就...、多轮信息确认、模糊表述等。 4. **工具调用序列必须严格基于上述工具定义**参数需合理。 请按以下格式输出每条示例之间用‘---’分隔 指令[用户指令文本] 工具调用序列 1. 工具名: [工具1名称], 参数: {“参数1”: “值1”, “参数2”: “值2”} 2. 工具名: [工具2名称], 参数: {“参数1”: “值1”} ... template Template(prompt_template) return template.render(tools_desctools_desc, num_examplesnum_examples) def _call_llm(self, prompt: str): 调用LLM API try: response self.client.chat.completions.create( modelself.model, messages[{role: user, content: prompt}], temperature0.7, # 一定的随机性以保证多样性 max_tokens1500 ) return response except Exception as e: print(f调用LLM失败: {e}) # 此处应加入降级策略例如回退到模板合成 raise def _parse_examples(self, text: str, tool_defs: List[ToolDefinition]) - List[dict]: 简易解析LLM返回的文本。实际项目应使用更鲁棒的解析如JSON模式。 examples [] raw_blocks text.split(---) for block in raw_blocks: lines block.strip().split(\n) if not lines or 指令 not in lines[0]: continue instruction lines[0].replace(指令, ).strip() tool_calls [] in_sequence False for line in lines[1:]: line line.strip() if line.startswith(工具调用序列): in_sequence True continue if in_sequence and line and line[0].isdigit(): # 简易解析例如: 1. 工具名: get_weather, 参数: {\city\: \北京\} parts line.split(参数:) if len(parts) 2: tool_part parts[0] tool_name tool_part.split(:)[-1].strip() # 这里简化处理实际需要安全地eval或json.loads参数 import json try: params json.loads(parts[1].strip()) except: params {} tool_calls.append({tool_name: tool_name, parameters: params}) if instruction and tool_calls: examples.append({instruction: instruction, tool_calls_raw: tool_calls}) return examples def _simulate_and_answer(self, example: dict, tool_defs: List[ToolDefinition]) - Optional[TrainingExample]: 模拟工具执行并生成最终答案 tool_calls [] tool_results [] # 将原始调用信息转换为ToolCall对象并模拟执行 for call_raw in example[tool_calls_raw]: tool_name call_raw[tool_name] params call_raw[parameters] # 查找工具定义 tool_def next((td for td in tool_defs if td.name tool_name), None) if not tool_def: print(f警告未找到工具定义 {tool_name}) continue # 创建ToolCall对象 tool_call ToolCall(tool_nametool_name, parametersparams) tool_calls.append(tool_call) # 模拟执行这里需要映射到实际的工具函数 result self._simulate_tool_execution(tool_name, params) tool_results.append(result) # 基于所有工具结果生成最终答案这里可以再用一个LLM或规则 final_answer self._generate_final_answer(example[instruction], tool_calls, tool_results) return TrainingExample( instructionexample[instruction], tool_callstool_calls, tool_resultstool_results, final_answerfinal_answer ) def _simulate_tool_execution(self, tool_name: str, params: dict) - dict: 模拟工具执行返回结果 # 这里应有一个工具名到模拟函数的映射 if tool_name get_weather: from ..tools.weather_tools import get_weather city params.get(city, 北京) date params.get(date, today) return get_weather(city, date) elif tool_name suggest_activity: from ..tools.weather_tools import suggest_activity # 注意suggest_activity需要weather_condition和temperature这里需要从上下文或假设获取 # 这是一个简化示例实际中可能需要更复杂的上下文管理 condition params.get(weather_condition, 晴) temp params.get(temperature, 22) return {suggestion: suggest_activity(condition, temp)} else: return {error: f未知工具 {tool_name}, result: None} def _generate_final_answer(self, instruction: str, tool_calls: List[ToolCall], tool_results: List[dict]) - str: 生成最终答案。简化版使用规则或另一个LLM调用。 # 此处为简化我们写一个基于规则的简单生成器 if 天气 in instruction and 活动 in instruction: # 假设最后一个工具调用是suggest_activity for call, result in zip(tool_calls, tool_results): if call.tool_name suggest_activity: return f根据查询到的天气情况建议您{result.get(suggestion, 暂无具体建议。)} return 已根据您的指令完成信息查询和处理。4.3 步骤三配置与运行主程序创建一个配置文件来管理API密钥和模型设置。# file: config.yaml llm: api_key: your_openai_api_key_here # 请替换为你的实际API密钥 base_url: https://api.openai.com/v1 model: gpt-3.5-turbo tools: definitions: - name: get_weather description: 获取指定城市在指定日期的天气信息包括温度和天气状况。 parameters: - name: city type: string description: 城市名称 required: true - name: date type: string description: 日期例如 today 或 tomorrow required: false returns: 包含温度和天气状况的字典。 - name: suggest_activity description: 根据天气状况和温度给出户外活动建议。 parameters: - name: weather_condition type: string description: 天气状况如‘晴’、‘雨’、‘多云’ required: true - name: temperature type: integer description: 温度单位摄氏度 required: true returns: 活动建议字符串。编写主程序串联整个流程。# file: src/main.py import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import yaml from src.core.data_models import ToolDefinition from src.core.synthesizer import LLMBasedSynthesizer import json def load_config(config_path: str ../config.yaml): with open(config_path, r, encodingutf-8) as f: config yaml.safe_load(f) return config def main(): # 1. 加载配置 config load_config() # 2. 构建工具定义列表 tool_defs [] for tool_cfg in config[tools][definitions]: params [ToolParameter(**p) for p in tool_cfg[parameters]] tool_def ToolDefinition( nametool_cfg[name], descriptiontool_cfg[description], parametersparams, returnstool_cfg[returns] ) tool_defs.append(tool_def) # 3. 初始化合成器 llm_config config[llm] synthesizer LLMBasedSynthesizer( api_keyllm_config[api_key], base_urlllm_config.get(base_url, https://api.openai.com/v1), modelllm_config[model] ) # 4. 合成数据 print(开始合成训练数据...) try: examples synthesizer.synthesize(tool_defs, num_examples5) print(f成功合成 {len(examples)} 条数据。) except Exception as e: print(f数据合成失败: {e}) # 降级方案使用预定义的模板数据 examples load_fallback_examples() # 5. 保存数据 output_data [example.dict() for example in examples] os.makedirs(../data/synthesized, exist_okTrue) output_path ../data/synthesized/training_data.json with open(output_path, w, encodingutf-8) as f: json.dump(output_data, f, ensure_asciiFalse, indent2) print(f数据已保存至: {output_path}) # 6. 打印示例 if examples: print(\n 合成数据示例 ) sample examples[0] print(f指令: {sample.instruction}) print(工具调用:) for call in sample.tool_calls: print(f - {call.tool_name}({call.parameters})) print(f最终答案: {sample.final_answer}) def load_fallback_examples(): 降级方案加载预定义的示例数据 from src.core.data_models import TrainingExample, ToolCall # 这里可以硬编码几个高质量的示例 fallback [ TrainingExample( instruction我想知道北京明天天气怎么样如果下雨我就不去公园了。, tool_calls[ ToolCall(tool_nameget_weather, parameters{city: 北京, date: tomorrow}) ], tool_results[{temp: 18, condition: 小雨}], final_answer北京明天天气为小雨温度18度。建议您更改户外计划。 ) ] return fallback if __name__ __main__: main()4.4 步骤四运行与结果在项目根目录下安装依赖并运行。# 安装依赖 (假设已配置好Python环境) pip install openai pydantic jinja2 pyyaml # 运行主程序 cd midtool_demo python src/main.py预期输出开始合成训练数据... 成功合成 5 条数据。 数据已保存至: ../data/synthesized/training_data.json 合成数据示例 指令: 帮我查一下上海今天和明天的天气如果明天温度高于25度且是晴天就推荐一个户外活动。 工具调用: - get_weather({city: 上海, date: today}) - get_weather({city: 上海, date: tomorrow}) - suggest_activity({weather_condition: 晴, temperature: 26}) 最终答案: 根据查询到的天气情况建议您适合进行户外运动如跑步、骑行。生成的training_data.json文件其格式可以直接用于后续的模型微调例如转换为类似{messages: [{role: user, content: 指令}, {role: assistant, content: 工具调用和最终答案}]}的格式。5. 常见问题与排查思路在实际使用MidTool或类似数据合成方案时你可能会遇到以下问题问题现象可能原因排查思路与解决方案LLM生成的工具调用序列不符合定义1. 提示词描述不够清晰。2. LLM能力不足或未理解约束。3. 输出格式解析错误。1.优化提示词在提示词中更明确地要求“必须使用且仅使用提供的工具”并给出更严格的输出格式示例。2.使用Function Calling如果LLM支持使用其Function Calling功能来约束输出而非自由文本。3.后处理与过滤增加一个验证步骤检查生成的工具调用是否在定义列表中参数类型是否匹配过滤掉无效数据。合成数据多样性不足1. 提示词引导性过强。2. LLM温度temperature参数设置过低。3. 工具定义本身限制。1.调整提示词要求生成“多样化的”、“涵盖不同场景的”指令。2.调整LLM参数适当提高temperature(如0.8-1.0) 以增加随机性。3.引入多种合成策略混合使用基于模板、基于LLM和基于对抗的合成方法。模拟的工具结果不真实或与指令无关1. 模拟逻辑过于简单或随机。2. 缺少上下文关联。1.增强模拟器建立更复杂的模拟规则库甚至可以用一个LLM来根据调用参数生成合理的模拟结果。2.维护对话状态在合成多轮对话数据时需要维护一个上下文状态确保工具返回结果与历史调用一致。生成速度慢成本高1. 频繁调用大模型如GPT-4。2. 合成流程复杂。1.分层合成使用小模型如GPT-3.5-Turbo生成初稿再用大模型或规则进行润色和校验。2.缓存与复用对相似的指令或工具组合复用已生成的数据模板。3.本地模型在数据质量要求不极端的情况下尝试使用开源的、可本地部署的大模型进行合成。数据格式与训练框架不匹配1. 生成的TrainingExample格式与训练代码预期不符。1.定义转换器编写一个适配层将MidTool的核心数据格式TrainingExample转换为目标训练框架如Hugging Face Datasets、OpenAI格式所需的格式。确保转换过程可逆、可追溯。6. 最佳实践与工程建议将MidTool思想应用到生产环境或严肃的研究项目中需要考虑更多工程细节。6.1 数据质量评估体系不要盲目相信合成数据。建立一套评估体系自动评估使用规则或另一个LLM评判者对合成数据的指令清晰度、工具调用合理性、结果一致性进行打分。抽样人工评估定期抽样一批数据由人工标注员判断其质量。将人工评估结果反馈给合成器用于优化提示词。动态难度评估将新合成数据在当前的“学生模型”上跑一遍计算其任务成功率。选择那些成功率在50%-80%的数据加入训练集确保数据“有挑战但可学习”。6.2 合成策略的迭代与闭环MidTool应是一个动态、自适应的系统训练模型使用当前数据集训练模型。评估模型在保留的验证集或新任务上评估模型找出薄弱环节例如不擅长处理多工具条件组合。定向合成针对薄弱环节调整合成策略例如在提示词中强调“请生成需要复杂条件判断的指令”生成一批新数据。数据去重与融合将新数据与旧数据去重、混合形成新的训练集。回到步骤1形成闭环。6.3 安全与可控性合成数据可能产生有害或带有偏见的指令。内容过滤在合成管道中加入内容安全过滤器过滤掉涉及暴力、歧视、违法等内容的指令。工具权限控制在工具定义中明确其安全边界。合成器不应生成需要调用“删除数据库”或“发送邮件”等高风险工具的指令除非在极其可控的模拟环境中。模拟环境隔离所有工具调用必须在完全模拟的“沙箱”环境中进行绝不能触及真实系统或数据。6.4 与现有工作流的整合版本化管理对工具定义、合成提示词、生成的数据集进行版本控制如Git。便于回滚和复现实验。流水线化使用Airflow、Kubeflow Pipelines或简单的Python脚本将数据合成、评估、训练、部署流程自动化。监控与日志记录每一次数据合成的元信息如使用的提示词、LLM参数、生成数量、质量评分便于分析和调试。通过以上实践MidTool从一个简单的数据生成脚本升级为一个可持续、可评估、可迭代的AI能力增强引擎。它能帮助你的智能体在工具使用的道路上不断突破瓶颈越来越“聪明”和“可靠”。