从零构建Agent测试沙盒:实战评估大模型任务执行能力

发布时间:2026/8/8 3:00:59
从零构建Agent测试沙盒:实战评估大模型任务执行能力 1. 项目概述从“模型神话”到“任务实战”的认知跃迁最近和几个做AI应用的朋友聊天发现一个挺有意思的现象大家聚在一起聊得最多的不再是哪个模型又刷新了某个榜单的分数而是“你那个Agent跑起来了吗效果怎么样”。这背后反映的恰恰是行业认知的一次集体转向。我们曾经热衷于比较GPT-4、Claude 3、DeepSeek-V3在MMLU、GSM8K这些基准测试上的零点几个百分点的差异仿佛那就是衡量模型能力的唯一标尺。但当我们真正把这些模型塞进一个需要自主规划、调用工具、与环境交互的智能体Agent框架里去完成一个哪怕看起来简单的任务时才会发现基准测试的高分和实际任务中的“靠谱”完全是两码事。这就好比评价一个赛车手不能只看他在直线加速赛上的成绩更要看他在复杂多变的F1赛道上如何超车、如何进站、如何处理突发状况。模型在静态数据集上的表现就像是直线加速而Agent在动态、开放环境中的任务执行能力才是真正的“综合赛道”。最近行业里的一些动态也印证了这一点无论是关于某些模型单日处理海量token的讨论还是各种Agent框架如Hermes Agent和教程的涌现都说明大家的焦点正在从“模型本身”快速迁移到“模型能干什么事”上。所以这个项目想探讨的核心就是如何搭建一个真实、可评估的Agent任务测试沙盒绕过华丽的模型宣传直接检验其在实际任务流中的“实战能力”。无论你是想评估开源模型如Llama、Qwen在Agent场景下的潜力还是想为自己开发的应用选择一个最“趁手”的模型大脑亦或是单纯好奇各种“智能体”宣传背后的真实水分这套方法都能给你提供一个清晰、客观的视角。我们不会停留在理论探讨而是会手把手构建测试环境设计涵盖规划、工具调用、纠错、多步推理等核心能力的任务并记录下不同模型在这些任务中的真实表现与“翻车”现场。2. 核心思路设计一个“照妖镜”式的评估沙盒要戳破宣传泡沫我们需要的不是另一个评分榜而是一个高度可控、可复现、贴近真实应用场景的测试环境。我的核心思路是构建一个“沙盒”Sandbox在这个沙盒里Agent的能力被分解为多个可观测、可度量的子任务就像一套精心设计的体检项目全面检查模型的“健康状况”。2.1 为什么是“沙盒”而不是“基准测试”传统的NLP基准测试如GLUE、SuperCLUE存在几个关键局限使其无法有效评估Agent能力静态与封闭性任务和答案是固定的无法评估模型的动态规划和交互能力。缺乏工具使用场景大多数测试不涉及调用外部API、查询数据库、操作文件等关键Agent技能。忽略长程推理与状态保持Agent任务往往是多轮的需要模型记住历史、管理任务状态而单轮测试无法体现这一点。因此我们的沙盒必须包含以下要素可编程的环境能够模拟一个微型的“世界”比如一个简单的文件系统、一个计算器API、一个知识库查询接口。清晰的任务规约用自然语言描述一个目标但这个目标的达成需要多个步骤。严格的观察与执行分离Agent必须通过“思考-行动-观察”的循环来推进任务不能直接“跳步”得到答案。多维度的评估指标不仅仅是最终任务的成功/失败还要记录步骤合理性、工具调用准确性、效率步数/时间、以及失败时的错误类型。2.2 沙盒架构设计一个最小化的评估沙盒可以由三部分组成任务服务器Task Server负责任务的定义、分发、状态管理和最终评判。它向Agent描述任务并接收Agent的动作。模拟环境Simulated Environment根据任务需要虚拟出一组工具或一个场景。例如一个“线上购物”环境可能包含search_product(product_name),get_price(product_id),add_to_cart(product_id)等工具函数。Agent运行器Agent Runner这是核心它加载待测试的大语言模型LLM并按照一定的框架如ReAct、Plan-and-Execute来运行。运行器负责将环境反馈和任务历史组织成提示词Prompt喂给LLM解析LLM的输出得到下一步动作Action然后提交给环境执行。[Agent Runner (LLM)] --思考/规划-- [任务历史 环境观察] | | (解析出 Action) v [任务服务器 / 模拟环境] --执行Action返回结果--在这个架构下LLM只是一个“决策大脑”它的能力边界会在与环境的反复交互中暴露无遗。一个只会夸夸其谈的模型可能连第一步该调用哪个工具都判断错误。注意这里不推荐初学者一上来就尝试用特别复杂的框架如AutoGen、Camel。从最简单的while循环开始实现一个ReAct模式的核心逻辑更能让你理解Agent运作的每一个细节也更容易进行调试和定制。3. 实战构建从零搭建你的第一个Agent测试沙盒理论说再多不如动手做一遍。下面我将以一个“本地文件信息查询与处理”的沙盒为例展示完整的构建过程。这个任务看似简单却涵盖了信息检索、工具调用、条件判断和多步规划。3.1 环境与工具准备首先你需要一个Python环境3.8以上和几个核心库。我们选择OpenAI兼容的API方式这样可以方便地切换不同的模型后端如OpenAI GPT、DeepSeek、国内各大平台的模型。# 创建虚拟环境可选但推荐 python -m venv agent_sandbox source agent_sandbox/bin/activate # Linux/Mac # agent_sandbox\Scripts\activate # Windows # 安装核心依赖 pip install openai requests python-dotenv接下来准备你的模型API。这里以使用DeepSeek API为例因其最近讨论热度高但请注意你需要自行注册并获取API Key。创建一个.env文件来管理密钥# .env 文件 DEEPSEEK_API_KEYyour_deepseek_api_key_here DEEPSEEK_API_BASEhttps://api.deepseek.com MODEL_NAMEdeepseek-chat然后我们编写一个简单的LLM客户端类用于统一对话接口# llm_client.py import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() class LLMClient: def __init__(self): self.client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_API_BASE) ) self.model os.getenv(MODEL_NAME, deepseek-chat) def chat_completion(self, messages, temperature0.1): 发起对话请求temperature调低使输出更稳定 try: response self.client.chat.completions.create( modelself.model, messagesmessages, temperaturetemperature, streamFalse ) return response.choices[0].message.content except Exception as e: print(fAPI调用错误: {e}) return None3.2 设计模拟环境与工具我们的沙盒环境模拟一个本地目录里面有若干文本文件。Agent可以调用工具来与之交互。# environment.py import os import re class FileSystemEnv: def __init__(self, workspace./sandbox_workspace): self.workspace workspace os.makedirs(workspace, exist_okTrue) # 初始化一些测试文件 self._init_test_files() def _init_test_files(self): test_files { report_2024_q1.txt: 第一季度销售额为150万元。主要产品A销量增长20%。\n存在的问题供应链延迟。, meeting_notes_0401.md: ## 项目启动会\n**日期**: 2024-04-01\n**参会人**: 张三李四王五\n**决议**: 1. 采用新框架。2. 下周进行技术评审。, todo_list.json: {tasks: [{id: 1, title: 完成Agent测试沙盒, status: pending}, {id: 2, title: 编写项目文档, status: done}]}, config.yaml: project_name: agent_sandbox\nversion: 1.0\nlog_level: info } for filename, content in test_files.items(): path os.path.join(self.workspace, filename) with open(path, w, encodingutf-8) as f: f.write(content) # 定义Agent可用的工具 def list_files(self): 列出工作空间中的所有文件 files os.listdir(self.workspace) return {status: success, files: files} def read_file(self, filename): 读取指定文件的内容 path os.path.join(self.workspace, filename) if not os.path.exists(path): return {status: error, message: f文件 {filename} 不存在。} try: with open(path, r, encodingutf-8) as f: content f.read() return {status: success, content: content} except Exception as e: return {status: error, message: str(e)} def search_in_files(self, keyword): 在所有文本文件中搜索关键词 results {} for filename in os.listdir(self.workspace): if filename.endswith(.txt) or filename.endswith(.md): path os.path.join(self.workspace, filename) with open(path, r, encodingutf-8) as f: content f.read() if keyword.lower() in content.lower(): # 简单返回匹配行 lines [line for line in content.split(\n) if keyword.lower() in line.lower()] results[filename] lines[:3] # 只取前三行 return {status: success, results: results} def calculate(self, expression): 一个简单的计算器工具安全评估 # 非常重要在实际应用中这里必须做严格的安全过滤防止代码注入。 # 此处仅为演示进行极简的过滤。 allowed_chars set(0123456789-*/(). ) if not all(c in allowed_chars for c in expression): return {status: error, message: 表达式包含非法字符} try: # 警告eval非常危险此处仅用于封闭沙盒演示。 # 生产环境必须替换为更安全的计算方式如ast.literal_eval或自己写parser。 result eval(expression, {__builtins__: {}}, {}) return {status: success, result: result} except Exception as e: return {status: error, message: f计算错误: {e}}实操心得工具设计的安全性上面calculate工具使用了eval这是为了演示方便但在任何面向真实用户或网络的Agent中这都是极度危险的行为会导致严重的代码注入漏洞。一个安全的做法是1. 使用ast.literal_eval只评估字面量2. 或自己实现一个简单的四则运算解析器3. 或直接调用一个受信任的外部计算API。工具的设计是Agent安全的第一道防线必须慎之又慎。3.3 实现Agent运行器ReAct模式我们将实现一个经典的ReActReasoning Acting循环。Agent的每一步输出都应该是“思考Thought”“行动Action”的格式。# agent_runner.py from llm_client import LLMClient from environment import FileSystemEnv import json import re class ReActAgentRunner: def __init__(self, llm_client, environment): self.llm llm_client self.env environment # 定义工具列表供LLM知晓 self.tools [ {name: list_files, description: 列出工作区中的所有文件。无参数。}, {name: read_file, description: 读取一个文件的内容。参数: filename (字符串)。}, {name: search_in_files, description: 在所有文本文件中搜索关键词。参数: keyword (字符串)。}, {name: calculate, description: 执行一个简单的数学计算。参数: expression (字符串如23*4)。}, ] self.max_steps 10 self.history [] # 记录交互历史用于构造prompt def _build_system_prompt(self): 构建系统提示词定义角色、工具和输出格式 tools_desc \n.join([f- {t[name]}: {t[description]} for t in self.tools]) return f你是一个运行在沙盒环境中的智能助手。你的目标是通过使用工具完成用户的任务。 你可以使用的工具如下 {tools_desc} 你必须严格按照以下格式输出 Thought: 这里是你对当前情况的分析和下一步计划。 Action: 这里是你决定要调用的工具名称和参数必须是有效的JSON格式例如 {{name: tool_name, args: {{arg1: value1}}}}。 在你得到工具返回的观察结果Observation后继续分析直到任务完成或无法继续。 当你认为任务已经完成时在Thought中总结并在Final Answer中给出最终答案。 格式 Thought: 任务完成。 Final Answer: 这里是给用户的最终答案。 def parse_llm_output(self, text): 解析LLM的输出提取Thought和Action thought_match re.search(rThought:\s*(.*?)(?\nAction:|\nFinal Answer:|$), text, re.DOTALL) action_match re.search(rAction:\s*(\{.*?\}), text, re.DOTALL) final_answer_match re.search(rFinal Answer:\s*(.*), text, re.DOTALL) thought thought_match.group(1).strip() if thought_match else action_json action_match.group(1).strip() if action_match else None final_answer final_answer_match.group(1).strip() if final_answer_match else None action None if action_json: try: action json.loads(action_json) except json.JSONDecodeError: print(f警告无法解析Action JSON: {action_json}) return thought, action, final_answer def run(self, user_query): 执行一个任务 system_prompt self._build_system_prompt() messages [{role: system, content: system_prompt}] self.history [{role: user, content: user_query}] messages.extend(self.history) for step in range(self.max_steps): print(f\n 步骤 {step 1} ) # 调用LLM llm_response self.llm.chat_completion(messages) if not llm_response: return {status: error, message: LLM调用失败} print(fLLM原始输出:\n{llm_response}) # 解析输出 thought, action, final_answer self.parse_llm_output(llm_response) print(f解析结果 - Thought: {thought}) if action: print(f - Action: {action}) if final_answer: print(f - Final Answer: {final_answer}) # 记录LLM的回复到历史 self.history.append({role: assistant, content: llm_response}) # 判断是否结束 if final_answer is not None: print(f\n任务完成最终答案: {final_answer}) return {status: success, answer: final_answer, steps: step 1} # 执行动作 if action and isinstance(action, dict) and name in action: tool_name action[name] tool_args action.get(args, {}) # 检查工具是否存在 if not hasattr(self.env, tool_name): obs f错误工具 {tool_name} 不存在。 else: tool_func getattr(self.env, tool_name) try: # 调用工具 obs tool_func(**tool_args) except TypeError as e: obs f错误调用工具参数不正确。{e} except Exception as e: obs f错误工具执行异常。{e} else: obs 错误未解析出有效的Action格式。 print(f工具执行观察: {obs}) # 将观察结果作为“用户”消息加入历史开启下一轮 obs_str json.dumps(obs, ensure_asciiFalse) self.history.append({role: user, content: fObservation: {obs_str}}) messages.extend([{role: assistant, content: llm_response}, {role: user, content: fObservation: {obs_str}}]) print(f\n达到最大步数 ({self.max_steps})任务未完成。) return {status: max_steps_reached, history: self.history}3.4 运行你的第一个Agent任务现在让我们把所有的部分组装起来跑一个任务看看。# main.py from llm_client import LLMClient from environment import FileSystemEnv from agent_runner import ReActAgentRunner def main(): # 1. 初始化 llm_client LLMClient() env FileSystemEnv() agent ReActAgentRunner(llm_client, env) # 2. 定义一个测试任务 # 任务找出第一季度销售额并计算如果增长10%是多少。 task 请先找到第一季度销售额的数据然后计算如果这个销售额增长10%会是多少钱 print(f开始执行任务: {task}) result agent.run(task) # 3. 输出结果摘要 print(f\n 任务执行摘要 ) print(f状态: {result[status]}) if result[status] success: print(f步骤数: {result.get(steps, N/A)}) print(f最终答案: {result.get(answer, N/A)}) if __name__ __main__: main()运行这个脚本你会看到Agent一步步地思考、调用工具、观察结果。一个能力足够的模型其执行轨迹可能如下Thought: 用户需要第一季度销售额。我应该先看看有哪些文件。Action:{name: list_files, args: {}}Observation: 列出report_2024_q1.txt等文件。Thought: 有一个report_2024_q1.txt文件很可能包含第一季度数据。我需要读取它。Action:{name: read_file, args: {filename: report_2024_q1.txt}}Observation: 文件内容显示“第一季度销售额为150万元”。Thought: 我找到了销售额150万元。现在需要计算增长10%。这需要计算工具。Action:{name: calculate, args: {expression: 150 * 1.1}}Observation: 计算结果为165.0。Thought: 计算完成。150万元增长10%是165万元。可以给出最终答案了。Final Answer: 第一季度销售额为150万元增长10%后是165万元。这个过程清晰展示了Agent的规划、工具选择、信息提取和计算能力。如果模型在任何一个环节出错比如找不到文件、错误解析数字、调用错误的工具我们都能立刻发现。4. 设计多层次任务全面评估Agent能力单一的测试任务不足以反映模型的全貌。我们需要一套“测试用例”从不同维度考察Agent。以下是我设计的四类核心测试任务难度和侧重点递增。4.1 基础工具调用与信息检索这类任务检验模型是否理解工具描述并正确调用。任务1简单“请列出工作区里所有的文件。”预期动作调用list_files。考察点能否理解最简单的指令并匹配到无参数工具。任务2中等“meeting_notes_0401.md这个文件里提到了哪些人”预期动作先调用read_file读取文件然后从文本中提取人名张三李四王五。注意这里模型需要具备基础的文本信息提取能力这属于其内在的NLP能力而非工具调用。考察点串联工具调用与内在信息处理能力。任务3陷阱“计算一下2加上3再乘以4。”预期动作调用calculate参数应为23*4。一个常见的错误是输出(23)*4这暴露了模型对数学表达式优先级可能存在的理解偏差或者未能将自然语言精确转换为表达式。考察点自然语言到工具参数的精确转换能力。4.2 多步骤规划与状态跟踪这类任务要求模型能分解任务并记住中间状态。任务4“todo_list.json里还有多少个未完成的任务请把数量告诉我。”预期路径调用read_file读取todo_list.json。解析JSON内容。统计status为pending的项目数量。直接回答或调用calculate确认数量。考察点多步规划、数据结构理解JSON解析、条件过滤。任务5我们之前的例子“找出第一季度销售额并计算增长10%后的值。”考察点跨文件信息检索、数值提取、数学计算的多步骤规划与执行。4.3 条件判断与动态决策这类任务需要模型根据环境反馈做出不同的决策。任务6“请帮我找一个包含‘供应链’关键词的文件并告诉我它是什么文件。”预期路径调用search_in_files搜索“供应链”。观察结果发现report_2024_q1.txt包含该词。可选调用read_file确认一下内容。回答文件是report_2024_q1.txt。考察点根据工具返回结果可能为空可能有多条进行判断和后续操作。任务7“如果存在一个叫‘budget.xlsx’的文件请告诉我它的内容如果不存在就告诉我‘文件未找到’。”考察点这是关键测试模型需要在规划中体现出“如果-那么”的逻辑。它应该先尝试list_files或直接尝试read_file然后根据Observation中的错误信息决定最终的输出。能力弱的模型可能会在read_file失败后陷入困惑或死循环。4.4 复杂推理与工具组合这是最高难度的测试需要模型进行更深度的推理和工具创新性使用。任务8“我们第一季度销售额是150万主要产品销量增长20%。请估算一下主要产品A去年的季度平均销售额是多少提示需要从增长比例反推”考察点这需要模型进行数学逆运算。它需要从文本中提取“150万”和“增长20%”然后理解“当前过去*(1增长率)”从而推导出“过去当前/(1增长率)”最后调用calculate计算150/1.2。这考验了模型的数学推理和将自然语言问题转化为数学公式的能力。任务9“请总结一下report_2024_q1.txt和meeting_notes_0401.md这两个文件中提到的下一步行动计划。”考察点需要模型分别读取两个文件理解不同格式纯文本和Markdown的内容提取出“行动计划”或“决议”这类关键信息并进行归纳总结。这考验了多文档理解、信息整合和摘要生成能力。通过让同一个模型跑完这一套任务并记录其成功率、平均步数、以及典型的失败模式你就能得到一份远超任何宣传文案的、实实在在的“Agent能力体检报告”。5. 结果分析与模型对比直面“翻车”现场当你用上述沙盒测试不同的模型时你会发现巨大的差异。以下是我用不同级别模型测试时遇到的一些真实情况模型名称以类型代替5.1 典型问题分类问题类型具体表现可能原因常见于工具调用格式错误输出的Action不是JSON或JSON格式错误键名不对。指令遵循Instruction Following能力弱或提示词Prompt设计不佳。部分中小规模开源模型工具选择错误该用read_file时用了search_in_files或试图用calculate处理文本问题。对工具功能的理解偏差或任务分解能力不足。逻辑推理能力较弱的模型参数提取错误从Thought到Action的参数转换出错。如把“销售额150万元”中的“150万元”直接作为参数未去除单位“万元”。细节感知和精确抽取能力不足。普遍问题精度要求高的任务中明显状态遗忘与逻辑断裂在多步任务中执行完一步后下一步的Thought不再提及上一步的关键结果或做出矛盾决策。长上下文理解与状态管理能力弱。上下文窗口管理不佳或注意力机制有缺陷的模型无法处理分支逻辑对于任务7条件判断模型无法根据“文件不存在”的观察结果切换到备选路径而是不断重试或“卡住”。复杂规划和动态决策能力差。多数模型在此类任务上表现不佳数学与逻辑推理错误在任务8中无法正确推导出反推公式或计算错误。数学推理能力是许多大模型的固有短板。非专门针对数学训练的模型5.2 从“翻车”中我们能学到什么提示词工程不是万能的你可以通过精妙的Prompt让模型在单轮对话中表现更好但在多轮、有状态的Agent任务中模型底层的规划、推理和指令遵循能力是决定性的。Prompt可以引导但无法创造模型中不存在的能力。上下文窗口不是越大越好虽然长上下文是基础但更重要的是模型如何利用上下文。有些模型即使有128K的窗口也会在几步之后“忘记”最初的目标。评估时需关注其“有效上下文长度”。“聪明”不等于“可靠”一个模型可能在创意写作上惊艳你但在需要严格逻辑和精确性的Agent任务中频频出错。选择模型必须任务匹配。开源模型的差距与机会测试中发现一些优秀的开源模型如Qwen2.5系列、DeepSeek最新版本在基础工具调用和简单规划上已经非常接近顶级闭源模型成本却低得多。但在复杂推理和容错处理上仍有可见差距。这为开源社区指明了优化方向。5.3 建立你的评估矩阵建议为每个测试的模型建立如下表格模型名称任务1任务2任务3任务4任务5任务6任务7任务8任务9综合成功率平均步数主要失败模式模型A✅✅✅✅✅✅❌❌⚠️67%3.2复杂推理、条件判断模型B✅✅❌✅⚠️✅❌❌❌44%4.1数学、参数提取、规划模型C✅✅✅✅✅✅⚠️✅✅89%2.8偶尔状态遗忘通过这样的矩阵你可以非常直观地看到哪个模型最全面综合成功率高。哪个模型最高效平均步数少说明规划更精准。每个模型的致命伤是什么针对性地看失败模式决定它是否适合你的特定场景。6. 避坑指南与进阶优化在搭建和运行测试沙盒的过程中我踩过不少坑也总结出一些让测试更稳定、评估更准确的经验。6.1 提示词Prompt设计的核心技巧格式强制至关重要在系统提示词中必须用极其清晰、不容置疑的语言规定输出格式如Thought:...\nAction:...。可以使用类似“你必须严格遵守以下格式”、“你的输出必须且只能包含以下两部分”这样的强调语句。对于不听话的模型可以在每个user消息里都重申格式要求。提供少量示例Few-Shot在系统提示词中加入1-2个完整的Thought - Action - Observation - Final Answer示例能极大提升模型输出的规范性。这对于开源模型尤其有效。工具描述要具体且无歧义描述工具时明确说明输入参数的类型和格式如filename (字符串)并举例说明如例如 {name: read_file, args: {filename: report.txt}}。温度Temperature设置在测试评估时务必设置较低的temperature如0.1或0以降低输出的随机性确保测试结果可复现。在创意性任务中再调高它。6.2 提升沙盒的稳定性和可扩展性超时与重试机制在Agent Runner中增加每一步的超时控制以及对于网络错误或API限流的重试逻辑。更健壮的输出解析之前的parse_llm_output函数使用正则表达式比较脆弱。可以升级为1. 先尝试用json.loads解析整个输出2. 如果失败再用更灵活的正则或基于关键词的搜索3. 甚至可以调用一个“小模型”或规则引擎来辅助解析。引入验证器Validator在工具被调用前对参数进行预验证。例如在调用read_file前检查filename是否包含路径遍历字符如../。支持更复杂的工具逐步添加如write_file写文件、execute_sql查询数据库、call_web_api调用网络API等工具让测试环境更贴近真实应用。可视化与日志将每一步的Thought、Action、Observation记录到文件或数据库中并可以生成可视化的执行流程图便于分析和调试。6.3 针对特定场景的定制化评估领域特定任务如果你的Agent用于客服就模拟一个问答和工单查询环境如果用于数据分析就提供pandasDataFrame操作工具。让测试环境与你的真实业务对齐。压力与压力测试设计一些需要调用多个工具、步骤冗长接近最大步数的任务测试模型在长序列任务中的表现是否稳定。对抗性测试故意给出模糊、矛盾或有误导性的指令观察模型的鲁棒性。例如“删除那个重要的文件”而工具中没有删除功能看模型是会拒绝还是尝试危险操作。构建并运行这样一个Agent测试沙盒最大的收获不是得到一个模型的排名而是获得了一种批判性评估AI能力的思维方式。你不再会被“万亿参数”、“榜单第一”这样的宣传语轻易说服而是会习惯性地问“它在我的任务场景下真的能行吗” 这种从“模型消费者”到“能力评估者”的转变才是这个项目带给你的最核心价值。下次再听到某个新模型发布时你大可以微微一笑打开你的沙盒跑几个任务看看——真相永远在代码执行的那一刻浮现。