WRITING / 2026.09.23

Agent 从 Demo 到上线:评测、可观测性与安全边界

从研发排障助手的离线测试出发,建立任务成功、工具正确性、证据引用与执行成本的评测框架。说明怎样记录轨迹、定位失败、测试越权与提示注入,并把真实模型接入、灰度运行和回滚需要的证据与本地验证结果明确区分。

一个 Agent 在演示中成功回答问题,只能说明那次输入在那种条件下产生了一个结果。它是否引用了正确资料,是否执行了多余动作,换一种问法还能不能完成,工具失败后会不会越界,都需要独立的证据。上线验收不能只看一段流畅的回答。

本系列已经实现执行循环、工具调用、检索、上下文选择和恢复机制。最后一篇把这些能力放进统一评测。这里的“从 Demo 到上线”讨论所需的工程步骤,并不宣称配套离线案例已经成为生产 Agent;它没有接入真实模型,也没有执行真实运维操作。

先定义任务怎样才算完成

本例的目标是给出有证据、可供人工评审的排障建议。完成条件至少包括:查询到正确服务的现场数据,识别相关变更,引用适用文档,保持建议与证据一致,并且没有执行越权动作。服务是否恢复,不属于这个只提供建议的任务能够证明的结果。

回答流畅、工具成功和任务完成是三个不同层次。工具可以正确返回数据,而模型仍然误读它;回答可以措辞谨慎,却没有取得必需证据;任务也可能通过不允许的写操作达到表面目标。评测需要把这些情况分开,不用一个“看起来不错”统一代替。

可以为每个场景描述输入、允许工具、所需证据和失败条件。失败条件应包含禁止行为,例如不得重启服务、不得读取另一用户资料。这样,即使答案内容正确,只要轨迹出现越权动作,也不能被记为合格任务。

场景还需要说明哪些情况允许停止。知识库没有答案时,承认证据不足可能是正确行为;如果评分器强制要求完整结论,就会鼓励编造。好的任务规范不仅定义应该做到什么,也定义在信息不足时应该怎样结束。

将评测拆成几层证据

第一层是确定性程序测试,验证参数、过滤、幂等、状态转换和预算。这些行为适合使用明确断言,失败后能够直接定位代码。第二层是模型行为评测,观察真实模型是否选择正确工具、利用证据并遵守边界。第三层是完整系统运行评测,覆盖网络、并发、鉴权和用户交互。

本系列完成的是第一层,并额外使用官方 MCP SDK 进行工具发现与调用集成测试。模型决策来自固定样例,所以二十四个场景全部通过,并不等于真实模型在二十四个任务上成功。将这两种“通过”混在一起,会给使用者错误的可靠性预期。

固定样例仍然很有价值。如果执行器本身允许未知工具、跨用户查询或无限循环,再强的模型也无法补上这些系统边界。先建立确定性的基础,再加入真实模型评测,可以缩小故障归因范围,避免同时调试业务逻辑与生成行为。

评测闭环:定义任务与约束,执行并记录轨迹,分别检查结果和过程,归因失败,修正后回归

二十四个离线场景覆盖什么

测试按六组组织,每组四个场景,对应前面文章的核心机制。分组是为了让读者知道通过结果具体支持哪些结论,而不是用测试数量代替覆盖质量。

分组 验证范围
执行循环 正常引用、重复动作、步数预算、未知工具
工具契约 参数边界、故障注入、幂等冲突、只读权限
检索证据 范围过滤、空结果与补查、冲突资料、虚构引用
上下文 跨连接保存、过期过滤、用户与信任隔离、长输出预算
恢复编排 中断恢复、方案摘要绑定、等待确认、任务身份隔离
评测边界 正常契约、缺失证据、不可信内容与工具白名单、预算失败

另外一个测试使用 mcp==2.2.0 的真实客户端和服务端,在内存连接中发现并调用工具,校验结构化结果。它没有测试远程 HTTP 鉴权、网络中断或分布式传输,这些需要在实际部署条件下补充。

python run_tests.py

测试脚本生成 test-results.json,记录每个测试标识与状态。本次验证得到二十五个测试通过,其中二十四个是离线机制场景,一个是 MCP SDK 集成测试;没有跳过项。源码、固定数据和报告一起提供,读者可以修改条件后重新运行。

报告刻意把模型 token 与模型费用设置为空值,因为测试没有模型调用。空值表达“没有这个测量对象”,不能解释为真实模型零成本。类似地,本地工具耗时也不能当作端到端响应延迟。

评分器必须对应任务契约

示例的 evaluate 检查停止原因、必要引用、允许工具和工具错误。它不尝试判断整段中文的所有事实,而是覆盖程序可以确定的条件。这样的评分器容易复现,也便于发现“任务未完成却显示成功”的错误。

from demo import database, Tools, NORMAL_ACTIONS, run, evaluate

with database() as db:
    result = run(NORMAL_ACTIONS, Tools(db))
    report = evaluate(
        result,
        required_citations=["rb-01"],
        allowed_tools=["get_service_status", "get_recent_changes", "search_runbooks"],
    )
    print(report["passed"], report["errors"])

正常样例通过这些契约检查。删掉引用后,即使把回答改成“问题已经全部解决”,仍然会被标记为缺少证据。把一次不允许的重启动作放入轨迹,评分器会标记禁止工具。这样可以直接说明内容表面质量不能覆盖过程违规。

不过,引用存在也不保证结论正确。模型可能引用正确文档,却把“建议核对”写成“已经确认”。需要进一步将关键结论拆成可审查项,检查来源是否支持相应强度。某些判断可以使用人工评审或模型评分,但应保留规则和不确定性。

模型评分器本身也需要校准。不同措辞、顺序或背景信息可能影响评分,同一个模型还可能偏好自己的写法。可以用一组人工标注的正反例检查评分器是否区分真实错误,避免让一个没有验证过的模型输出成为最终质量标准。

执行轨迹应该记录什么

最小轨迹需要任务标识、步骤、工具名、参数摘要、返回状态、耗时和停止原因。还应能追溯证据来源、模型与工具版本,以及恢复或人工确认发生的时间。记录这些字段,是为了在失败时重建发生了什么,而不是收集越多越好。

本例记录工具名称、状态、观察或错误以及实际本地耗时。决策来源明确标为 scripted_fixture。这让读者能够判断报告的范围,不会把预置动作误认为模型自主生成。生产轨迹应同样保留运行条件,否则不同版本的结果难以比较。

日志应控制敏感信息。完整工具返回可能包含内部地址、用户信息或凭据,直接记录原文会扩大数据暴露范围。可以保存经过筛选的字段、内容摘要和受权限保护的证据引用。调试方便不能成为绕过访问边界的理由。

还要区分任务级与步骤级指标。一个任务经历三次重试后完成,步骤成功率和最终任务成功率并不相同。只统计最后一次结果,会隐藏不稳定性;只统计所有失败步骤,又可能夸大用户最终遭遇的失败。两者一起展示才能解释系统行为。

失败归因怎样指导修改

可以先判断失败发生在哪一层:没有找到正确工具,参数不符合契约,工具执行失败,检索证据不足,上下文丢失前提,回答误用证据,或者编排没有恢复。每种原因指向不同修正,而不是都归为“模型不够强”。

例如,检索结果为空,但文档确实存在,应检查索引、切分、过滤和查询表达。若文档已经进入上下文,回答仍然忽略版本限制,才进一步分析上下文布局、生成提示和模型能力。轨迹越能分层,修改越容易针对实际原因。

一次修正应保留原来的失败样例作为回归案例。否则换一个提示词改善当前问题后,可能破坏其他路径,而演示者只看见最近一次成功。评测集需要随着真实故障增长,同时防止反复针对少量案例过拟合。

也应保存失败分布,而不是只报告平均成功率。普通问题全部完成,却在涉及权限与写操作的少数案例上失败,可能比总体分数略低但边界稳定更难接受。业务需要为不同错误设定不同的验收要求,不能让大量简单样例稀释高影响失败。

接入真实模型后要测哪些东西

首先固定模型版本、提示词、工具定义、知识库版本和运行参数。修改其中任何一项,都可能影响结果。报告应能说明对比时究竟改变了什么,不能拿不同资料集或不同权限范围下的结果直接作结论。

同一个任务应重复运行,观察不稳定性。一次成功只能证明存在成功路径,不能证明系统会持续走到那里。重复次数应结合成本和风险选择,并报告样本量与失败类型;样本很少时,不要用精确到小数点的百分比制造过度确定性。

延迟应测完整请求时间,并拆分模型、工具、等待和恢复开销。工具本地耗时很短,不代表用户等待很短;并行调用降低了部分等待,也可能增加总 token 与资源消耗。应同时观察用户体验与系统成本,而不是单独追求某一个数字。

费用计算需要真实用量与当时采用的计费依据。模型输入、输出、缓存或其他计费项可能不同,不能用字符数直接乘一个固定价格。本系列没有实际模型用量,因此只介绍测量方法,不给出看似真实的费用表。

测试问题应覆盖自然问法、缩写、信息缺失、错误前提、知识冲突和多轮修正。固定数据有利于比较,但也要有独立保留集,避免所有调整都针对同一批问题。评测应该帮助发现未知失败,而不仅是证明熟悉的演示仍然成功。

安全边界要落到可执行约束

提示注入可能来自文档、网页、日志或工具错误文本。攻击内容试图把外部资料中的指令提升为应用命令。上下文标记可以帮助区分来源,但真正高影响的动作仍然需要服务端权限校验、受限工具集合和必要的人工授权。

示例把注入样本文档保留为外部数据,并验证执行器不支持任意重启动作。这证明了受测工具边界不会因为文档里出现命令就扩展能力,不能证明真实模型在所有恶意输入下都不会被误导。真实系统还需要用多种攻击方式检查数据泄露和越权尝试。

权限测试应包括跨用户读取、伪造身份参数、访问已撤回资料和使用过期授权。写操作还要检查重复提交和结果未知后的重试。把这些约束放在应用与工具层,可以让模型犯错时仍然受到限制,而不必假设模型永远遵守指令。

资源预算也是运行边界。调用次数、总时间、输出体积和并发数应当有界。任务在达到预算时停止,需要产生可识别状态,不应继续在后台消耗资源。对于长任务,还应提供取消与恢复语义,避免用户以为已经结束而系统仍在执行。

从只读灰度逐步验证

可以先让真实模型只读取经过筛选的数据,输出建议供人对照,而不执行生产写操作。此时观察工具选择、证据质量、错误分布与调用成本,逐步建立实际基线。影子运行也可能接触敏感数据,因此仍需要遵循正常权限与日志规则。

当只读任务达到业务验收要求后,再考虑引入影响较小、可验证且具有幂等保护的写操作。每次扩展都应有独立场景与停止条件。允许创建一条排障记录,不等于已经授权修改服务配置;权限范围需要逐项明确。

灰度应能限制用户、任务类型或流量比例,并保留快速关闭某类能力的方式。出现异常时,可以回退到固定工作流、只读建议或人工处理。降级结果必须准确表达能力变化,不能在工具被关闭后仍显示已经执行了操作。

回滚也需要区分代码与业务数据。恢复旧版本执行器,并不会撤销此前产生的外部动作;恢复旧检查点,甚至可能重复提交已经完成的写入。上线方案应说明哪些内容可以回滚、哪些需要补偿或人工处理,避免用一个部署按钮掩盖业务后果。

Java 后端经验仍然是可靠性的基础

任务标识、状态版本、幂等键、超时传播、审计日志和隔离测试,都是后端系统常见能力。Agent 增加了生成与动态决策的不确定性,但没有让这些基础失效。相反,越无法保证每次决策相同,越需要确定的执行边界。

可以把评测分为应用逻辑回归、模型行为回归和生产运行监控。前者适合常规测试流水线,模型评测需要记录版本与样本,运行监控则关注真实失败与成本。三者互相反馈,但不应该用某一层的通过代替其他层的证据。

这套系列最终交付的是可检查的学习材料与离线案例:读者能够运行工具、看到停止原因、验证记忆隔离和恢复,并理解哪些问题仍需要真实模型与部署环境回答。将已验证与未验证的部分分清楚,本身就是从演示走向可靠工程的重要一步。

保留能够复查的验收记录

验收记录应说明运行环境、依赖版本、样例集合和实际执行命令,并将通过项与未运行项分开。本系列把逐项结果随示例发布,读者不必只相信文章中的总数。若修改代码后重新运行,报告会反映新的状态,而不是继续沿用旧截图。

对于真实模型测试,还应保留失败样例的可重放输入和经过脱敏的轨迹。只有一个平均分无法解释某次危险动作是怎样发生的;能够复查的具体记录,才有助于修正工具边界并验证问题是否再次出现。

参考资料

AI Agent 工程实践:从原理到可靠运行

  1. AI Agent 是如何工作的:从一次模型调用到任务执行闭环
  2. 让 Agent 真正执行任务:Tool Calling 与 MCP 实战
  3. 从知识库问答到 Agentic RAG:让 Agent 找到可靠答案
  4. Agent 的上下文与记忆:如何记住有用信息
  5. 复杂任务如何编排:工作流、单 Agent 与多 Agent 的取舍
  6. Agent 从 Demo 到上线:评测、可观测性与安全边界 · 当前文章

按时间浏览