跳到主要内容

25 篇博文 含有标签「api-design」

查看所有标签

你的代码从未检查过的 Finish Reason

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的处理器(handler)做对了一切。HTTP 状态码是 200。Body 解析成功。文本字段里有内容。你增加了 responses_succeeded 的计数,将消息追加到对话中,把 JSON 返回给客户端,然后继续下一步。用户得到的是一个在句中戛然而止的句子,一个伪装成正常答案的经过编辑的回复,或者是一个被措辞为补全的礼貌拒绝。你的仪表盘对这一切一无所知。供应商已经告诉了你,但你没有读取那个字段。

每个主流的推理 API 都会在文本之外返回一个停止信号:OpenAI 称之为 finish_reason,Anthropic 称之为 stop_reason,Gemini 称之为 finishReason。这个字段很小,每个响应对应一个枚举值。它也是模型用来告诉你刚才发送的响应是一个完整答案还是一个碎片的唯一带外(out-of-band)通道。将其视为无关紧要的装饰,与忽略 HTTP 状态码属于同一种类型的 Bug —— 不同之处在于,你的监控系统在十年前就能捕捉到 HTTP 错误,但对这个错误却无动于衷。

你为人类设置的速率限制,AI 智能体三秒钟就会让其饱和

· 阅读需 11 分钟
Tian Pan
Software Engineer

速率限制从来就不是一种公平性原语。它只是一个逐渐“演化”而来的销售工程指标——是三年前某个解决方案工程师在客户接入期间随手写进文档、被复制到套餐定义中,且由于从未有人触发过而从未被重新审视的一个数字。这个限制写着“每分钟 100 次请求”,其真实含义是“超出了任何理性的集成方案的需求”,因为当时平台上的每一个集成都是由人类在键盘前驱动的后端服务,而人类每分钟敲不了 100 次字。

然后,一个付费租户将一个智能体(agent)指向了该端点。智能体不会打字。它不会为了阅读响应而停顿。它没有需要在请求之间渲染的 UI。它执行一个规划循环,每一个推理步骤调用一次 API,而模型制定一个推理步骤只需要大约 30 毫秒的实际时间。智能体在 3 秒内就触及了每分钟的限额,在 3 分钟内触及了每小时的限额,而在轮值工程师的咖啡还没变凉之前,它就已经耗尽了每日配额。在限流仪表盘更新之前,技术支持的升级请求就已经送达了。

你的智能体把指针当成了值:工具输出里的引用 vs 值

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个搜索工具返回了十个文档 ID。一个素材工具返回了一个 S3 预签名 URL。一个数据库工具返回了一行的 handle。一个文件工具返回了一条路径。这些返回值,从形式上看,全都是指针——一串简短的字符串,命名着智能体当前还没有真正拿到手的那个值。模型接下来怎么走,完全取决于它是否意识到这一点、并在推理之前先做一次解引用,还是说它把指针当成了对象本身。

这个失败模式在 trace 里是看不见的。工具调用成功了。返回结构正常。模型也输出了看上去合理的文本。日志里没有任何一行会说:"智能体在对一个文件名做推理,并把它当成了文档。"指针 vs 值的混淆,发生在可见行为底下的那一层——一个你的工具 schema 从未命名过的层。

智能体从未接收到的服务降级信号

· 阅读需 10 分钟
Tian Pan
Software Engineer

当下游 API 开始出现波动时,人类操作员在任何事情真正崩溃之前,就会通过十几种方式察觉到。状态页变为黄色。变更日志邮件飞进收件箱。提供商的仪表板上出现警告横幅。值班频道因有人在日志中发现 429 错误而热闹起来。队友发帖询问:“还有人看到写入变慢吗?”这些都不是对请求的响应。它们是围绕 API 的环境运行信号,人类几乎是被动地吸收了这些信号。

调用同一个 API 的智能体(agent)只收到一样东西:它刚刚发出的请求的响应。状态码、Header、Body。这就是全部的渠道。它没有收件箱,没有仪表板,没有 Slack,没有外围视野。它察觉不到最后十个调用每个耗时都是之前十个的两倍。它读不了状态页,因为没人给它 URL,它也没有查看状态页的常规指令。当依赖项降级时,智能体是系统中最后一个知情方——而且它通常是通过失败才知晓的。

这种不对称性并非模型能力问题。更聪明的模型也解决不了。智能体对运行信号是盲目的,因为底层设施(plumbing)从未传递过这些信号,而且大多数智能体架构在出厂时,甚至没人注意到缺失了这些底层设施。

你的智能体从未发送的幂等键

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位客户曾因一次退货收到了三次退款。这不是因为模型幻觉出了某种政策,也不是因为人类填错了表单——而是因为退款工具超时了两次,智能体随之重试了两次,而每次重试都发起了一个全新的请求,导致支付处理器根本无法识别这其实是它处理过的工作。三个干净的 HTTP 200。三次真实的资金转移。智能体完全按照指令行事:当调用失败时,重试。

这个 Bug 不在模型中。Bug 出在一个从未发送的 Header(标头)中。

重试是智能体最自然不过的本能。工具调用返回错误,或者更糟,什么都不返回,而循环系统的直觉——无论是编码在框架、提示词还是模型自身的训练中——就是重新尝试该动作。这种直觉对于“读”操作是正确的,而对于“写”操作则是灾难性的。一个韧性十足的智能体与一个会向客户重复收费的智能体之间的区别,不在于智力高低,而在于每一次改变状态的工具调用是否携带了幂等键(Idempotency Key),以及另一端的系统是否真正履行了它。

你的内部 API 在智能体调用的那一天起就变成了公共 API

· 阅读需 12 分钟
Tian Pan
Software Engineer

内部 API 依赖于一种默契而存在:没人会写下契约,因为每个人都已经心知肚明。那些碰巧存在的字段、调用者在暗地里解析的报错、返回空列表而非 404 的 200 响应 —— 这些都是关键的承重行为。而维系这些行为的基础是,你可以叫出每个调用者的名字,并在做出任何更改之前通过 Slack 联系他们。这种安排一直有效,直到它失效的那天。

当你将一个智能体(Agent)连接到该 API 的那天起,这种默契就失效了。这并非因为智能体怀有恶意或粗心大意,而是因为智能体是一个你无法触及的调用者。它没有 Slack 账号。它没有阅读你的迁移说明。它依赖于从示例载荷或 Schema 快照中吸收的响应形态,并且在你早已更新版本后,它仍会长期依赖这些形态。

一个令人不安的事实是,“内部”从未是 API 本身的属性。它其实是调用者列表的属性。将列表缩减到你认识的人,API 就是内部的;一旦增加一个你无法协调的参与者,API 就是公共的 —— 这意味着你需要承担“公共”一词所暗示的所有严谨规范,尽管你并没有构建任何本应具备的基础设施。

你在没告诉智能体的情况下修改了工具 Schema

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位后端工程师重命名了一个字段。user_id 变成了 customer_id,因为团队终于在所有服务中统一了 “customer” 这个术语。他们还增加了一个参数 region,因为计费系统现在需要它。这次变更通过一个包含两个批准的普通拉取请求(pull request)发布。每一个调用该端点的下游服务都在同一个发布版本中进行了更新。集成测试全部通过。按照后端团队衡量的一切标准,这是一次常规且执行良好的 API 变更。

一周后,支持工单开始增加。负责下单的智能体偶尔会在没有关联客户的情况下下单,或者将其关联到错误的区域。没有人改动过智能体。没有人改动过提示词(prompt)。模型的版本与上个月完全相同。然而,智能体现在却出现了一种以前从未有过的错误。

原因既不是模型中的 Bug,也不是后端中的 Bug。而是工具 Schema(tool schema)有两个消费者,但在审查变更时,只有其中一个在场。

你无法通过邮件给模型发送变更日志:为什么当调用者是 LLM 时,API 弃用机制会失效

· 阅读需 11 分钟
Tian Pan
Software Engineer

API 弃用是一种建立在接收者具备阅读能力这一假设之上的通信协议。你发布更新日志、向注册开发者发送邮件、添加 Deprecation 标头、提前六个月发出通知,并寄希望于另一端的人类能看到警告、提交工单,并在停用日期之前完成迁移。当你的最活跃调用者变成了语言模型的那一刻,这整套工作流程就悄无声息地失效了。

LLM 不会订阅你的开发者时事通讯。它没有 Slack 频道让人转发你的迁移指南。它在每一次调用中重新发现你的 API —— 或是通过被交付的工具描述,或是通过一份可能已经过时 18 个月的文档页面,亦或是基于其训练数据中对你 API 样子的记忆。这里没有一个你可以进行版本化、通知或传呼的持久客户端。每一次请求都是与一个实体的全新博弈,而这个实体既不记得你上一次的公告,也没有义务阅读你下一次的通知。

这并非假设。随着 Agent 逐渐成为内部和外部 API 的主要消费者,后端团队沿用了 15 年的弃用策略手册正在以一种特定的、可诊断的方式失效 —— 且大多数团队只有在发现一个“已弃用六个月”的端点仍在生产环境中为 Agent 提供服务、且没有路径使其停止时,才会意识到这一点。

流式推理中的海勒姆定律:节奏、停顿和中间 Token 是未成文的契约

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个团队从前沿模型升级到了其更快的后继版本。评估套件(eval suite)全绿。最终答案一致。工具调用的 Schema 完全相同。结构化输出通过了与以往一样的 JSON Schema 验证。他们发布了。不到一天,支持票据就堆积如山:“助手感觉太匆忙了”,“它不再真正思考了”,“感觉不对劲”。产品经理调取了遥测数据,发现任务完成率没有变化。工程团队反复检查了评估和 Schema,没发现任何问题。投诉是真实的,但团队定义的契约——就如团队所定义的那样——依然完好无损。

改变的是流的纹理(texture)。旧模型在调用工具前会停顿 800 毫秒,发出一句“让我查一下……”的前导词,并以每秒约 35 个 Token 的速度输出,在子句边界处有自然的节奏。新模型以每秒 90 个 Token 的速度输出,从不停顿,且完全跳过了前导词。这些都没有出现在任何文档记录的契约中。但所有这些都是不可或缺的“承重”部分。

这就是海勒姆定律(Hyrum's Law),而流式传输(streaming)让它的表面积变得巨大。系统的任何可观察行为都会被某人所依赖——而流式 AI 界面暴露的可观察行为远比团队意识到的要多。

API 文档即可靠性基础设施:文档如何决定智能体的成功率

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数工程团队将 API 文档视为开发者体验关注点——一种为了减少支持工单和入职时间而进行的改进。当你的主要消费者是在浏览器中阅读文档的人类时,这种思维方式是有道理的。但现在,这已经不再足够。

当 AI 智能体通过工具调用访问你的 API 时,你的文档就不再仅仅是指南,而是变成了运行时行为。模糊的参数描述不再是 UX 上的不便,而是对模型产生幻觉值的直接指令。缺失的错误代码不再是参考文档中的空白,而是一个模糊信号,可能导致智能体陷入没有退出条件的重试循环。你三年前为人类读者编写的文档,现在正被一个无状态的语言模型解析,无论它是否理解正确,都会自信地执行。

Agent 作为用户:当机器人成为你的主力用户时,产品分析为何失效

· 阅读需 12 分钟
Tian Pan
Software Engineer

2025 年,自动化互联网流量同比增长了 23.5%,是人类流量增速的八倍。其中,agent 驱动的交互增长了 7851%。如果你的产品处理了相当体量的 API 流量,那你的最重度"用户"很可能根本不是人类。而令人不安的事实是:你的产品分析系统对此几乎一无所知。

这不是一个机器人检测问题,而是一个埋点架构问题。当 AI agent 预订差旅、提交费用报告、查询数据库或调用你的支付 API 时,它留下的行为特征与人类完全不同——而你的会话漏斗、NPS 问卷和队列留存图,正在悄悄对你撒谎。

AI 原生 API 设计:构建智能体真正能调用的后端

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 REST API 运行良好。文档齐全,错误码一致,每一个经过测试的人工编写客户端都能正常使用。然后你的团队接入了一个 AI 智能体,不到一小时,它就通过不断重试一个不存在的端点的各种变体生成了 2,000 次失败请求——bulk_search_userssearch_all_usersbulk_user_search——每次尝试都触发了真实的下游处理。

这不是提示词工程的失败,而是 API 设计的失败。

REST API 是为能够解析文档、遵守契约、严格调用规范的客户端而构建的。AI 智能体则不同:它们根据名称和描述推断端点可能做什么,在不追踪状态的情况下重试,并将错误信息视为指令而非诊断代码。为智能体调用方设计 API,需要重新审视大多数后端工程师从未质疑过的基本假设。