Skip to content

Agent 工程化与安全

目录

1. 学习目标

  • 将 Demo Agent 改造成可恢复、可审计、可限权的生产系统;
  • 理解超时、重试、幂等、检查点和补偿的不同职责;
  • 能设计工具权限、人工批准、输入输出 Guardrail 和敏感数据策略;
  • 能用 Trace、Metric、Log 和业务事件定位 Agent 失败;
  • 能解释提示注入、过度代理、记忆污染和重复副作用的解决方案。

2. 面试结论

2.1 30 秒回答

Agent 工程化的本质,是把模型的不确定性隔离在可控决策层,把状态、权限、预算、幂等、重试、审计和验收放在确定性运行时。生产系统必须假设模型会选错工具、外部调用会超时、回调会重复、进程会重启、输入可能恶意;因此每个副作用都要经过服务端校验和最小权限,每一步都能恢复和追踪。

2.2 一分钟复述版

一个可靠 Agent 至少需要五类保护:第一,执行保护,包括 Schema、白名单、权限、预算和人工确认;第二,可靠性,包括超时、错误分类、幂等、检查点和补偿;第三,状态保护,把工作状态持久化而不是只存在上下文;第四,安全保护,把外部数据视为不可信内容,防止提示注入和敏感信息泄露;第五,可观测和评测,记录模型、Prompt、工具、Guardrail、状态迁移和最终结果。模型可以建议动作,但不能绕过这些运行时规则。

3. 面试官为什么问

  • 是否经历过 Demo 到生产的差距;
  • 是否理解分布式系统中的至少一次执行和副作用风险;
  • 是否能把“安全 Prompt”升级为权限和策略系统;
  • 是否会区分模型错误、工具错误、业务错误和基础设施错误;
  • 是否能建立故障证据,而不是靠重试掩盖问题。

4. 概念与边界

4.0 小白先这样理解:Agent 办事要像网银转账

你在网银输入“给房东转 5000 元”,系统不会因为备注里写着“我已获授权”就放款:它先核验登录身份和收款对象,再展示金额让你复核,用唯一业务流水号防止连点两次;若提交后网络断开,先查银行账本确认成败,而不是盲目再转。这里,身份与账户校验是授权,复核页是人工确认,流水号是幂等键,账本查询是对账,电子回单是审计记录,断点记录是检查点

类比边界: 人工确认不等于权限校验,幂等也不等于跨系统事务或自动回滚;已经成功的外部副作用可能只能补偿。Prompt 和 Guardrail 只能帮助识别风险,真正的权限、状态条件、批准指纹、密钥隔离与执行审计必须由可信服务端强制执行。

4.1 工程化保护面

层次保护对象典型机制
输入用户、检索和外部内容类型校验、注入检测、数据分级
决策模型输出结构化动作、允许列表、预算和策略
工具真实副作用身份鉴权、最小权限、批准、幂等
状态长任务与恢复持久化、版本、检查点、事件历史
输出用户和下游系统Schema、事实引用、安全和合规校验
观测整条执行链Trace、Metric、Log、审计和成本

4.2 Guardrail 不等于权限

Guardrail 可以检查输入、输出或工具参数,但真正的权限必须由工具服务端根据用户身份、资源范围和当前状态重新判断。模型生成的“已获得授权”不能作为授权证据。

4.3 重试、幂等与补偿

  • 重试:再次尝试一个可恢复失败;
  • 幂等:同一逻辑请求执行多次,业务结果仍等同一次;
  • 补偿:无法原子回滚跨系统操作时,执行反向业务动作;
  • 检查点:保存可恢复位置和必要状态;
  • 对账:本地状态不确定时,查询外部系统的真实结果。

重试不能替代幂等,幂等也不能保证跨系统强事务。

4.4 安全边界

  • 系统指令、用户输入、检索内容、工具结果和外部网页必须分层;
  • 外部内容是数据,不是更高优先级指令;
  • 工具令牌和数据库凭据不进入模型上下文;
  • 不可逆动作默认要求显式用户确认;
  • 长期记忆写入需要来源、用途、TTL 和删除能力。

5. 原理剖析

5.1 失败分类

类型示例是否重试正确动作
瞬时基础设施错误网络抖动、临时 5xx有上限地重试指数退避、抖动、对账
限流429、并发配额延迟重试读取 Retry-After、背压
参数错误Schema 不合法不原样重试修复参数或回到模型
权限错误越权、令牌无权限不重试拒绝并审计
业务冲突资源状态已变化先对账重新规划或请求用户
不确定结果外部成功但本地超时不能直接重做用幂等键查询真实状态
安全策略命中注入、敏感数据不重试阻断、降权或人工审核

5.2 幂等键与状态版本

幂等键应代表一次业务意图,而不是一次网络请求:

key=hash(user_id,resource_id,action,normalized_payload,intent_version)

工具执行前写入唯一记录,状态从 PENDING 条件更新到 RUNNING,完成后保存外部结果。收到重复调用时返回已有结果或对账,不能再次制造副作用。

5.3 人工确认

确认请求应展示:

  • 即将执行的动作和对象;
  • 关键参数与预估影响;
  • 是否可撤销;
  • 数据或费用范围;
  • 确认有效期和动作指纹。

批准必须绑定动作指纹。模型在用户批准后修改参数,应重新确认。

5.4 状态持久化与恢复

模型上下文不是可靠状态库。恢复至少需要:

  • 工作流 ID、当前步骤和状态版本;
  • 已完成工具调用及幂等键;
  • 待确认动作和确认结果;
  • 预算消耗、重试次数和终止原因;
  • 必要输入摘要及原始证据位置;
  • 模型、Prompt、工具和策略版本。

5.5 可观测性

  • Trace:一次 Agent Run 的模型调用、工具、Handoff、Guardrail 和自定义步骤;
  • Metric:成功率、阶段耗时、工具错误率、循环步数、Token 和成本;
  • Log:结构化状态迁移、错误和审计事件;
  • Artifact:模型输入输出、检索证据、工具响应和评测结果的受控存档。

不要把 user_idrun_id 等高基数字段无限制写入指标标签;它们适合 Trace 和日志检索。

5.6 可视化辅助

工程化保护面不依赖某一个 Agent 框架。本文选择的参考栈把授权、幂等和观测放在模型之外;真实项目可替换具体策略引擎、数据库和采集后端,但不能删除这些职责。

技术清单

技术点 ID技术点/环节类型采用方案链路职责版本/证据边界
TP-SEC-POLICY工具授权与策略服务/策略组件服务端 RBAC/ABAC;复杂组织可接 OPA 或 Cedar基于真实身份、资源、动作和环境重新授权,不信任模型声称产品语法与能力随版本变化;本文只确认授权必须在可信执行边界完成
TP-SEC-IDEMPOTENCY幂等、状态与对账存储/中间件关系数据库唯一业务意图键 + 状态版本 + Outbox/执行记录原子占位、阻止并发重复副作用,并保存外部任务 ID 与不确定态不能承诺跨供应商 exactly-once;必须结合供应商查询、账单和补偿能力
TP-SEC-OBSERVABILITYTrace、Metric、Log 与审计可观测组件OpenTelemetry Trace + 结构化审计事件 + 低基数 Metric关联模型、Tool Call、批准、状态迁移、错误、版本和费用OTel 负责传播与采集语义,不自动解决敏感信息脱敏、留存和告警质量

横向选型对比

技术点 ID候选方案优点缺点/代价适用场景不适用场景选择结论与依据
TP-SEC-POLICY业务服务内 RBAC/ABAC距离业务状态近,调试和强制执行直接多服务容易复制规则并产生漂移服务数量少、资源模型稳定、团队边界清晰多语言多服务且策略需要统一审计默认起点;权限测试必须覆盖资源级越权和状态变化
TP-SEC-POLICYOPA 或 Cedar 等独立策略引擎策略集中、可版本化并支持跨服务复用引入策略语言、分发、一致性和故障降级成本多租户、多服务、合规审计和统一治理单体小系统或团队无策略运维能力复杂组织候选;以决策延迟、可用性和策略回归集验收
TP-SEC-IDEMPOTENCY关系数据库唯一键 + 状态机事务、条件更新、对账与审计证据完整热点写入、清理和 Schema 演进有成本支付、消息、部署等需要恢复的副作用极高吞吐且允许最终一致去重的短事件默认选择;业务意图、结果和状态放在同一可信事实源
TP-SEC-IDEMPOTENCYRedis SET NX + TTL延迟低、实现轻量、适合短窗口抑制重复TTL 过期、故障切换和持久化边界可能导致重复可容忍少量重复、短时间防抖和限流财务、删除、跨小时恢复和强审计场景只作前置去重或加速层,不作为高风险最终账本
TP-SEC-OBSERVABILITYOpenTelemetry + 审计事件跨模型、工具和服务统一 Trace,上下文传播标准化采样、存储、脱敏和成本治理复杂多阶段 Agent、异步 Worker、需要版本归因极简本地实验或无后端采集环境生产默认候选;高风险动作额外保留不可抵赖审计记录
TP-SEC-OBSERVABILITY仅应用日志接入快、排查单进程错误方便跨服务关联弱,指标与调用关系需人工拼接单进程原型和早期故障定位多租户、异步链路、发布归因和合规审计只用于最小原型,不能满足生产 Agent 全链路证据需求

图:架构|受控 Agent 的安全、状态与观测组件边界

替代文本: 用户身份和请求先进入 Agent Runtime;Runtime 调用模型取得候选动作,但工具动作必须依次经过 Schema、服务端策略与高风险人工批准。幂等状态库在执行前原子占位,工具网关使用短期凭据访问外部系统;OpenTelemetry 和审计存储旁路记录模型、批准、状态与结果,恢复器针对超时不确定态发起对账。

图表加载中…

读图结论: 模型、策略、状态、执行和观测必须分层;服务端授权与幂等账本决定动作能否安全发生,Prompt Guardrail 只能提供额外信号。

架构中的策略引擎、状态库和观测后端都可替换,但真实身份、动作指纹和外部结果不能只保存在模型上下文中。

图:技术调用流程|高风险工具调用的批准、执行与不确定态对账

替代文本: Agent Runtime 收到模型动作后先请求服务端策略;策略拒绝则记录并结束该动作,高风险动作需用户批准。通过后状态库以幂等键原子占位:重复请求返回已有状态,新请求才调用外部工具。成功时持久化结果;超时不确定时先按外部任务 ID 对账,确认未执行才安全重试,已执行则回填成功,无法确认则转人工处理。

图表加载中…

读图结论: 工具超时不是“直接再调用一次”的信号;安全链路必须先冻结重复提交并对账,再根据权威状态决定成功、重试或人工补偿。

该时序把成功、明确失败、策略拒绝、用户拒绝和结果不确定分开,便于为每一类结果配置不同告警、Runbook 和回归测试。

图:高风险工具调用的可靠执行路径

图表加载中…

替代文本: 工具调用先经策略和人工确认,执行不确定时先对账,再决定返回成功或重试。

读图结论: 高风险 Agent 的关键路径不是“调用—失败—重试”,而是“校验—授权—幂等执行—不确定结果对账”。

6. 实现与代码

6.1 最小可运行受控执行器

下面只演示进程内的控制模式:认证上下文不来自模型动作,批准绑定完整动作指纹,资源级授权发生在工具前,并用原子占位阻止同一调用并发执行。它不是数据库或外部供应商的 exactly-once 实现。

python
from dataclasses import dataclass
from hashlib import sha256
import json
from threading import Lock
from typing import Any, Callable


@dataclass(frozen=True)
class AuthContext:
    # 真实系统只能由认证中间件根据已验证令牌创建,模型不能填写这些字段。
    actor_id: str
    roles: frozenset[str]
    allowed_profile_ids: frozenset[str]


@dataclass(frozen=True)
class Action:
    # call_id 由服务端按一次业务意图签发。
    call_id: str
    tool: str
    args: dict[str, Any]


@dataclass(frozen=True)
class Approval:
    call_id: str
    actor_id: str
    action_fingerprint: str


@dataclass(frozen=True)
class ToolPolicy:
    allowed_roles: frozenset[str]
    needs_approval: bool
    authorize: Callable[[AuthContext, dict[str, Any]], bool]
    handler: Callable[..., Any]


@dataclass
class ExecutionRecord:
    action_fingerprint: str
    status: str
    result: Any = None


PROFILES = {"u-1": "active"}


def update_profile_status(user_id: str, status: str) -> dict[str, str]:
    if status not in {"active", "suspended"}:
        raise ValueError("unsupported status")
    PROFILES[user_id] = status
    return {"user_id": user_id, "status": status}


def can_manage_profile(auth: AuthContext, args: dict[str, Any]) -> bool:
    return (
        "admin" in auth.roles
        and args.get("user_id") in auth.allowed_profile_ids
    )


POLICIES = {
    "update_profile_status": ToolPolicy(
        allowed_roles=frozenset({"admin"}),
        needs_approval=True,
        authorize=can_manage_profile,
        handler=update_profile_status,
    )
}

APPROVALS: dict[str, Approval] = {}  # 仅由可信人工批准接口写入
RECORDS: dict[str, ExecutionRecord] = {}
RECORDS_LOCK = Lock()


def fingerprint(auth: AuthContext, action: Action) -> str:
    canonical = json.dumps(
        {
            "actor_id": auth.actor_id,
            "call_id": action.call_id,
            "tool": action.tool,
            "args": action.args,
        },
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    )
    return sha256(canonical.encode("utf-8")).hexdigest()


def record_human_approval(auth: AuthContext, action: Action) -> Approval:
    approval = Approval(action.call_id, auth.actor_id, fingerprint(auth, action))
    with RECORDS_LOCK:
        APPROVALS[action.call_id] = approval
    return approval


def execute(auth: AuthContext, action: Action) -> Any:
    policy = POLICIES.get(action.tool)
    if policy is None:
        raise PermissionError("tool is not allow-listed")
    if not (auth.roles & policy.allowed_roles):
        raise PermissionError("trusted role is not allowed")
    if not policy.authorize(auth, action.args):
        raise PermissionError("resource is outside actor scope")

    action_fingerprint = fingerprint(auth, action)

    # 结果命中、批准消费和执行占位使用同一临界区。
    with RECORDS_LOCK:
        record = RECORDS.get(action.call_id)
        if record is not None:
            if record.action_fingerprint != action_fingerprint:
                raise ValueError("call_id reused with different intent")
            if record.status == "SUCCEEDED":
                return record.result
            raise RuntimeError(
                f"execution status={record.status}; reconcile instead of resubmitting"
            )
        if policy.needs_approval:
            approval = APPROVALS.get(action.call_id)
            if (
                approval is None
                or approval.call_id != action.call_id
                or approval.actor_id != auth.actor_id
                or approval.action_fingerprint != action_fingerprint
            ):
                raise PermissionError("approval does not match exact action")
            del APPROVALS[action.call_id]  # 一次性批准与执行占位原子绑定
        RECORDS[action.call_id] = ExecutionRecord(
            action_fingerprint=action_fingerprint,
            status="RUNNING",
        )

    try:
        result = policy.handler(**action.args)
    except Exception:
        # 不知道外部副作用是否已经发生时,禁止自动重新提交。
        with RECORDS_LOCK:
            RECORDS[action.call_id].status = "SUBMISSION_UNKNOWN"
        raise

    with RECORDS_LOCK:
        RECORDS[action.call_id].status = "SUCCEEDED"
        RECORDS[action.call_id].result = result
    return result


if __name__ == "__main__":
    auth = AuthContext("admin-1", frozenset({"admin"}), frozenset({"u-1"}))
    action = Action(
        "intent-001",
        "update_profile_status",
        {"user_id": "u-1", "status": "suspended"},
    )
    record_human_approval(auth, action)
    print(execute(auth, action))
    print(execute(auth, action))  # 命中同一成功结果,不重复副作用

6.2 生产差距

  • 示例的认证上下文是测试夹具;生产中必须由认证中间件从已验证令牌和授权服务构造,绝不能接受模型或请求体自报角色;
  • 示例使用进程内锁和字典;生产中要先用数据库唯一约束原子插入 RUNNING 占位,再提交事务,不能持有内存锁或数据库锁跨越慢外部调用;
  • call_id 应由服务端根据业务意图签发,并与资源版本或业务唯一键共同防止攻击者换一个 ID 重复同一动作;
  • 命中幂等结果前仍要鉴权,并校验调用者、动作和规范化参数指纹完全一致;
  • 参数需要 JSON Schema/Pydantic 校验;
  • 外部供应商优先使用原生幂等 Token;若提交结果不确定,保存 SUBMISSION_UNKNOWN,按供应商任务 ID/客户端键对账,禁止盲目重提;
  • record_human_approval 只是测试夹具,生产批准库只能由可信人审接口写入;批准应持久化并绑定 call_id、用户身份、完整动作指纹、策略/资源版本、有效期和签名,并在创建执行占位时原子消费;
  • 不同 call_id 并发修改同一资源仍需乐观锁、条件更新或业务串行化;
  • 审计日志与普通调试日志分开保存。

6.3 必测场景

  • 重复请求和重复回调;
  • 服务在外部成功、本地落库前崩溃;
  • 批准后参数被替换;
  • 只读用户请求写工具;
  • 外部检索内容包含“忽略系统指令”;
  • Trace 关闭敏感输入后仍能定位错误;
  • 达到步数、时间或费用上限时可靠终止。

7. 实际项目案例

7.1 示例项目:AI 面试教练工程化

Agent 会读取候选人回答、检索知识、评分并生成追问。虽然大部分工具只读,仍存在评分漂移、记忆污染、提示注入和无限追问风险。

7.2 关键设计

  • 题库读取、知识检索为只读工具;
  • 评分采用固定量表和结构化输出,不直接把模型分数写为最终事实;
  • 用户回答与检索文本标记来源,不能覆盖系统评分规则;
  • 每轮保存主题、问题指纹、证据、评分维度和终止原因;
  • 最大轮次、单主题预算和退出指令由运行时控制;
  • 长期薄弱点记忆需要用户范围隔离、TTL 和删除入口。

7.3 技术难点

该项目仍是设计方案。下列内容用于说明工程难点和验证方法,不代表已经发生过真实事故,也不声明没有实测证据的效果提升。

技术难点为什么难关键约束验证方式
把概率决策限制在确定性运行时内模型输出会漂移,不能依赖它始终遵守权限、预算和终止条件模型只提出结构化动作;Schema、策略、鉴权、预算和状态迁移由代码执行对同一意图生成多种合法/非法动作,确认非法参数和越权动作始终被运行时拒绝
至少一次执行下避免重复副作用网络超时、Worker 重启和重复回调会让“是否执行成功”变得不确定幂等键表达业务意图;执行状态原子持久化;结果不确定时先对账注入重复请求、响应丢失和进程崩溃,确认外部业务结果至多产生一次
人工批准与最终动作保持一致用户批准后模型可能改写对象、金额或其他关键参数批准绑定用户、动作指纹、策略/资源版本和有效期;参数变化必须重新批准批准后篡改任一关键字段,确认执行器拒绝旧批准
长任务恢复时保持状态一致上下文不是事务日志,重启后可能遗失步骤、重复调用或越过确认持久化状态机、事件历史、检查点、重试预算和幂等记录在模型前后、工具提交前后和落库前后注入崩溃,逐点验证恢复路径
可观测性与隐私同时成立完整 Trace 有利排障,却可能保存用户回答、检索内容或工具秘密内容最小化、分级脱敏、受控 Artifact、审计访问和保留期限关闭敏感原文后仍可凭 run_id、版本、状态和错误码定位故障;越权读取审计用例被拒绝

7.4 方案亮点

方案亮点要解决的问题关键决策验证证据或方式适用边界
“模型建议、运行时裁决”双层架构Prompt 约束无法成为真实安全边界模型只产生候选动作,服务端重新做允许列表、资源级鉴权、预算和业务状态校验越权参数、伪造身份、未批准动作和策略升级后的旧动作均被执行器拒绝并留下审计事件运行时策略必须独立于模型上下文;只读低风险操作也不能跳过基础鉴权
业务意图幂等与不确定状态对账工具超时后盲目重试可能重复制造副作用服务端签发意图 ID,原子占位;超时进入 SUBMISSION_UNKNOWN,查询外部真实状态后再推进重复请求返回同一结果;响应丢失测试中先对账而非再次提交外部系统最好支持原生幂等键或按业务键查询;无法对账的不可逆动作需要更强人工控制
动作指纹绑定人工确认模糊确认无法证明用户批准了最终参数确认展示完整影响并绑定规范化动作指纹、用户、有效期和版本参数替换、确认过期、跨用户复用和重复消费测试全部失败关闭高频低风险动作可按策略降低确认频次,但风险分级必须由确定性规则管理
事件化状态与可恢复检查点进程内对话状态无法支撑长任务恢复与审计持久化每次状态迁移、工具结果、预算、终止原因和版本,恢复时从最近安全点继续崩溃注入后能区分已完成、待对账和可重试步骤,不重复越过人工确认需要数据库并发控制、事件顺序和 Schema 演进;不能把事件日志误当无限期原文存档
外部内容与指令隔离检索文档或网页可能通过间接注入操纵工具调用外部内容标记为不可信数据;工具权限、参数与输出由服务端策略校验恶意文档、混淆指令和越权资源测试无法触发高风险工具或读取未授权数据内容检测只能降低风险,最终边界仍是最小权限和人工批准

7.5 生产问题闭环

下表是生产风险演练与排障模板。每项都按“现象 → 影响 → 证据 → 根因 → 止损 → 修复 → 验证 → 防复发”闭环记录,不应包装成真实项目事故。

现象影响定位证据可能根因止损修复验证防复发
工具返回超时,但外部动作可能已成功直接重试可能重复扣费、发消息或修改资源run_id、call_id、动作指纹、外部任务 ID、提交时间和网络错误响应丢失;本地在外部成功后未落库将状态置为 SUBMISSION_UNKNOWN,暂停自动重提并限制同资源后续写入按幂等键或外部任务 ID 对账,再原子推进为成功、失败或可重试注入“外部成功、响应丢失”,确认外部结果仅出现一次且本地最终收敛所有副作用工具提供业务幂等键、对账接口、不确定状态告警和恢复演练
Webhook 重复或乱序到达重复推进、状态回退或补偿被多次执行event_id、业务版本、到达顺序、处理记录和状态变更日志至少一次投递;缺唯一约束和条件更新暂停冲突资源消费,将异常事件隔离到待审队列event_id 去重,按资源版本条件更新;乱序事件重放或忽略重复、乱序和并发回调测试后业务状态唯一且单调合法唯一约束、状态机合法迁移、死信队列和冲突率告警
用户读取到其他用户的长期记忆隐私泄露并污染后续决策认证主体、tenant/user scope、查询条件、缓存键、返回记录和审计日志作用域过滤缺失;缓存未隔离;模型提供了不可信 user_id关闭相关记忆读取/缓存,撤销访问并启动安全审计服务端注入主体范围,执行行级授权,缓存绑定权限范围并支持删除跨用户、同租户不同权限、撤权和删除回归全部通过权限测试进入发布门禁;异常跨主体命中告警;定期审计数据保留与删除
检索文档诱导 Agent 调用高风险工具越权操作、敏感数据泄露或错误业务动作文档来源、模型决策、候选动作、Guardrail/策略命中和工具审计外部内容被当成系统指令;服务端工具策略过宽禁用受影响高风险工具或强制人工批准,隔离恶意来源内容与指令分层,收紧允许列表和资源权限,关键参数由可信数据源填写直接/间接注入语料测试无法绕过服务端策略恶意样本回归、最小权限审查、来源信誉和高风险调用告警
Agent 持续循环同类工具或追问请求长时间不结束,Token、费用和队列占用增长步数、工具指纹、状态变化、预算、无进展次数和终止原因缺最大步数/时间预算;无重复动作与无进展检测取消当前 Run、限流相关入口并释放占用资源增加总预算、动作去重、进展判定和明确终止状态长对话、同参工具循环和依赖持续失败测试能在预算内终止循环步数、无进展次数、预算耗尽率告警和回归样本
进程重启后状态丢失或重复执行已完成步骤任务卡住、结果丢失或副作用重复状态表、事件历史、检查点、幂等记录、Worker 日志和外部结果状态只在内存;状态与工具结果非原子记录;恢复逻辑缺失暂停自动恢复,对待定步骤逐一对账持久化状态机和检查点,按幂等记录恢复,跨系统结果使用对账或补偿在关键边界注入崩溃,重启后任务收敛且不越过确认定期恢复演练、卡住状态告警、状态迁移不变量和补偿测试
同一答案评分波动或持续追问同一角度反馈失真,面试流程难以稳定验收rubric/prompt/model 版本、采样配置、问题指纹和覆盖矩阵量表模糊、随机性高、问题目标与去重缺失暂停写入长期能力结论,回退稳定量表和轮次上限结构化 rubric、示例锚点、低随机性、问题指纹和覆盖约束固定样本重复评测与长对话回归,检查分歧和主题覆盖评分版本化、漂移抽检、重复问题率和异常轮次告警

7.6 可复用经验卡

经验卡核心规则固化动作验收证据适用边界
信任边界落在工具服务端模型可以建议,不能授权自己认证上下文由中间件注入;工具前重新鉴权、校验参数和业务状态伪造身份或 Prompt 注入后仍无法访问未授权资源Guardrail 是补充,不替代 IAM、行级权限和业务校验
超时不等于失败未收到响应不代表外部未执行显式 SUBMISSION_UNKNOWN、停止盲重试、按外部标识对账响应丢失和恢复测试中不产生重复副作用只读且真正幂等的调用可按受控策略直接重试
幂等键代表业务意图网络请求 ID 无法阻止换 ID 重复同一动作用用户、资源、动作、规范化参数和意图版本构造唯一意图重复请求、并发请求和重启后重放返回同一业务结果随机生成任务需要区分“重试同一意图”和“用户明确重新生成”
参数变化就使旧批准失效人工批准的是具体影响,不是无限授权批准绑定动作指纹、身份、资源/策略版本和有效期,执行时原子消费任一关键参数变化、过期或跨用户复用都被拒绝批量批准需要明确范围、上限和可审计的展开规则
每种不确定性都进入显式状态模糊异常会诱发错误自动恢复为待批准、执行中、结果未知、待补偿和终止原因定义状态与合法迁移Trace 和状态表能解释每次恢复决策,不存在静默跳步状态数量要服务于恢复和审计,避免无业务意义的过度细分
可观测与隐私一起设计事后补日志常导致“不可排障”或“过度采集”先定义诊断问题,再选择脱敏字段、Artifact 权限、采样与保留期不保存秘密也能定位版本、步骤和错误;敏感内容访问可审计高风险审计可能要求更长保留期,应由合规和业务共同确定

8. 方案权衡与常见误区

8.1 Guardrail 的时机

  • 输入 Guardrail:适合阻断明显违规请求,但不能理解所有外部数据影响;
  • 工具 Guardrail:靠近副作用,适合参数和结果检查;
  • 输出 Guardrail:检查最终回答,但不能撤销已经执行的工具副作用;
  • 服务端权限:独立于模型,是最终授权边界。

8.2 常见误区

  • 只在 System Prompt 中写“不要泄露秘密”;
  • 所有错误都自动重试;
  • 把模型生成的用户 ID、金额或资源 ID 当可信参数;
  • 记录完整 Trace,却没有敏感数据控制;
  • 用内存对话保存长任务状态;
  • 认为人工确认后后续所有参数都自动获得授权。

8.3 成本与延迟权衡

  • 每步评审模型会增加延迟,可只对高风险动作启用;
  • 全量 Trace 有利排障,但需采样、脱敏和保留期限;
  • 多 Agent 隔离职责会增加调用和上下文成本;
  • 严格去重与缓存提高稳定性,但随机创作任务需要保留“重新生成”语义。

9. 面试题与参考答案

9.1 为什么 Agent 工具调用必须幂等?

  • 合格答案:网络、Worker 和回调通常是至少一次执行,重试可能重复副作用;
  • 加分项:说明业务意图幂等键、唯一约束、外部任务 ID 和对账;
  • 常见错误:认为 HTTP 重试库能自动保证幂等。

9.2 Prompt Injection 和越权有什么区别?

  • 合格答案:注入试图操纵模型决策,越权是执行系统未正确限制资源或动作;
  • 加分项:说明即使模型被注入,最小权限仍应阻止真实危害;
  • 常见错误:只通过关键词过滤解决两者。

9.3 工具超时后为什么不能直接重试?

  • 合格答案:外部可能已成功,只是响应丢失;直接重试会重复副作用;
  • 加分项:使用幂等键查询或对账,再决定重试;
  • 常见错误:把超时等同于未执行。

9.4 如何设计人工确认?

  • 合格答案:展示动作、对象、关键参数、影响和可撤销性,批准绑定动作指纹与有效期;
  • 加分项:参数改变重新确认,确认事件可审计;
  • 常见错误:只弹一个“是否继续”而不展示内容。

9.5 Agent 可观测性至少记录什么?

  • 合格答案:模型调用、工具、Guardrail、Handoff、状态、错误、Token、成本和最终结果;
  • 加分项:敏感数据开关、高基数处理和回归评测关联;
  • 常见错误:只记录最终回答。

10. 递进追问

  1. 基础概念:重试、幂等、补偿和对账分别解决什么问题?
  2. 原理细节:为什么“模型先判断是否有权限”不是安全边界?
  3. 实现边界:如何处理同一 Webhook 的重复和乱序到达?
  4. 工程权衡:全量保存模型输入输出有哪些隐私和成本问题?
  5. 系统设计:如何给一百个租户隔离工具权限、预算和长期记忆?
  6. 项目复盘:如果 Agent 误删数据,应该如何从事件、审批和工具日志定位责任链?

11. 实践任务

  • [ ] 为只读、可逆写入和不可逆写入设计三级工具策略;
  • [ ] 使用数据库唯一约束替换示例内存幂等表;
  • [ ] 模拟“外部成功、内部超时”,实现查询对账;
  • [ ] 加入动作指纹绑定的人工确认;
  • [ ] 设计一组直接和间接 Prompt Injection 测试;
  • [ ] 输出一次 Agent Run 的 Trace、Metric、Log 字段清单。
  • [ ] 将一次故障注入按“现象 → 影响 → 证据 → 根因 → 止损 → 修复 → 验证 → 防复发”整理成复盘卡。

12. 相关知识与参考资料

12.1 相关知识

12.2 一手参考资料

以下资料于 2026-07-10 核对:

13. 简明总结

一句话记忆: 生产 Agent 要默认模型、网络和外部系统都会出错,再用确定性运行时限制损失。

  • 权限、预算、幂等和终止条件必须由代码掌握;
  • 超时不代表未执行,不确定结果应先对账;
  • Guardrail 辅助模型安全,服务端最小权限才是最终边界;
  • 状态持久化和全链路观测决定系统能否恢复与排障;
  • 每个高风险动作都应可解释、可确认、可审计,生产问题必须以证据、验证和防复发闭环。