ARTICLE DETAIL

资讯详情

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

终端智能体评测:脚手架比模型更影响分数

终端智能体评测:脚手架比模型更影响分数 真正拉开终端智能体评测分数的可能不是模型而是脚手架。如果你关注过 Agent 评测一定经常看到这类讨论换了一个更强的大模型为什么任务完成率还是上不去也有人在跑 SWE-bench、Terminal-Bench 这类评测时发现同一个模型在不同执行框架下的分数差异甚至超过不同模型之间的差异。这不是偶然。终端智能体评测的对象从来不只是模型而是一整套模型 脚手架系统。脚手架决定了模型能不能稳定地观察环境、执行命令、消化报错、调整策略。评测分数是两者共同作用的结果而在模型能力达到基础线之后脚手架往往是更主要的分数杠杆。这篇文章会从终端智能体评测的底层逻辑讲起然后给出一套可以在本机复现的最小评测方案帮你把模型因素和脚手架因素分开观察最后整理常见丢分点和工程建议。读完你会明白当你的终端智能体评测分数不理想时第一个该改的可能不是模型而是脚手架。1. 为什么终端智能体评测值得关注1.1 终端智能体正在从聊天走向干活过去一年AI 编程助手和智能体的边界在快速变化。聊天助手负责回答问题而终端智能体负责真正把活干完安装依赖、修改配置、执行测试、定位线上故障。相比生成一段代码然后丢给你终端智能体会把代码放进真实环境里跑并根据结果继续调整。这个变化带来了新的产品形态也带来了新的评测难题。普通问答有参考答案终端任务没有标准答案只有完成与未完成以及用了多少步和过程中是否安全。终端智能体的典型应用场景包括自动化运维根据告警信息定位并修复常见故障。开发流水线自动处理依赖冲突、测试失败、构建报错。数据工程在服务器上完成日志采集、清洗、统计分析。个人生产力用自然语言操作本机环境完成任务型操作。在这些场景里用户希望智能体像一名有经验的工程师那样面对未知环境时能够一步步探测、试错、收敛而不是背出几句正确的 shell 命令。这正是终端智能体评测要比传统模型评测复杂得多的原因。1.2 评测是工程改进的前提没有评测就没有改进。把终端智能体当作黑盒来用遇到问题就只能靠感觉猜。评测的意义不是给团队一个可以炫耀的分数而是把模型能力、脚手架设计、任务难度三个变量拆开找到真正的瓶颈。这也是为什么现在越来越多团队开始关注 agent 评测、运行 swe-bench 评测、自己构造领域任务集。评测跑不通后面所有优化都缺少锚点。如果你所在的团队正在做终端智能体、AI 编程助手或运维机器人评测体系迟早会成为你最依赖的基建之一。2. 基础概念模型、脚手架、评测集各指什么2.1 模型决策的大脑这里的模型指大语言模型本身。模型的参数量、训练数据、推理能力、指令跟随能力共同决定了单轮决策的合理程度。在终端智能体场景里模型的职责包括理解任务描述拆解目标根据当前状态决定下一步命令根据终端输出判断成功或失败在失败时调整策略。模型能力是重要的但只是系统的一部分。评测模型单轮表现是一回事评测一个完整终端智能体是另一回事。很多人在评测时只关注模型名称和部署方式却忽略了模型只是被脚手架包装起来的一个组件。2.2 脚手架系统工程里的躯干脚手架是连接模型与目标环境的工程层。它在模型和终端之间起着翻译、执行、反馈、约束的作用。很多人第一次听到脚手架这个词可能会觉得它只是提示词模板实际上远不止如此。脚手架至少包含以下模块模块功能如果缺失会发生什么工具协议定义模型输出如何变成命令模型输出无法解析任务直接中断动作空间命令白名单与工具集模型随意执行危险命令环境被破坏上下文管理压缩历史、截断输出上下文爆炸模型遗忘原始目标任务跟踪记录已完成和待办模型重复执行同一操作错误恢复失败后重试或更换策略模型陷入错误循环安全沙箱限制影响范围一次事故导致整个评测失败从工程角度看脚手架就是 Agent 的执行框架。它决定了模型的每一次决策能不能被正确处理、能不能在真实环境里落地。同样一个模型放在不同的脚手架上最终表现可以差别很大。2.3 评测集与评测指标评测集是指一组带检查脚本的任务典型的有 SWE-bench、Terminal-Bench。评测指标则包括完成率、步骤数、超时率、安全违规次数等。这里要注意一个误区不是所有跑分都适合直接用来调优。公开测试集适合做横向对比但不同测试集的任务难度、环境假设、检查方式差异很大。团队内部最好也维护一套贴近自身业务的私有任务集与公开集互补否则很容易被某一个测试集的偏向性带偏方向。3. 脚手架为什么比模型更影响分数3.1 终端任务的反馈循环特征终端智能体完成任务的过程可以抽象为一个循环模型 → 输出命令 → 脚手架解析 → 执行 → 收集输出 → 反馈给模型 → 下一轮一个任务可能需要循环 10 到 30 次。只要某一轮出现问题后续就可能连锁失败。例如模型输出一条合法命令但工具协议没有解析成功命令执行超时整个任务被挂起一条命令输出 10 万字符模型上下文被冲垮模型因为上一条命令失败而连续输出相同命令。这些问题都不是模型不懂任务而是脚手架没有把能力传递到真实环境。大量评测失败实际上是这一类问题。换句话说模型的能力再强如果脚手架不能稳定地接收指令、执行命令、反馈结果评测分数也不可能高。3.2 同一个模型不同脚手架分数差异可能很大这里做一个直观对比。假设模型相同脚手架 A 只有最基础的工具调用脚手架 B 增加了输出截断、超时中断、失败提示、命令白名单。同样跑一组终端任务可能出现的结果是脚手架 A 的完成率偏低部分任务中途卡死平均步数偏高脚手架 B 的完成率更稳定危险操作次数下降错误恢复更及时。需要说明的是这不是某个固定产品的实测数据而是从常见评测经验中总结的典型趋势。真正的意义在于当你在同一模型上修改脚手架任务完成率可能会有明显提升这正是脚手架比模型更影响分数的微观来源。3.3 一个类比司机和赛车的底盘可以把模型比作司机脚手架比作赛车。司机的驾驶技术决定单圈潜力但赛车的转向精度、刹车响应、仪表可视化、辅助系统决定了一圈内实际能发挥出多少。在终端智能体评测中环境是一条复杂赛道安装依赖可能遇到版本冲突启动服务可能遇到端口占用修复代码可能遇到测试环境不一致。没有好的底盘模型驾驶员再强也会在弯道里打转。这个类比能解释很多看似奇怪的现象同一个模型换一个执行框架效果天差地别。3.4 为什么这个结论容易被人忽略最直接的原因是可感知性不同。换一个模型人机交互的质感立即不一样但是改动脚手架例如在 prompt 里增加错误恢复提示、在解析层做容错效果需要跑完整评测才能看到。另外很多评测方案只输出最终分数不统计中间过程导致脚手架问题被掩盖。这也是我建议做过程日志设计的原因。只有把每一步命令、退出码、模型回复都记录下来才能判断分数损失到底发生在模型决策环节还是工具执行环节。4. 环境准备与评测方案设计4.1 前置条件推荐准备一个 Linux 环境装有 Python 3.10、Docker、curl。macOS 也可以但最终效果取决于命令兼容性。如果不方便使用 Docker至少要在虚拟机里做评测不建议直接在开发机上跑不受限的终端智能体。依赖方面只需要一个能调用模型的 Python SDK 或 HTTP 客户端。无论你用的是在线模型 API 还是本地部署的开源模型评测循环本身并不关心模型部署在哪一层。4.2 任务集设计为了验证脚手架比模型更影响分数建议先构造一组覆盖多个技能点的小任务。我建议任务集包含以下类型编号任务要点完成判定task_001安装依赖并启动 HTTP 服务curl 返回 200task_002在日志中定位 ERROR 关键字并提取时间check.sh 校验输出task_003修复 Python 语法错误使测试通过pytest 退出码为 0task_004编写脚本批量重命名文件文件命名结果正确task_005查找占用 8080 端口的进程并停止curl 连接失败每个任务对应一个目录里面包含任务描述和检查脚本。这样设计的好处是既有正向操作搭建、启动也有反向操作排查、修复能比较全面地暴露智能体在多轮交互中的弱点。4.3 评测指标建议统计以下指标任务完成率最终通过 check.sh 的任务比例平均执行步数完成任务需要的命令数错误恢复率一次命令失败后能在几步内调整策略的比例超时任务数因单命令或总时长超时导致失败的任务危险命令尝试次数是否尝试过 rm -rf、格式化等高风险命令。这些指标里任务完成率适合对外汇报但错误恢复率和超时率才是诊断脚手架问题的关键。如果只统计完成率你很难分辨一个任务失败是因为模型不理解还是因为命令解析出错导致模型多次尝试后放弃。5. 完整示例搭建一个最小终端智能体评测脚手架这一章给出一个可以跑通的评测循环框架。为了不绑死某个模型厂商模型调用部分使用假设 OpenAI 兼容接口的写法你可以在实际使用时替换为任意模型服务。5.1 评测配置创建eval_config.json{ model: your-model-name, max_steps: 30, timeout_seconds: 60, allowed_commands: [ python, pip, ls, cat, curl, grep, sed, pwd ], blocked_commands: [ rm -rf, mkfs, shutdown, reboot ] }配置里有两个值得注意的点allowed_commands是命令白名单防止模型尝试危险操作blocked_commands是黑名单即使白名单没有覆盖也强制拦截。实际项目中白名单需要结合任务类型调整。如果任务要求编写脚本可能还需要允许chmod、tee等命令。安全边界宁可严格一些也不要为了评测通过率放开对危险命令的限制。5.2 任务描述与检查脚本创建tasks/task_001/task.md安装 requirements.txt 中的依赖并启动一个 HTTP 服务。 服务需要监听 8080 端口健康检查路径为 /health。 最终通过 curl -sf http://localhost:8080/health 能返回 200。创建tasks/task_001/check.sh#!/bin/bash # 检查 8080 端口健康检查是否通过 if curl -sf http://localhost:8080/health /dev/null 21; then echo PASS exit 0 else echo FAIL exit 1 fi这里的检查脚本只判断最终状态。更严格的评测可以加入过程检查例如检查是否修改过某个文件、是否执行过指定命令但最小方案不需要一次性做完。5.3 评测循环主程序创建run_eval.pyimport json import os import subprocess def load_task(task_dir: str) - str: with open(os.path.join(task_dir, task.md), r, encodingutf-8) as f: return f.read() def check_task(task_dir: str) - bool: check_script os.path.join(task_dir, check.sh) result subprocess.run([bash, check_script], capture_outputTrue, textTrue) return PASS in result.stdout def execute_command(cmd: str, workdir: str, timeout: int) - dict: try: result subprocess.run( cmd, shellTrue, cwdworkdir, capture_outputTrue, textTrue, timeouttimeout, ) return { exit_code: result.returncode, output: (result.stdout result.stderr)[-2000:], } except subprocess.TimeoutExpired: return {exit_code: -1, output: timeout} def build_prompt(task_desc: str, history: list) - str: system_msg ( 你是一个终端智能体。请根据任务描述输出一条 bash 命令。 每轮只输出一条命令不要解释。 ) user_msg f任务\n{task_desc}\n\n历史执行记录\n for item in history[-10:]: user_msg ( f命令: {item[cmd]}\n f退出码: {item[exit_code]}\n f输出:\n{item[output]}\n ) user_msg \n请输出下一条命令 return system_msg \n\n user_msg def parse_model_reply(reply: str) - str: lines [line.strip() for line in reply.splitlines() if line.strip()] for line in lines: if not line.startswith(): return line return def call_model(model_name: str, prompt: str) - str: # 这里替换成你的模型服务调用 # 假设服务提供 OpenAI 兼容接口 from openai import OpenAI client OpenAI() response client.chat.completions.create( modelmodel_name, messages[ {role: system, content: 你是一个终端智能体请严格输出一条 bash 命令。}, {role: user, content: prompt}, ], temperature0.2, ) return response.choices[0].message.content def run_eval(task_dir: str, model_name: str, config: dict) - dict: task_desc load_task(task_dir) history [] max_steps config.get(max_steps, 30) timeout config.get(timeout_seconds, 60) for step in range(1, max_steps 1): if check_task(task_dir): return {status: PASS, steps: step, history: history} prompt build_prompt(task_desc, history) reply call_model(model_name, prompt) cmd parse_model_reply(reply) if not cmd: continue result execute_command(cmd, task_dir, timeout) history.append({ step: step, cmd: cmd, exit_code: result[exit
返回列表