上个月复盘内部一个Agent项目时,我看着监控面板皱了半天眉头:离线评测集上准确率明明有九成,可生产环境里,用户一次多轮任务平均要经历接近三次重试,才能换来一个勉强可用的结果。同一个模型,在评测Harness里跑得好好的,一放进真实业务就各种不受控。后来把精力从“换更强的模型”挪到“搭一个稳定的控制面”上,才真正把Agent从Demo推到了可用。
这篇文章想聊的是我对Harness的理解:它不该只是“管好模型”的评测框架,更应该成为企业Agent运行时真正的控制中枢。以及,这个控制面为什么值得你花心思认真建。
1. 先看清失控现场:Agent 明明能答对,为什么一上生产就翻车
1.1 离线评测的高分幻觉
很多团队对Agent的第一轮验证,都是拿一个评测Harness跑静态数据集。固定问题、固定标准答案、单轮问答、算准确率或者F1,最后得出一张漂亮的成绩单。我也这么干过,而且成绩很好看。
问题在于,评测Harness测的是“模型在孤立环境里能不能答对”,不是“Agent在复杂业务里能不能稳定完成任务”。生产环境里的Agent是三件事同时发生的:多轮对话、实时工具调用、动态上下文。评测集里没有并发,没有上下文挤爆,没有用户乱打字,也没有外部接口超时。单轮问答的高分,掩盖了多轮协作和异常恢复能力的缺失。
举个我实际见过的例子。某个客服Agent,单轮问题响应质量很高,准确率超过九成。但用户真正使用的时候,经常是先问A、再问B、中间还穿插一句情绪化表达。等Agent需要引用第一轮的信息时,上下文已经被中间那些无关内容挤走了。它只好再问用户一遍。用户体验极差,后台日志里全是“Agent兜底回复”和“重试”。
1.2 生产环境里的四类失控特征
我把实际踩到的失控问题归成四类,每一类都直接指向控制面为什么存在。
状态失控。这是最普遍的。生产Agent的上下文窗口是共享资源,但很少有人给它做预算管理。用户的会话一旦变长,早期关键信息被挤出窗口,Agent就“失忆”。再往下走,整个任务链条就断了。
工具失控。Agent在推理过程中会决定要不要调用工具,以及传什么参数。一旦模型出现幻觉,它可能用完全离谱的参数去调一个敏感的写操作接口。更可怕的是循环:某个工具调用失败后,模型为了“完成任务”会反复用不同参数重试同一个调用,形成递归风暴。
成本失控。我见过一次线上事故,一个看似简单的“查订单状态”请求,因为中间链路反复出错,实际触发了二十多次大模型调用。单次调用成本不高,放大到每天几万请求就是一笔让人肉疼的账单。没有控制面做调用次数预算和熔断,成本就像漏水的管子,等看到账单再补救已经晚了。
行为失控。同样的用户问题,早上和下午得到的回答可能完全不同。模型温度、上下文变化、提示词小幅调整都会影响输出。业务方来做审计时,问“为什么昨天给用户退了这个金额”,你翻遍日志也说不清楚决策链路。这在金融、医疗、政务场景里几乎是致命的。
单靠模型聪明是解决不了这四类问题的。模型只会生成文本,它不知道企业运行的边界在哪里。需要一个在它之上的控制面,把状态、工具、成本、行为全部管起来。
2. 控制面与模型面的分工:Harness 到底在“管”什么
2.1 模型面是发动机,控制面是驾驶舱
理解控制面最简单的方式,是用发动机和驾驶舱来类比。发动机负责产生动力,它只管把油转化成转动;驾驶舱负责定方向、踩刹车、看仪表、规避障碍。你不可能靠发动机自己完成一次安全驾驶,Agent也一样。
在系统架构里,模型面(Engine Plane)管的是推理本身:权重、KV Cache、解码参数、推理引擎调度。它关心的是吞吐、延迟、单次生成质量。控制面(Control Plane)则管的是推理之外的所有决策:把请求路由到哪个模型、上下文窗口怎么分配、工具要不要放行、超时怎么处理、失败要不要重试、成本配额有没有超、整条链路有没有留痕。
容器编排平台正是靠这个思路运转的:它把“跑容器”和“管容器”分开,才让大规模集群稳定多年。企业Agent要规模化,也必须走同样的路。
2.2 模型面与控制面的关注点对比
| 维度 | 模型面 | 控制面 |
|---|---|---|
| 核心职责 | 文本生成、逻辑推理 | 调度、策略、记忆、工具、审计 |
| 典型指标 | 准确率、生成延迟、吞吐 | 任务成功率、成本、合规、可解释性 |
| 变更频率 | 几周或几个月更新一次 | 随业务需求随时演进 |
| 故障影响 | 单次回答质量下降 | 整个Agent运营体系失控 |
| 用户感知 | “这回答对不对” | “这事办没办成、稳不稳定” |
这里有个容易被忽略的细节:控制面的策略变化通常比模型版本迭代频繁得多。业务规则今天加一条、明天调一个阈值,如果我们把这些规则写进提示词里,就等于让模型来背业务账本,后果就是行为失控。控制面把这些规则外部化,变成可配置、可测试、可回滚的策略,模型只管理解语义和生成内容。
2.3 从评测Harness到生产Harness的演进
很多人熟悉的是评测Harness——用来跑Benchmark、比模型、验证提示词效果。它像一个体检中心,定期告诉你模型各科成绩如何。但体检报告再准,也不能代替医院做治疗。
生产Harness才是运营系统:它接收真实请求,安排上下文,决定工具调用,执行限流降级,记录所有轨迹,最后对结果负责。评测Harness应该成为生产Harness的一个子模块——即发布门禁——而不是被当成最终的交付物。
我在项目里常跟团队强调一句话:评测Harness回答的是“模型行不行”,生产Harness回答的是“Agent能不能一直行”。后者才是企业要的东西。
3. 控制面必须“独立”于模型面的三个理由
控制面如果只是概念层面的东西,很容易被忽略。我用了很长时间才说服自己,它必须作为架构上独立的一层,而不是散落在业务代码里的若干工具函数。理由有三个。
3.1 模型迭代节奏与应用迭代节奏严重错位
模型领域的更新节奏是月度甚至季度级的,而且版本升级往往伴随能力波动,今天是这个模型强,明天可能另一个模型在某些任务上更优。企业应用侧的迭代节奏却是天级的——促销规则、合规要求、业务流程,随时可能变。
如果控制逻辑全部写在某个特定模型的提示词里,模型一换,整个Agent行为就要重新调一遍。反过来说,如果业务规则散落在系统代码里,业务想改一条策略,还得排队等研发发版。
控制面独立出来之后,模型层和应用层就解耦了。应用侧只依赖稳定的控制接口,控制面内部决定调用哪个模型、用哪套策略。换模型只是控制面配置变更,而不是系统重写。
3.2 治理一致性没法靠模型自觉
企业治理要求是一致的:敏感信息不能外泄、工具调用不能越权、单次任务成本有上限、黑名单必须生效。但不同模型对这些规则的执行能力差异很大。某个模型可能对“不要读取用户隐私字段”的指令执行得很好,另一个模型换表述就翻车。
把这些规则全部押在模型自觉上,就等于让执行结果取决于运气。控制面把治理逻辑放在模型之外:请求进入前先做权限校验,工具调用前先做参数schema校验,输出前做敏感信息过滤,超预算直接熔断。每一步都是确定的代码逻辑,不依赖模型心情。这样才能对业务方给出确定性承诺。
3.3 回滚与灰度必须是两层操作
没有独立控制面的时候,Agent出了线上故障,你只能修改业务代码然后重新发布。且不说发布周期长,光是定位“是模型抽风还是策略写错”就要吵半天。
控制面独立之后,回滚结构变得清晰:模型层可以灰度,控制面策略层也可以灰度。甚至更细一点,工具权限配置、上下文摘要逻辑、重试参数,每一项都能独立回滚。某个环节出了问题,先判断是哪一层,然后单独操作那层。风险爆炸半径被控制在较小范围内,这对生产系统来说是实打实的安全感。
4. 生产级控制面的落地路径:从 Mini Demo 到稳定平台
概念讲再多,不如动手做。下面是我认为从零搭建生产级控制面最务实的四条路径,按顺序来,不要跳。
4.1 第一步:定义Agent的“运行契约”
地基是一份统一的请求/响应协议。没有这份契约,后面所有中间件都无从谈起。我建议在一开始就定义清晰的数据结构,包含请求、决策轨迹、资源消耗、工具调用记录和最终结果。
一个简化版的协议模型大致长这样:
{ "request_id": "req_20250101_001", "agent_task": { "goal": "查询订单状态并触发退款", "user_context": { "session_id": "s_12345", "input": "帮我看看昨天那个订单到哪了", "user_id": "u_88", "sensitive_fields_masked": true }, "constraints": { "max_llm_calls": 8, "max_tool_calls": 5, "timeout_ms": 30000 } }, "control_trace": { "model_route": "mid-tier-chat", "context_summary": "用户昨天在咨询订单配送问题...", "tool_plan": [ { "sequence": 1, "tool_name": "query_order", "args_schema_validated": true, "permission": "allowed", "latency_ms": 180, "cost_tokens": 1200 } ], "error_handling": [ { "step": "tool_call_3", "error_type": "timeout", "action": "retry_with_backoff", "retry_count": 2 } ], "final_output": { "content": "订单已查到,正在为您发起退款...", "cost_tokens_total": 5400 } } }这串东西看起来繁琐,但它解决了一个实质问题:让Agent的每一步决策都可被程序访问、可被策略拦截、可被后续审计。没有这个字段结构,后面的上下文控制、工具授权、成本限制、可观测,全都是空谈。
4.2 第二步:搭三个核心中间件
有了契约,就可以在Agent周围挂中间件了。我把生产控制面需要的中间件收敛为三个,太多会过度设计,太少兜不住风险。
上下文中间件。它负责管理进入模型的所有内容。核心职责是:窗口预算(每轮该给多少token)、动态摘要(长对话压缩历史)、关键槽位保留(用户ID、订单号、核心诉求不能被摘要丢掉)。我习惯把上下文策略设计成组合式:系统指令、业务槽位、动态摘要、近期对话,按照优先级分配预算。
工具中间件。它负责管理Agent能调用什么。核心职责包括:技能注册表(只有注册过的工具才能被模型调用)、参数schema校验(模型给出的参数必须符合接口定义)、权限白名单(这个用户这个会话允不允许执行写操作)、频控(某个工具一分钟内最多调用几次)。这一步做扎实了,工具失控问题能消掉八成。
可靠性中间件。它负责在异常情况下保住系统。包括超时控制(单次LLM调用最长等待时间)、指数退避重试(退避间隔随重试次数增长)、熔断器(连续失败N次,短时间不再调用该工具或模型)、降级方案(模型挂了,回落到简单规则回答)。
这三个中间件用装饰器式的管道来串联,每个中间件只处理自己那一跳,非常干净。
class ControlPlane: def __init__(self): self.pipeline = [ ContextMiddleware(), # 上下文预算与摘要 ToolMiddleware(), # 技能注册、权限、频控 ReliabilityMiddleware(), # 超时、重试、熔断 ] def run(self, agent_request): request = agent_request for middleware in self.pipeline: request = middleware.before(request) if request.is_blocked(): return request.blocked_response() response = self.invoke_model(request) for middleware in reversed(self.pipeline): response = middleware.after(request, response) return response这里的核心思想是:中间件把“模型决策”和“平台管控”彻底分层。模型在中间件围成的圈子里做推理,所有边界约束都由代码保证,模型只负责它最擅长的语义理解。
4.3 第三步:把评估Harness接进发布门禁
现在可以把评测Harness放到正确的位置:作为控制面的子模块,承担发布门禁职责。具体做法是,任何提示词改动、模型版本切换、控制面策略调整,先跑两套验证。
第一套是离线回归集。这个集合要来自线上真实流量,覆盖正常路径、边界路径和故障路径。比如“订单查询成功”“订单查询超时”“权限不足被拒”“上下文超长触发摘要”等等。改动上线前,控制面用这套回归集自动跑一遍,成功率低于阈值直接拦下。
第二套是灰度观察期。改动先发给百分之五的流量,观察任务成功率、平均token消耗、工具调用失败率、人工介入率这几个核心指标。没有异常再逐步放量。放量的过程不是拍脑袋,每一步都有指标对照。
这两套加在一起,才让“稳定控制面”有了可验证的标准。评测Harness不再只是算法团队跑比分用的玩具,而是整个平台发布的质检闸门。
4.4 第四步:可观测性与审计追踪
最后一步是给整个系统装上飞行记录仪。每个Agent任务生成一个Trace,里面完整记录:用户输入、每轮模型输入输出、每次工具调用的参数和结果、每条策略的命中情况、每次重试的原因和间隔、总token消耗、最终响应和置信度。
这些Trace既是排查利器,也是自动构建回归集的素材库。我见过最实用的一个习惯是:每周自动从线上Trace里挑出“表现不好的样本”(用户点踩、人工介入、重试过多),补充进回归集。这样一来,线上踩过的每个坑,都会永久沉淀成测试用例,防止回归。
Trace的存储结构可以按需设计,但至少要有按Request关联链路的能力,让一次完整业务操作能从入口查到出口每一步。
5. 踩过的坑与给团队的实操建议
控制面搭建过程中,真正让人印象深刻的不是架构设计多精巧,而是那些不起眼的细节如何把系统逼到墙角。复盘几个高频踩坑点,给后来人打个样。
5.1 坑一:上下文策略“一刀切”,长任务反而崩
刚开始我们把上下文策略做成固定的“最近N轮对话”模式,短任务是稳了,但长任务(比如理赔审核,需要用户提供多份材料)到第三轮就忘记用户的身份信息和材料状态。解决方式是引入“关键槽位保留”:从对话里用规则抽取用户ID、订单号、审批状态等结构化字段,存到独立槽位里,每次构造上下文时强制插入,不受窗口裁剪影响。记住,上下文管理不是简单裁剪,而是分配预算:系统指令、槽位、摘要、近期对话,每一项都有明确优先级和上限。
5.2 坑二:工具授权放在调用后,等于没挡
早期版本允许模型先调用工具,调用完成后再去检查权限,查出来有问题就终止。这听起来合理,但模型已经看到了工具返回结果,敏感信息已经暴露在对话里。后来改成“前置双检”:模型表达调用意图后,先校验工具白名单和参数schema,再检查用户对目标资源的操作权限,全部通过才真正放行。在“调用前”拦截,而不是“调用后”追责,这是所有生产级Agent必须把握的底线。
5.3 坑三:无差别重试,把抖动放大成雪崩
有段时间一到高峰期,外部接口偶尔超时,我们的重试逻辑就疯狂触发。单次超时加两次重试看起来没什么,但当几百个请求同时进来时,每个请求都打三倍流量出去,外部依赖直接被压垮,后来又连带影响模型服务限流。最终调整为:对超时和限流类错误做指数退避重试,首次退避300毫秒,最多三次;对非法输出、参数错误这种逻辑错误,坚决不重试,直接返回兜底文案。重试不是万能的,你要区分哪些错误值得重试,哪些错误重试只会添乱。
5.4 给团队的落地顺序建议
如果你们正处在从Demo迈向生产的时间点,我的建议很直接:先解决看得见的问题,再谈管得住的优化。
第一优先级是把可观测性做出来。没有Trace,你连Agent到底怎么死的都不知道,所有优化都是猜。第二优先级是扎好工具权限和成本预算这两道篱笆,它们是可控运行的基本盘。第三优先级才是评测Harness与发布门禁的打通。
试点范围不要铺太大。先选一两个核心Agent,用控制面框架完整跑通生命周期,再逐步推广。控制面的技术负责人最好放在平台或基础架构侧,而不是算法团队内部,因为它本质上是一个跨模型、跨业务的应用运行时平台,需要的是工程化思维,而不是单纯调模型的能力。
最后分享一个我现在保留的癔症级习惯:每个Agent上线前,强制跑一次故障注入演练。人为制造工具超时、模型限流、上下文超长、参数非法这些场景,看控制面的熔断和降级能不能在预期时间内兜住。这个演练帮我挡住了好几次潜在的线上事故。控制面的价值,恰恰就在你希望它永远用不上、但它永远守着的那道防线里。