YAOTU INSIGHTS

Agent模型切换实操:从Sonnet到DeepSeek v4 Flash评估指南

Agent模型切换实操:从Sonnet到DeepSeek v4 Flash评估指南
之前做 Agent 项目时我一直在用 sonnet 作为默认模型整体效果稳定但成本与延迟在业务量上去之后越来越让人头疼。后来团队决定评估切换到 deepseek v4 flash本以为只是换一个 model 参数的事结果一跑测试工具调用格式、上下文长度、参数行为全都不太一样甚至在部分用例里直接出现 agent execution terminated due to error。为了把这次验证过程沉淀下来我整理了一份精简的评估与切换实操笔记也就是本文的主要内容。这篇文章不是一份简单的“模型对比评测报告”而是一套你自己也能复用的 Agent 评估方案包括评估维度设计、测试用例组织、评估脚本实现、切换前后的对比方法以及 deepseek v4 flash 参数设置建议和常见报错排查。无论你是在做 AI Agent 开发还是准备把项目从其他模型迁移到国产模型都可以照着这套思路落地。1. 为什么要做 Agent 评估与模型切换验证1.1 Agent 项目中的模型选型困境很多同学在 Agent 项目初期选模型的标准非常简单哪个模型在几个“感觉很难”的问题上答得好就用哪个。但真正进入开发阶段后会发现Agent 和普通聊天应用完全不是一回事。普通聊天只要求模型生成一段文字而 Agent 要求模型在每一步做出正确决策——决定调用哪个工具、传递什么参数、如何根据工具返回结果调整下一步计划最后还要给出符合用户预期的最终答案。任何一个环节出错整个任务就失败。这意味着模型的能力差异会被 Agent 框架放大。sonnet 在单轮对话里表现好不代表它在多次工具调用组成的复杂任务里表现也好deepseek v4 flash 可能在常规问答上很流畅但到了结构化工具调用场景可能出现参数格式错误、调用次数失控、中途自行终结等问题。所以在做模型切换时不能靠“问几个问题 肉眼判断”来决定而必须建立一套可量化的 Agent 评估流程。这也是本文开篇要先讲评估体系的原因。1.2 为什么从 sonnet 切到 deepseek v4 flash我们这次切换的背景是业务侧对两个指标提出了更高要求一个是单次任务的平均成本另一个是端到端延迟。sonnet 系列模型在复杂 Agent 任务上确实稳定但它的成本和延迟在大规模调用场景下不够理想。deepseek v4 flash 被纳入候选主要有几个原因它的 API 在行业里支持 OpenAI 兼容协议接入成本低输出速度更适合高并发生产环境中文场景下的指令跟随能力也比较稳。当然具体是否真的适合你的业务需要通过评估来验证而不是看宣传参数。需要强调的是本文讨论的不是“谁比谁强”的问题而是“在你的 Agent 任务集上谁更符合你的验收标准”。同一套评估脚本你可以用来对比 sonnet、deepseek v4 flash甚至可以把 glm-5.3-flash 等模型一起加进对比池。评估的意义不在于给模型排名而在于给你自己的项目提供可重复的决策依据。1.3 Agent 评估到底评估什么Agent 评估和传统模型评测最大的区别在于不仅要看最终答案对不对还要看“过程质量”。同一个任务模型 A 用了 3 轮工具调用完成模型 B 用了 10 轮还绕了弯路即使最终结果一样B 的质量也是更差的。我习惯把 Agent 评估拆成几个层面任务成功率最终结果是否满足用户意图。工具调用准确率调用的工具是否正确参数是否完整且类型正确。任务路径效率完成一个任务需要多少轮 LLM 请求。稳定性同一个任务多次运行结果是否一致。资源消耗单任务 token 消耗与耗时。错误恢复能力工具调用失败后Agent 能否纠正并继续。这里有一个容易混淆的概念Agent 本体和 Agent 外面的调度、评测、安全检查框架。很多项目把 Agent 等同于“大模型 提示词”其实真正决定线上表现的是模型外加一层工程结构——你可以叫它 harness也可以叫 Agent 框架。评估时要把这层结构固定下来只切换底层模型才能比较出模型本身的行为差异。2. 环境准备与项目目录设计2.1 运行环境与依赖安装本文的评估脚本使用 Python 编写建议使用 Python 3.10 以上版本。依赖方面不引入额外的 Agent 框架直接用最朴素的方式实现一个“模型循环 工具调用”的 Runner这样你能更清楚地看到模型行为差异。需要安装的包主要是 openai 和 anthropic。如果你的项目只需要调用 deepseek v4 flash只用 openai 包就够了因为 DeepSeek 的 API 是 OpenAI 兼容格式。这里建议把两个 SDK 都装上方便在同一套评估代码里并排对比两个模型。pip install openai anthropic python-dotenv环境变量方面建议把所有敏感配置放在项目根目录的 .env 文件里不要硬编码在代码中。DEEPSEEK_API_KEY你的deepseek_key ANTHROPIC_API_KEY你的anthropic_key版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示评估思路。2.2 项目目录结构评估项目我建议单独建一个仓库或目录不要和线上 Agent 服务耦合在一起。这样可以把测试集、评估脚本、历史报告都沉淀下来后续每次换模型、改提示词都能重新跑一遍。agent-eval/ ├── .env ├── requirements.txt ├── data/ │ ├── tasks.json │ └── expected_results.json ├── eval/ │ ├── __init__.py │ ├── adapters.py │ ├── runner.py │ ├── tools.py │ └── judge.py ├── reports/ │ └── result_20250101.json └── run_eval.pydata 目录存放人工整理的评测任务eval 目录存放评估核心代码reports 目录存放每次运行的报告run_eval.py 是入口脚本。这样组织的好处是测试数据和代码分离以后新增用例不用改代码只改 JSON 文件。2.3 用适配器屏蔽模型差异不同厂商的模型 API 调用方式不一样如果评估脚本里到处写死 SDK 调用后续每换一个模型都要改一遍业务代码。这里我引入一个很简单的 Adapter 模式即所有模型统一暴露一个 generate 方法输入消息列表和工具定义输出文本或工具调用结果。这样做的好处有两个评估脚本只依赖统一接口新增模型时只需要新增一个 Adapter 类其他代码不用动。这也是负责任的生产级 Agent 项目中很常见的设计手法。3. 搭建 Agent 评估体系3.1 核心评估指标怎么定在动手写代码前先要把指标定清楚。不同业务对 Agent 的要求不一样但下面的指标是通用的建议全部记录再根据业务重点决定看哪个。指标说明关注原因任务成功率最终结果与预期是否匹配最基本的业务目标工具调用准确率调用的工具名和参数是否正确直接影响任务能否完成平均轮数完成任务的 LLM 请求次数轮数越多失败概率越高平均耗时单任务端到端耗时影响用户体验与成本token 消耗输入输出 token 总量直接关联成本错误恢复率第一次调用失败后能否继续完成反映模型的鲁棒性在 Agent 评估里任务成功率不等于“最终答案对”。因为用户可能只关心最终答案但我们要分析的是成功是高效地成功还是碰巧成功。低质量的 Agent 经常表现为最终结果对了但过程里工具调用错误很多或者出现幻觉参数。3.2 评测数据集正常用例与异常用例都要有很多人准备评测数据时只写“标准任务”比如“查询订单状态”其实这远远不够。我建议按比例准备三类用例正常用例包含明确意图和完整信息用于验证基本能力。缺参用例用户没说全参数需要 Agent 追问或推断。冲突/异常用例工具返回错误或者用户指令与事实冲突考察 Agent 的纠错能力。下面是一个 tasks.json 的示例结构每个任务都有 id、用户描述和可选的预期工具序列。[ { id: order_normal_001, type: normal, user_input: 帮我查一下订单 20250101001 的物流状态, expected_tools: [get_order_status] }, { id: user_missing_002, type: missing_param, user_input: 这个订单好像卡住了帮我看看怎么回事, expected_tools: [ask_user, get_order_status] }, { id: tool_error_003, type: error_recovery, user_input: 查一下订单 20250101999 的物流状态, expected_tools: [get_order_status], note: 该订单在模拟工具中会返回错误观察模型是否能处理 } ]注意expected_tools 只是用于辅助分析不完全等于“必须完全一致”因为很多任务有多条合法路径。比如“查订单状态”可以先询问用户具体订单号也可以根据上下文直接推断。这部分需要结合人工判断不能全交给自动规则。3.3 结果判定规则、工具结果与 LLM 审查Agent 产出的结果不能简单用“包含关键词”来判断我一般分三层来判定。第一层是工具层校验检查工具调用格式是否合法参数是否完整是否在已注册工具集合内。这些完全可以用程序规则判断不依赖 LLM 能力。第二层是结果层校验根据任务类型编写规则检查最终输出是否包含关键信息比如订单号、状态字段、金额等。第三层是语义层校验由另一个更稳定的模型作为裁判输入任务描述、Agent 完整执行轨迹和最终答案输出 1 到 5 分并给出简短理由。这种做法通常叫 LLM-as-judge适用于最终结果无法用规则穷举的任务。有了这三层评估结果就不会只看一个“过/不过”而是能看到每个任务的具体失分点。4. 评估脚本完整实现4.1 任务定义与模拟工具先定义两个简单的模拟工具get_order_status 和 refund_order。注意 tools.py 中模拟工具返回的是结构化数据这样便于观察模型是否真的按工具返回结果来组织语言。# 文件路径eval/tools.py import json import datetime TOOLS [ { type: function, function: { name: get_order_status, description: 根据订单号查询物流与订单状态, parameters: { type: object, properties: { order_id: {type: string, description: 订单号} }, required: [order_id] } } }, { type: function, function: { name: refund_order, description: 对指定订单发起退款申请, parameters: { type: object, properties: { order_id: {type: string, description: 订单号}, reason: {type: string, description: 退款原因} }, required: [order_id, reason] } } } ] def get_order_status(order_id: str) - str: if order_id 20250101999: return json.dumps({error: order not found}, ensure_asciiFalse) return json.dumps({ order_id: order_id, status: shipping, logistics: 顺丰速运, tracking_number: SF1234567890, updated_at: datetime.datetime.now().isoformat() }, ensure_asciiFalse) def refund_order(order_id: str, reason: str) - str: return json.dumps({ order_id: order_id, refund_status: applied, reason: reason }, ensure_asciiFalse) TOOL_DISPATCH { get_order_status: get_order_status, refund_order: refund_order }这里要把工具名、参数和返回结果设计得尽量贴近真实业务因为模型对工具描述非常敏感。工具 description 写得越清楚模型越不容易乱调参数。4.2 模型适配器sonnet 与 deepseek v4 flash 统一调用接下来实现两个模型适配器。核心是一个 generate 方法输入 messages 和可选的 tools返回一个统一结构文本内容、工具调用列表、usage 信息。# 文件路径eval/adapters.py import os from openai import OpenAI from anthropic import Anthropic class BaseAdapter: def generate(self, messages, toolsNone): raise NotImplementedError class V4FlashAdapter(BaseAdapter): def __init__(self): self.client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com) ) def generate(self, messages, toolsNone): resp self.client.chat.completions.create( modelos.getenv(DEEPSEEK_MODEL, deepseek-v4-flash), messagesmessages, toolstools, temperaturefloat(os.getenv(V4_FLASH_TEMPERATURE, 0.2)), max_tokensint(os.getenv(V4_FLASH_MAX_TOKENS, 2048)) ) choice resp.choices[0] tool_calls [] if choice.message.tool_calls: tool_calls [ { id: tc.id, name: tc.function.name, arguments: tc.function.arguments } for tc in choice.message.tool_calls ] return { content: choice.message.content or , tool_calls: tool_calls, usage: { prompt_tokens: resp.usage.prompt_tokens, completion_tokens: resp.usage.completion_tokens } } class SonnetAdapter(BaseAdapter): def __init__(self): self.client Anthropic( api_keyos.getenv(ANTHROPIC_API_KEY) ) def generate(self, messages, toolsNone): system_messages [m for m in messages if m[role] system] normal_messages [ {role: m[role], content: m[content]} for m in messages if m[role] ! system ] kwargs { model: os.getenv(SONNET_MODEL, claude-sonnet-latest), max_tokens: int(os.getenv(SONNET_MAX_TOKENS, 2048)), messages: normal_messages, temperature: float(os.getenv(SONNET_TEMPERATURE, 0.2)) } if system_messages: kwargs[system] system_messages[-1][content] if tools: kwargs[tools] tools resp self.client.messages.create(**kwargs) tool_calls [] for block in resp.content: if block.type tool_use: tool_calls.append({ id: block.id, name: block.name, arguments: block.input }) text_content .join( block.text for block in resp.content if block.type text ) return { content: text_content, tool_calls: tool_calls, usage: { prompt_tokens: resp.usage.input_tokens, completion_tokens: resp.usage.output_tokens } }需要特别说明的是这个代码只是示例思路不同版本的 SDK 在系统提示词、工具参数格式上有差异建议以你本地实际安装版本的官方文档为准。核心思想是外部代码只依赖 generate 方法至于内部怎么组装消息是每个 Adapter 自己的事。4.3 Agent 运行器与 Trace 记录有了统一适配器之后就可以写一个不依赖具体模型的 Agent Runner。它负责维护消息列表、循环调用模型、执行工具调用、把结果回填给模型直到模型不再返回工具调用或达到最大轮数。Runner 还会记录每一步的 trace包括每轮的输入输出、工具调用结果、耗时、token 用量这样之后分析失败原因时才有据可查。# 文件路径eval/runner.py import time import json from eval.tools import TOOL_DISPATCH class AgentRunner: def __init__(self, model, tools, max_rounds5): self.model model self.tools tools self.max_rounds max_rounds def run(self, task): messages [ {role: system, content: 你是一个智能订单助手需要根据用户需求调用工具。 如果信息不足可以直接提问不要盲目调用工具。}, {role: user, content: task[user_input]} ] start_time time.time() trace [] total_prompt_tokens 0 total_completion_tokens 0 tool_count 0 for round_idx in range(self.max_rounds): resp self.model.generate(messages, toolsself.tools) total_prompt_tokens resp[usage][prompt_tokens] total_completion_tokens resp[usage][completion_tokens] trace.append({ round: round_idx 1, messages_snapshot: messages, response: resp }) if resp[tool_calls]: messages.append({ role: assistant, content: resp[content], tool_calls: resp[tool_calls] }) for call in resp[tool_calls]: tool_count 1 try: args json.loads(call[arguments]) if isinstance(call[arguments], str) else call[arguments] result TOOL_DISPATCH[call[name]](**args) except Exception as exc: result json.dumps({error: str(exc)}, ensure_asciiFalse) messages.append({ role: tool, tool_call_id: call[id], content: result }) continue messages.append({role: assistant, content: resp[content]}) break return { task_id: task[id], messages: messages, trace: trace, tool_count: tool_count, latency_seconds: round(time.time() - start_time, 3), total_prompt_tokens: total_prompt_tokens, total_completion_tokens: total_completion_tokens, rounds: len(trace) }注意这里有一个很关键的细节模型返回 tool_calls 后需要把 assistant 的 tool_calls 原样回填到消息列表然后逐条把工具执行结果按 tool_call_id 追加为 roletool 的消息。如果这一步做错模型就会在下一轮无法关联工具结果很容易出现 agent execution terminated due to error。除了这些还可以在 Runner 里加入安全护栏逻辑比如超过 max_rounds 直接强制停止避免模型在失败任务里无限循环消耗 token。4.4 批量运行与汇总报告最后通过 run_eval.py 批量跑任务把每个任务的结果、判定结论和汇总指标输出为 JSON 报告。这样你可以把多份报告存档方便后续做模型版本回归对比。# 文件路径run_eval.py import json import os from dotenv import load_dotenv from eval.adapters import SonnetAdapter, V4FlashAdapter from eval.runner import AgentRunner from eval.tools import TOOLS load_dotenv() MODEL_CHOICES { sonnet: SonnetAdapter, v4-flash: V4FlashAdapter } def run_all(model_name, task_filedata/tasks.json): with open(task_file, r, encodingutf-8) as f: tasks json.load(f) adapter MODEL_CHOICES[model_name]() runner AgentRunner(modeladapter, toolsTOOLS, max_rounds5) results [] for task in tasks: result runner.run(task) result[model] model_name result[completed] ( result[rounds] 5 and result[tool_count] 0 ) results.append(result) print(f[{model_name}] {task[id]} rounds{result[rounds]} ftools{result[tool_count]} completed{result[completed]}) report { model: model_name, total_tasks: len(results), success_count: sum(1 for r in results if r[completed]), avg_rounds: round(sum(r[rounds] for r in results) / len(results), 2), avg_tokens: round(sum(r[total_prompt_tokens] r[total_completion_tokens] for r in results) / len(results), 2), results: results } os.makedirs(reports, exist_okTrue) with open(freports/result_{model_name}.json, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2) return report if __name__ __main__: import sys model sys.argv[1] if len(sys.argv) 1 else v4-flash report run_all(model) print(json.dumps(report, ensure_asciiFalse, indent2))运行方式如下python run_eval.py sonnet python run_eval.py v4-flash这份脚本的重点不在算法而在于“可重复、可留痕、可对比”。每次运行后都生成一份 JSON 报告将来可以把报告放到代码仓库里作为模型切换时的审批附件。5. 从 sonnet 切换到 deepseek v4 flash 的实操要点5.1 切换前的调用差异整理在真正切代码之前先把两个模型在 API 层面的差异列出来避免踩到低级坑。第一消息格式不同。Anthropic 的 messages 接口里系统提示词通常通过 system 参数传递而 OpenAI 兼容接口里系统提示词是 messages 数组中 rolesystem 的消息。所以 Adapter 里我做了一次转换把 system 消息单独提取出来。第二工具调用格式不同。Anthropic 的 tool_use block 里input 是一个 dictOpenAI 兼容接口里arguments 是 JSON 字符串。解析时要么用 json.loads要么直接透传不能混用。第三上下文管理策略不同。两个模型的上下文窗口大小以及超长截断行为都不一样。如果你的 Agent 有长对话记忆切换后要重新测试“多轮记忆”场景不能假设行为一致。这些差异在评估脚本里已经通过 Adapter 屏蔽了但线上业务代码同样需要做一层类似的适配否则切换后很容易出现解析错误。5.2 deepseek v4 flash 参数设置建议在评估过程中我重点调了 temperature、max_tokens、stop 这几个参数。模型名字符串以控制台实际配置为准不要照抄文章里的名字。temperature 建议先设为 0.2 到 0.3。Agent 场景需要稳定决策温度太高会让工具参数随机变化出现同样的请求有时成功有时失败。如果你想在稳定基础上带一点多样性可以设置在 0.4 左右但不要超过 0.7。max_tokens 要根据工具返回结果大小来设置。如果你的 Agent 会接收比较长的工具返回且要求模型把完整信息摘要给用户建议给足输出空间。这里需要通过评估数据的 completion_tokens 分布来判断不要拍脑袋。stop 参数可以用来控制模型不要输出多余内容但如果你使用 JSON 结构化输出或工具调用能力一般不需要手动设置 stop优先使用模型自带的约束机制即可。参数设置建议在评估脚本里通过环境变量配置不要每跑一次就改代码。这样既方便调参也方便后面做多组参数对比。5.3 工具调用格式兼容处理切换模型后最常见的坑是工具调用的 arguments 格式不统一。Anthropic 返回的是 dictOpenAI 兼容接口返回的是字符串很多 Agent 框架在解析时只兼容其中一种。我建议在 Adapter 层就把工具调用统一成同一个内部结构比如统一为 name、arguments、id 三个字段其中 arguments 可以是 dict。这样下游代码就不用关心来源模型所有逻辑都基于内部结构开发。另外deepseek v4 flash 这类模型在工具调用时偶尔会输出一段解释性文本再附带工具调用。Runner 里要同时处理 content 和 tool_calls不要让 content 为空时崩溃。这也是很多 Agent 项目在切换模型后报错的原因之一。5.4 切换后的结果对比怎么读跑完两个模型后不要只看成功率一个数字。我把常见的对比视角整理成下面的表格你在自己分析时也可以这样归类。对比维度关注问题说明成功率新模型能否完成同样多的任务这是最基础的门槛平均轮数新模型是否更绕路轮数越多失败概率越高工具调用规范度是否存在参数乱传直接影响下游系统延迟与成本是否达到切换目的没有收益就没必要切换失败模式失败集中在哪类任务决定是否值得针对优化根据我这次切换的验证经验典型的结果是deepseek v4 flash 在正常用例上的成功率与 sonnet 接近但在缺参追问、错误恢复类用例上需要额外调参数和提示词。这里不给出具体分数因为你的任务集不同数字没有参考意义但下面这个表格演示了如何组织对比数据你可以自行填充任务类型sonnet 成功率v4-flash 成功率主要差异正常查询高高基本持平缺参追问高中v4-flash 可能直接猜测参数工具异常恢复中低v4-flash 更容易直接终止如果发现 v4-flash 在某类任务上明显偏弱不要急着否定它可以在 system prompt 里增加约束比如“当工具返回错误时必须向用户说明并重试一次”然后重新评估。模型的行为是可以通过提示词和参数调节的这也说明了评估体系的重要性——没有评估你就无法判断调整到底有没有效果。6. 常见报错与排查思路6.1 agent execution terminated due to error这是切换模型后比较常见的报错现象是 Agent 在运行一段时间后突然终止没有任何有效输出。常见原因有三种第一是上下文消息格式错误。比如把 assistant 的 tool_calls 没有回填或者把 tool 消息的 tool_call_id 写错导致模型生成的下一轮请求不合法。第二是工具调用返回内容异常。工具执行抛出的异常没有转换成 string 传给模型而是直接让 Runner 崩溃上层框架抓到后终止任务。第三是最大轮数触发。模型在同一任务里反复调用同一个错误工具达到轮数上限被强制停止但错误提示不直观。排查时先打开 trace看最后一步模型返回了什么。如果是 tool_call_id 报错检查消息回填逻辑如果是工具异常检查工具函数的异常兜底如果是轮数触发考虑是提示词不够明确还是参数设置不合理。6.2 工具调用格式不兼容切换模型后出现“工具参数解析失败”很常见。比如 sonnet 返回的 input 是 dict而 deepseek v4 flash 返回的 arguments 是字符串你用同一套代码去处理就会报错。解决思路是在 Adapter 层统一格式对 arguments 做一次 json.loads 尝试如果失败则保留原始字符串并记录 warning。尤其要警惕模型偶尔输出 Markdown 包裹 JSON 的情况解析前需要先清理多余字符。另外不要把工具调用的解析逻辑散落在业务代码里最好收敛到统一函数中并加上结构校验。这样就算模型行为变化也只需要改一个地方。6.3 输出截断与参数调整如果你发现模型在长任务里只完成了前半段后面的工具调用没有出现最常见原因是 max_tokens 设置太小。模型在生成中途用完 token导致整个响应被截断最后你根本拿不到 tool_calls。另一个原因是上下文太长模型在最后几轮“迷失”了。此时可以精简 system prompt或者把历史消息截断到最近几轮。不要依赖“加 max_tokens”解决所有问题上下文过长时即使 token 足够模型注意力也可能分散。调试时可以记录每一轮的输入 token 数再观察 max_tokens 是否经常触顶。这样就能判断问题是输出长度不够还是输入信息过载。7. Agent 评估与模型切换的最佳实践7.1 先把评估集沉淀到仓库很多团队做模型评估是一次性的换模型前跑一次觉得“差不多”就上线之后再也不维护。这是比较危险的做法。模型版本会更新提示词会调整Agent 框架也会升级任何一次改动都可能让线上效果退化。正确的做法是把评测数据当成代码资产纳入版本管理。每次调整 system prompt、修改工具定义、升级模型版本都在本地跑一遍完整评估把结果报告提交到仓库。这样既方便回滚也能在模型能力变化时及时发现回归。评测数据要持续补充。线上用户反馈的失败案例、运营发现的异常行为都可以加工成新的评测用例。时间越长评估集的覆盖度越高评估结果越可信。7.2 小流量灰度与模型路由评估全部通过后也不建议一次性把所有流量切到新模型。生产环境的真实输入分布和测试集有差异总有一些用例是你评测时没想到的。建议按用户比例灰度比如先放 5% 流量给 deepseek v4 flash观察成功率、耗时、成本再逐步提升到 10%、30%、50%。如果出现明显异常可以迅速把流量切回 sonnet。更进一步可以根据任务复杂度做模型路由简单任务走 fast/cheap 模型复杂任务走能力更强的模型。比如“查订单状态”这种单工具调用任务用 v4-flash“多轮退款投诉”这类复杂任务继续用 sonnet。评估报告里的任务类型数据可以帮你判断哪些任务适合路由给哪个模型。7.3 可观测性日志、Trace 与成本监控切换模型前团队里最慌的往往是运维新模型是不是更贵了延迟是不是变高了回答质量是不是不稳定这些担忧需要通过可观测性来回应。线上 Agent 日志至少要包含任务 ID、模型名、每轮的工具调用记录、token 用量、耗时、最终结果。这样排查问题时才能回溯到具体是哪一轮出错是模型问题还是工具问题。成本监控建议按任务维度聚合不要只看总量。有时候总量下降但某个高频任务反而变贵了这种细节要能看见。延迟同样要按 p50、p95 分开统计平均延迟不说明问题。7.4 保持模型供应商兼容性这次只切换了一次模型但你迟早会遇到下次切换。为了降低未来的切换成本从一开始就保持模型供应商兼容。具体来说业务代码不要直接依赖某个厂商的 SDK 数据结构而是定义自己的内部消息结构、工具调用结构和结果结构。厂商 SDK 只放在 Adapter 层上层逻辑只依赖内部结构。这个设计一开始会多花一点时间但后续每次模型升级或迁移都会省很多事。8. 后续建议与实用收尾上面这套评估方法覆盖了从指标设计、测试集组织、评估脚本、参数调整到生产灰度的完整流程。你可以先把自己项目的 20 到 30 个典型任务整理成 tasks.json跑通 script再慢慢补充用例。前期不需要追求大而全的评估集先保证能稳定复现、能留存报告、能定位失败原因。切换模型这件事真正有效的工具不是“哪个模型更好”的讨论而是一套让模型行为变得可测量、可对比、可回滚的评估机制。如果你正在纠结 sonnet、deepseek v4 flash 或者其他模型选型不妨先把本文中的 runner 和 adapter 跑起来用你真实的业务数据说话。建议你把报告结果保存下来一个月后再用同一份测试集跑一遍看模型更新后是否带来退化。如果有问题可以按照第六节的排查思路逐项定位。记住一点Agent 项目的质量不是模型单方面决定的而是模型、提示词、工具定义和工程结构合力的结果。