跳到主要内容

智能体流量不等同于人类流量:为两类调用者设计 API

· 阅读需 13 分钟
Tian Pan
Software Engineer

你两年前发布的 API 是为单一类别的调用者设计的:浏览器或移动客户端背后的人,点击一次,然后等待响应。现在,大约一半的关键端点上,这个假设都是错误的。另一半流量是智能体(Agents)——你自己的、你客户的,或者是将你的端点作为工具使用的第三方集成——它们具有不同的运行逻辑。它们会产生爆发式流量。它们会无限重试。它们会并行处理。它们会逐字解析错误字符串。它们代表人类行事,而当出现问题时,人类无法即时提供意图说明。

今年出现在复盘报告(postmortems)中的大多数生产环境异常,都可以追溯到一个架构错误:将这两类调用者视为同一种类别。为人类步调设置的频率限制(Rate limits)会被智能体的并行扇出瞬间击穿。为人类可读而设计的错误消息,会被一个在 400 错误上无限重试的智能体解析错误。人类默认会满足的幂等性假设,在智能体从恢复的检查点重试相同的负载时会被打破。身份验证日志失去了区分“用户执行了此操作”与“用户的智能体代表用户执行了此操作”的能力。

解决方法不是更智能的 WAF 或更大的频率限制桶。而是一种深思熟虑的 API 设计,它定义了两类调用者,将它们的流量视为不同的形态,并记录委托链,以便在间接层级中保持可追溯性。

这两类调用者具有不同的流量特征

人类会话是对 UI 的一种缓慢、稀疏且基本连贯的操作过程。每秒两到三次的请求频率就已经是高级用户了。错误会被阅读;重试是手动的;下一个请求通常以用户可以表述的方式依赖于上一个请求。爆发式流量来自页面加载或分页,而非自主性行为。

智能体会话则完全相反。一个单一的自主任务可以在几秒钟内串联十到二十个顺序或并行的 API 调用——工具查找、检索增强生成(RAG)查询、权限检查、写入、验证读取、后续补充——所有这些都在几秒钟内完成。当智能体决定需要补充五十个条目的列表时,你会在同一时间窗口内收到五十个并行调用。当智能体中断并恢复时,你可能会收到带有相同负载的相同调用,因为它的检查点没有记录完成情况。

这不是智能体的缺陷。这是智能体在履行其职责。但强制执行“每个 API 密钥每分钟 100 个请求”的网关是针对人类步调设计的,现在它将合法的智能体工作流视为滥用。过去一年的行业数据表明,在许多公共端点上,AI 驱动的流量已经跨过了 50% 的界限,相当比例的组织承认他们对非人类流量基本上是盲目的——他们无法分辨哪些调用者是机器人,更不用说该机器人是他们自己的还是别人的了。

第一步设计举措是停止假装这两个分布是重叠的。它们并不重叠。智能体的“爆发后沉寂”形态与人类的“稳定滴流”形态并不是围绕同一均值的噪声;它们是不同的分布,而单一的频率限制策略只是将两个你应该分开衡量的事物进行了平均。

调用者类别识别应在网关层完成

在任何其他规范落地之前,网关必须知道是哪类调用者在发起请求。成本最低的机制是类型化凭据:API 密钥或服务令牌在签发时被打上 caller_classhumanagentservice 的标签,网关在每次请求时读取该标签。一种稍复杂的机制是 OAuth claim——现代提供商已经区分了用户主体(user principals)和服务主体(service principals),你可以将这种区分提升到 Header 中,以便你的下游服务在无需重新验证的情况下进行读取。

这种设计的严谨版本还捕获了第三个维度:智能体在代表 行事。代表内部任务自主运行的智能体,与代表特定客户运行的智能体,并不是同一种调用者。这种情况的 OAuth 惯用法是“代表(on-behalf-of, OBO)”流,而与审计相关的事实是 (agent_id, human_principal_id) 这一对信息,而不是其中任何单独的一个。一个标明“这是智能体 X”的令牌只是答案的一半;另一半是“由用户 Y 在 Z 时间以权限 S 委托”。

几个实践建议:

  • 每次内部调用的调用者类别 Header。 网关从凭据中提取类别,并将 X-Caller-Class(或等效)Header 转发给下游服务。内部服务永远不应该为了解它们正在服务的调用者形态而重新解析原始令牌。
  • 可进行加密验证的智能体凭据。 2026 年的现实是,User-Agent 字符串会被大规模伪造。最近的报告显示,很大一部分声称是知名 AI 智能体的流量并非源自这些运营商的基础设施。如果你的信任决策取决于“这是否真的是 ChatGPT”,你需要的是签名凭据,而不是字符串匹配。
  • 预配和轮换至关重要。 智能体凭据需要能够在无需人工干预的情况下进行签发、范围限定和轮换。由操作人员从 UI 生成密钥的工作流无法支撑成千上万个智能体组成的集群。

速率限制、幂等性与错误封装 —— 为爆发与重试模式而设计

一旦调用者类别变得清晰,三个策略层面就显现出其意义。

基于消耗而非请求量的速率限制。 当请求成本低廉且大致统一时,“每分钟请求数”是衡量成本的替代指标。但一个访问单一检索端点的代理,其计算成本可能比人类在同一路径上的 GET 请求高出几个数量级,因为检索调用会扇出(fan out)到嵌入模型、向量数据库、重排器和生成步骤中。基于 Token 的限制(统计输入 Token、输出 Token 或计算单元)能追踪实际的资源消耗。将其与明确允许代理扇出模式的爆发容量(burst capacity)相结合:持续限制与短窗口爆发限制并不是同一种策略。

幂等性是必须而非可选。 人类通过偶然行为实现幂等 —— 他们看到加载动画,然后等待,不会双击。代理只有在协议强制要求时才能实现幂等。代理调用接口中的每个变更(mutating)端点都应要求提供 Idempotency-Key 请求头,在首次执行时存储请求哈希和响应,并在遇到重复 Key 时重放存储的响应。其成本只是键值存储中一个具有短 TTL 的行;而收益是代理在恢复检查点时不会悄无声息地创建两次相同的订单。如果你想保持人类接口不变,可以对 caller_class=agent 设置为必填,对 caller_class=human 设置为可选。

代理可处理的错误响应。 返回 400 状态码并带有“我们无法处理此请求,请稍后重试”字符串正文的响应是一个陷阱。代理会一遍又一遍地重试,因为文字描述暗示这是暂时的,但状态码却在说“你的请求有误”。RFC 9457(HTTP API 的问题详情)定义了一个结构化的封装 —— typetitlestatusdetail,以及扩展字段 —— 代理无需接入 LLM 即可对其进行解析。最低限度的升级是在响应中放入机器可操作的下一步:类型化错误代码(invalid_idempotency_keyrate_limited_burst_windowrequires_user_consent),如果是真正的暂时性错误则提供 retry_after,如果不是则提供 retry: false。一个知道“这是永久性错误,不要重试”的代理将不再挥霍你的错误预算。

retry-after 请求头值得单独拿出来说。当你设置它时,遵循规范的代理会遵守它,从而平息你的请求风暴。当你不设置时,代理会自行选择退避(backoff)策略,来自一千个调用者的惊群效应重试将演变成一场自找的 DDoS。代理端的抖动(Jitter)有所帮助;但从服务器端发送明确的 retry-after 才是让整个系统趋于收敛的关键。

审计与可观测性:委托链必须完整保留

代理可观测性的难点不在于“记录更多日志”,而在于“记录足够的信息以重建谁做了什么决定”。对于仅限人类使用的 API,审计日志回答一个问题:哪个用户做了这件事?对于由代理介入的 API,它必须回答三个问题:哪个用户授权了这件事,哪个代理代表用户行事,以及哪个模型和提示词(prompt)产出了决定。

一个代理驱动变更的实际审计记录看起来如下:

  • actor.human_principal_id —— 正在行使权限的用户
  • actor.agent_id —— 发起调用的代理身份
  • actor.delegation_id —— 将两者关联在一起的 OBO 授权或会话,包含范围(scope)和有效期
  • actor.model_versionactor.prompt_id —— 实际进行决策的内容(应是冻结版本,而非别名)
  • request.idempotency_key, request.parent_trace_id —— 用于重放和追溯
  • decision.policy_version —— 批准此操作的授权策略版本

当六周后出现问题时,你希望能够回答“是用户点击了那个按钮,还是代理根据注入了提示词的文档做出的决定”,唯一的回答方式就是将这两者都记录下来。当前针对代理系统的审计日志指南正趋向于这种形式:触发者身份(发起工作流运行的人员)是追溯的锚点,下游的每个动作都引用其委托谱系,以便审计员可以追溯到原始委托人。

可观测性仪表盘也必须进行同样的拆分。将人类和代理流量平均化的延迟 SLO 对两者都失去了参考价值。如果不了解代理流量天生具有爆发性,异常检测将在每一次合法工作流运行时报警。最低限度的有用拆分是设置两个顶级仪表盘 —— 每个调用者类别一个 —— 并保留现有的各端点基数。大多数团队发现这种拆分是零成本的;他们已经在发送调用者类别标签了,只是之前没有按其分组。

代理是新型的内部攻击者

被提示词注入攻击(prompt injection)窃取的代理并非“困惑的用户”。它是一个拥有用户全部权限、执行用户授权工具、持有用户委托凭证的内部攻击者。OWASP 2025 榜单将提示词注入排在首位是有原因的:它出现在大多数经过审计的 LLM 部署中,而代理层正是这种攻击变现的地方。

API 设计的含义是,最小权限原则必须延伸到代理的凭证,而不能止步于用户。一个可以在其整个工作空间进行读写的用户在 UI 会话中是安全的 —— 因为人类参与了每一个操作。但将同样的范围交给代理,意味着单个被注入的文档就可以发出用户从未意图执行的删除指令。由此产生了两个变化:

  • 代理凭证应比用户的凭证范围更窄。 发起针对每个任务的委托,范围限制在任务实际需要的资源上。带有明确 scope 和短 TTL 的 OBO 流程是正确的形式。这样审计记录就能反映出代理仅拥有其应有的范围,这既限制了爆炸半径,又使事件响应变得可行。
  • 高风险端点应要求人类参与确认步骤,而代理无法单独完成。 转账、注销账号、授予权限 —— 这些操作应置于确认令牌之后,代理可以请求该令牌,但只有人类才能完成。返回一个带有 requires_human_confirmation 类型化错误代码的 4xx 响应,以及一个用户可以解决的跳转 URL,是一个非常合理的方案。

审计链在这里同样至关重要。当事后分析(postmortem)询问“是用户点击了那个按钮,还是代理根据注入了提示词的文档做出的决定”时,唯一的回答方式就是记录了这两者,并拥有足够的保真度来重现决策过程。“代理做的”不是答案;“代理在委托 D 之下执行,由从存储 Y 在时间 Z 检索到的文档 X 触发提示,基于模型版本 M 做出决定”才是答案。

架构实现

你今天发布的 API 正在为两种不同类型的调用者提供服务,它们具有不同的流量特征、不同的故障模式、不同的安全威胁以及不同的恢复模式。假装它们是同一种类型,是所有“为什么这个端点突然变得如此不稳定”调查、所有始于功能发布的频率限制事故,以及在客户询问是谁删除了什么内容一个季度后才浮现的审计漏洞的根源。

这项工作并不光鲜夺目。在网关层添加调用者类别请求头(caller-class header)、在变更端点上使用幂等键(idempotency keys)、带有类型代码和重试提示的 RFC 9457 错误封装、捕获完整委托链的审计记录、按调用者类别拆分的仪表盘,以及代理凭据上的最小权限范围。这些都不是什么新鲜事,但它们都是正确的。现在就开始实施这些方案的团队,将不再需要忙于应对已有的代理流量“救火”工作。而那些不这样做的团队将会在一次又一次的事故中不断发现,在他们不经意间,“这个 API” 已经变成了两个不同的 API。

References:Let's stay in touch and Follow me for more thoughts and updates