跳到内容

12.3 工具与 Agent:由宿主程序控制的执行状态机

预言厅接到一个任务:“查出延迟最高的服务,并创建修复工单。”模型正确选中了监控查询工具,却把自然语言时间写进整数参数;重试后查询成功,它又重复创建了两张相同工单。

所谓 Agent,不是获得独立权限的数字员工,而是一个由应用宿主控制的循环:模型提出下一步动作,宿主验证、授权并执行,再把有限结果返回模型。只要动作会产生副作用,可靠性边界就必须由确定性代码承担。

本课目标

  • 把 Agent 实现为有界状态机;
  • 设计 typed tool schema、授权与参数验证;
  • 处理幂等、重试、超时和部分失败;
  • 区分对话历史、工作状态与长期记忆;
  • 评估工具选择、参数、结果和副作用。

1. 模型只能提出调用

Tool calling response 通常包含 tool name 和 arguments。它们都是不可信输入:模型可能编造工具名、漏字段、给错类型,或把文档中的 prompt injection 复制进参数。

工具注册表应定义:

  • 稳定的 tool name、version 和用途;
  • JSON Schema 或等价 typed input/output;
  • required fields、枚举、长度、数值和格式限制;
  • read-only 或 side-effect classification;
  • timeout、rate limit、cost 和 data sensitivity;
  • 调用者所需 permission;
  • retry 与 idempotency contract。

对未知字段默认拒绝,避免模型把未审计参数偷偷传到底层 SDK。Tool output 同样不可信,要限制大小、转义控制内容,并把 data 与 instructions 分开。

2. 权限不会因模型建议而扩大

用户能让 Agent 做什么,取决于当前 principal 和业务政策,不取决于 prompt 写得多坚定。宿主程序应在每次调用前检查:

  • 当前用户/服务身份;
  • tenant、resource scope 与 operation;
  • 数据分类和目的限制;
  • 是否需要二次确认或审批;
  • 是否超过预算、速率或时间窗口。

对“删除、付款、发消息、部署、改权限”等不可逆或外部可见动作,先展示具体目标和影响,让用户确认的是解析后的结构化操作,而不是含糊的自然语言任务。

不要把数据库超级用户凭据或生产部署 token 交给模型上下文。由受限执行器持有凭据,并根据已授权的 tool call 使用。

3. 用状态机表达循环

python
def run_agent(task, principal, registry, limits):
    state = new_run(task=task, principal=principal, limits=limits)

    while state.steps < limits.max_steps:
        if state.deadline_exceeded() or state.cost_exceeded():
            return state.stop("budget_exhausted")

        reply = model.respond(
            messages=state.visible_messages(),
            tools=registry.schemas_for(principal),
        )
        state.record_model_reply(reply)

        if reply.final_answer is not None:
            return state.finish(reply.final_answer)

        calls = registry.parse_and_validate(reply.tool_calls)
        if not calls:
            return state.stop("invalid_or_empty_action")

        for call in dependency_order(calls):
            policy.authorize(principal, call)
            approval.require_if_needed(call, principal)
            result = executor.execute(
                call,
                timeout=registry.timeout(call.name),
                idempotency_key=state.idempotency_key(call),
            )
            state.record_tool_result(call, sanitize(result))

    return state.stop("max_steps_reached")

真实实现还要捕获模型、验证器和工具异常,并把它们归入可恢复、不可恢复和需人工处理的状态。不要无限把错误文本喂回模型重试。

4. 规划是策略,不是权限机制

有些任务适合 model 一步一调用;有些任务需要先产生 plan,再逐步执行;还有些任务可用固定 workflow,只让模型填写局部参数。越稳定、越高风险的流程,越适合确定性 orchestration。

公开展示模型的完整内部“Thought”并不是 Agent 必需条件。系统可以保留结构化 plan、decision summary、tool trace 和可验证依据,而不依赖自由文本 chain-of-thought 作为审计记录。

并行工具调用只适合相互独立的动作。若第二个调用依赖第一个输出,必须建立显式依赖图;否则模型可能使用尚不存在的 ID 或基于失败结果继续执行。

5. Side Effects 需要幂等和补偿

网络超时不代表服务端没有执行。客户端若直接重试“创建工单”,就可能重复产生副作用。

优先设计:

  • 由 run/action identity 派生 idempotency key;
  • 服务端保存 key 与操作结果;
  • 重试返回原结果,而不是再次执行;
  • 为不可幂等接口先查状态,或设计业务去重键;
  • 多步骤事务定义补偿动作和人工接管点;
  • 把“请求已接受”和“业务已完成”分开。

分布式系统通常无法靠一次 HTTP 调用获得普遍的 exactly-once guarantee。应用要在业务语义上处理 at-least-once delivery 和重复结果。

6. 为每条路径设置预算

至少设置:

  • max model steps;
  • max tool calls 与单工具调用次数;
  • token、money 和 wall-clock budget;
  • 单次/总输出大小;
  • query/data scan limits;
  • 重试次数和 backoff;
  • 可访问的工具 allowlist。

终止条件不只是“模型说完成”。还包括目标已由 verifier 满足、无合法动作、重复状态、预算耗尽、用户撤销和不可恢复错误。

检测重复调用时比较规范化 arguments 与相关 state,不要只比较自然语言响应。

7. 三种状态不要混在一起

对话历史

用于理解当前交互。历史过长会带来 token 成本和旧指令污染;summary 是有损压缩,关键约束应保存为结构化字段。

工作状态

包括 task graph、已完成动作、artifact IDs、approvals、预算和错误。它应由应用持久化并校验,不依赖模型“记得”。

长期记忆

跨会话保存用户偏好或事实时,需要 consent、来源、有效期、可更正/删除和访问控制。模型生成的总结不能未经验证就变成长期事实。

8. Error Recovery 要可解释

对失败建立有限策略:

  • schema error:返回具体字段错误,最多修正若干次;
  • auth denied:终止该动作,不能换工具绕过;
  • rate limit/transient error:按 policy backoff;
  • timeout/unknown outcome:先查询操作状态,再决定重试;
  • partial success:记录已完成子步骤,执行补偿或请求人工;
  • unsafe output:隔离内容,不继续传给高权限工具。

每次恢复都要记录触发原因、策略、调用版本和最终状态。Agent“自己想办法”不是恢复策略。

9. 评估完整执行轨迹

Final answer 正确仍可能伴随多余查询、越权尝试或重复副作用。Agent evaluation 应分层:

  • tool selection accuracy;
  • argument schema/semantic correctness;
  • authorization decision;
  • task completion;
  • side-effect correctness 与 duplication rate;
  • recovery success;
  • steps、latency、tokens 和 cost;
  • unsafe action attempt rate;
  • trace 是否足以复盘。

测试集应包含 tool unavailable、malformed output、timeout、权限不足、prompt injection、同名实体和未知执行结果。对真实副作用使用 sandbox/fake tools 或隔离环境。

常见误区

  • Agent 等于会思考的 LLM:产品行为由模型、工具、状态机和政策共同决定。
  • Tool schema 能保证安全:它只解决部分结构验证,不能替代授权和业务约束。
  • 超时后重试即可:原操作可能已经成功,需要幂等键或状态查询。
  • 把全部历史塞回去就是记忆:那会增加成本、污染和隐私风险。
  • 设置 max_steps 就不会失控:还要限制工具、权限、费用、数据和副作用。

练习

  1. 为“创建工单”设计 strict input schema、权限和 idempotency contract。
  2. 画出工具调用从 proposed 到 validated、authorized、approved、executed 的状态迁移。
  3. 为 timeout with unknown outcome 写恢复流程。
  4. 区分一项任务中的对话历史、工作状态和长期记忆。
  5. 构造五条会暴露重复调用或权限绕过的 Agent 测试。

小结

Agent 的核心不是让模型获得更多自主权,而是把不确定的动作建议放进受控执行循环。Typed schema、逐调用授权、显式状态、幂等、预算和轨迹评估共同限定风险;宿主程序始终对实际副作用负责。

下一课进入预言厅的评测台:不再问“回答看起来好不好”,而是定义任务、证据、轨迹、统计单位与发布门槛。

Built with VitePress | Software Systems Atlas