拓冰建站拓冰建站
首页 / 资讯中心 / 正文

为 AI Agent 建立可验证的工具调用测试体系

AI Agent 的难点不只是“能不能生成回答”而是能否在不确定的模型输出下稳定地选择工具、组织参数、处理错误并在执行失败或用户中断时保持业务状态可控。一个调用天气 API 的示例看起来很简单但进入生产环境后工具可能遇到超时、限流、返回字段变化、重复提交、权限不足或下游服务部分成功等情况。传统单元测试通常验证确定性的函数输入和输出而 Agent 测试还要回答几个问题模型是否选择了允许的工具参数是否通过了严格校验高风险操作是否经过审批同一个任务重试时会不会产生重复副作用调用外部模型或中转接口时测试结果是否被服务端模型、路由和随机性影响因此测试对象不应只是模型本身而应是“模型决策、工具边界、执行器和状态记录”组成的完整链路。本文以 Python 为例设计一个不依赖真实下游系统的最小测试框架。示例中的模型客户端可以替换为企业内部服务也可以按 HaerAPI 当前文档支持的接口形式进行适配具体请求路径、模型名称和返回格式必须以实际文档为准。核心原理1. 把工具当成带契约的 API工具不是一段附加在提示词后的函数描述而是一个需要独立治理的接口。至少应固定以下契约工具名称和用途禁止用模糊描述诱导模型扩大权限。参数结构、数据类型、枚举值、长度和必填字段。返回结构以及可重试错误、不可重试错误和业务拒绝的区别。是否具有副作用以及幂等键如何生成。调用者身份、租户范围和允许访问的资源。模型输出只能表达“建议调用什么以及传入什么参数”不能直接获得数据库连接、Shell 或任意网络访问能力。执行器应再次校验工具名称、参数和权限。2. 将测试分成四层第一层是纯函数测试验证参数校验、权限判断、幂等键和错误分类。第二层是工具契约测试使用模拟下游服务确认请求和响应符合约定。第三层是编排测试给定模型返回的工具调用序列验证 Agent 是否正确执行、重试和停止。第四层才是受控的模型评估用固定测试集检查模型是否倾向于选择正确工具。这四层应尽量隔离。模型评估失败不一定代表执行器有缺陷执行器测试失败也不应通过更换提示词掩盖。测试报告需要记录失败发生在哪一层。3. 把副作用放在边界之后建议采用如下调用路径模型输出 - JSON 解析 - 工具名称白名单 - 参数 Schema 校验 - 身份与资源授权 - 幂等键检查 - 审批或人工确认 - 工具执行 - 结果规范化 - 状态与审计记录任何一步失败都应生成结构化事件。不要让异常文本直接回填给模型后继续执行因为模型可能把错误内容误解为新的指令。可执行实现1. 定义工具和错误类型下面的示例使用标准库实现最小边界。生产项目可以替换为成熟的 Schema 校验库但无论使用何种库都应保留执行前的二次校验。fromdataclassesimportdataclassfromtypingimportAny,CallableimporthashlibimportjsonclassToolError(Exception):def__init__(self,code:str,message:str,retryable:boolFalse):super().__init__(message)self.codecode self.retryableretryabledataclass(frozenTrue)classToolSpec:name:strside_effect:boolhandler:Callable[[dict[str,Any]],dict[str,Any]]defmake_idempotency_key(tool:str,args:dict[str,Any])-str:rawjson.dumps({tool:tool,args:args},ensure_asciiFalse,sort_keysTrue,separators(,,:))returnhashlib.sha256(raw.encode(utf-8)).hexdigest()幂等键不能只依赖模型生成的文本因为同一语义可能有不同措辞。应使用规范化后的工具名和参数生成。对于创建订单、发送通知等操作还需要把业务请求号纳入键中否则两个合法但不同的请求可能被错误合并。2. 对模型输出建立严格解析器假设模型只能返回以下结构{type:tool_call,name:create_ticket,arguments:{title:磁盘告警}}解析器应拒绝未知字段、空工具名、非对象参数和无法解析的 JSON。示例代码如下ALLOWED_TOOLS{create_ticket,get_ticket}SCHEMAS{get_ticket:{required:[ticket_id],types:{ticket_id:str}},create_ticket:{required:[title],types:{title:str}},}defparse_tool_call(raw:str)-tuple[str,dict[str,Any]]:try:itemjson.loads(raw)exceptjson.JSONDecodeErrorasexc:raiseToolError(invalid_json,模型输出不是有效 JSON)fromexcifitem.get(type)!tool_call:raiseToolError(invalid_type,输出类型不允许执行工具)nameitem.get(name)argsitem.get(arguments)ifnamenotinALLOWED_TOOLSornotisinstance(args,dict):raiseToolError(invalid_tool_call,工具或参数不在允许范围)schemaSCHEMAS[name]forkeyinschema[required]:ifkeynotinargs:raiseToolError(missing_argument,f缺少参数:{key})forkey,expectedinschema[types].items():ifnotisinstance(args.get(key),expected):raiseToolError(invalid_argument,f参数类型错误:{key})returnname,args这里没有把任意 URL、SQL 或 Shell 字符串作为通用参数开放给模型。若业务确实需要这些能力应再增加资源白名单、语句类型限制、超时和人工审批并单独设计测试集。3. 为执行器注入状态和权限执行器需要知道调用者、租户和当前任务状态。以下代码展示副作用工具的基本处理方式classExecutor:def__init__(self,tools:dict[str,ToolSpec]):self.toolstools self.completed:dict[str,dict[str,Any]]{}defrun(self,raw:str,tenant_id:str,approved:boolFalse):name,argsparse_tool_call(raw)specself.tools[name]ifspec.side_effectandnotapproved:raiseToolError(approval_required,副作用操作需要审批)keymake_idempotency_key(name,{tenant:tenant_id,**args})ifkeyinself.completed:return{status:replayed,result:self.completed[key]}try:resultspec.handler({tenant_id:tenant_id,**args})exceptTimeoutErrorasexc:raiseToolError(downstream_timeout,下游超时,retryableTrue)fromexcexceptPermissionErrorasexc:raiseToolError(downstream_forbidden,下游拒绝访问)fromexcifnotisinstance(result,dict):raiseToolError(invalid_result,工具返回值必须是对象)self.completed[key]resultreturn{status:completed,result:result}真实系统中completed应由持久化存储或具备过期策略的键值存储承担并在写入结果时考虑并发竞争。示例只用于说明接口边界不能直接视为分布式幂等实现。如何编写测试1. 正常路径测试先覆盖最小闭环合法 JSON、合法工具、合法参数、授权通过、工具返回规范结果。测试应断言工具实际收到的参数而不只断言最终自然语言回答。deftest_valid_tool_call():calls[]defget_ticket(args):calls.append(args)return{ticket_id:args[ticket_id],status:open}executorExecutor({get_ticket:ToolSpec(get_ticket,False,get_ticket)})rawjson.dumps({type:tool_call,name:get_ticket,arguments:{ticket_id:T-100}})resultexecutor.run(raw,tenant_idacme)assertresult[status]completedassertcalls[{tenant_id:acme,ticket_id:T-100}]2. 边界和安全测试至少应测试以下情况未知工具、缺少必填参数、类型错误、额外危险字段、跨租户资源 ID、未审批的写操作、重复提交、空响应、非法响应和异常过长响应。对于权限测试不能只更换提示词应直接构造越权参数确认执行器拒绝请求。3. 失败注入测试可以让模拟工具依次抛出超时、限流、认证失败和业务拒绝。断言规则应明确超时是否允许有限次数重试认证失败是否立即停止业务拒绝是否转为人工处理已有成功结果再次收到相同请求时是否返回已完成状态。不要用无限重试解决不稳定。建议给每个任务设置总超时、最大调用次数和最大费用预算。达到任一上限后系统应保存当前状态并返回可恢复的任务标识。4. 模型回归测试准备一组脱敏的用户意图和期望工具标签例如“查询工单状态”对应只读工具“关闭工单”对应写操作并要求审批。测试时固定系统提示词、工具描述、模型参数和输入版本并记录模型原始输出。模型服务的具体随机性控制能力取决于接口实现因此即便设置了低随机参数也不应把单次结果当成绝对保证。当接入外部模型 API 或中转接口时应在客户端增加请求超时、响应截断、请求 ID 和敏感字段脱敏日志。HaerAPI 可作为模型接入选项之一但模型可用性、路由规则、计费和数据处理边界需要以其当前公开文档和实际协议为准不能在测试中预设未确认的能力。常见问题测试是否必须调用真实模型不必。执行器和工具契约测试应使用固定模型输出或模拟客户端这样才能稳定定位问题。真实模型测试适合用于少量回归样本和上线前评估并应设置预算、超时和数据脱敏。为什么工具调用成功最终回答仍然错误工具返回值可能没有经过规范化或者模型没有获得清晰的执行结果。应把工具结果转换为固定结构区分成功、拒绝、可重试失败和不可重试失败并在最终回答前检查任务状态而不是只拼接异常字符串。重试会不会造成重复写入会除非下游和本地执行器共同支持幂等。幂等键需要覆盖租户、业务请求号、工具和规范化参数如果下游不支持幂等应先采用待确认状态、事务外盒或人工补偿机制不能仅依赖客户端重试。如何测试提示词注入在工具描述、用户输入和外部检索内容中加入试图改变权限或调用未知工具的文本观察解析器和授权层是否仍按白名单执行。安全结论必须以执行器拒绝结果为准而不是以模型口头表示“我不会执行”为准。日志应该记录什么建议记录任务 ID、请求 ID、工具名、参数摘要、租户和操作者、审批状态、结果类别、重试次数、耗时和错误码。敏感参数应脱敏或哈希化原始提示词和模型响应是否保存则要依据数据分类、保留期限和合规要求决定。总结AI Agent 的自动化测试重点不是让模型永远输出正确文本而是把不确定性限制在可观测、可拒绝、可恢复的边界内。可落地的做法包括为每个工具建立明确契约对模型输出执行白名单和 Schema 校验在副作用操作前检查权限、审批和幂等键通过模拟下游服务注入超时与拒绝使用固定样本进行模型回归最后把调用链路、状态变化和错误分类写入审计记录。完成这些基础建设后模型供应商或接入方式可以在不改变业务工具边界的前提下替换。真正需要回归的是接口协议、模型行为、延迟与费用、数据处理条款以及企业自身的安全和合规要求。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门