惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Comments on: Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
李成银的技术随笔
美团技术团队
博客园 - 三生石上(FineUI控件)
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
Cisco Talos Blog
Cisco Talos Blog
博客园 - 司徒正美
Jina AI
Jina AI
S
SegmentFault 最新的问题
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
E
Exploit-DB.com RSS Feed
J
Java Code Geeks
腾讯CDC
V
V2EX
NISL@THU
NISL@THU
M
MIT News - Artificial intelligence
量子位
T
Tor Project blog
T
Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - Franky
Scott Helme
Scott Helme
U
Unit 42
博客园 - 聂微东
Hacker News - Newest:
Hacker News - Newest: "LLM"
雷峰网
雷峰网
Vercel News
Vercel News
GbyAI
GbyAI
MyScale Blog
MyScale Blog
Microsoft Security Blog
Microsoft Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
aimingoo的专栏
aimingoo的专栏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
有赞技术团队
有赞技术团队
W
WeLiveSecurity
T
Tailwind CSS Blog
S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Y
Y Combinator Blog
I
Intezer
Last Week in AI
Last Week in AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security

暗无天日

读:tetris-sql——用一条SQL查询实现俄罗斯方块 - 暗无天日 TIL: dired 里按时间标记文件——dired-mark-if 与夏令时陷阱 - 暗无天日 TIL DDD战术模式:用Clojure让代码说人话 - 暗无天日 读:Amin Bandali 与 Protesilaos 谈 Emacs 内置功能的深度定制 读:Clojure 世界的 AI 代理调教术——四个改变行为的 Skill TIL: minibuffer 激活时也能操作其他窗口 - 暗无天日 读:Tramp改了配置怎么不生效 - 暗无天日 读:为 project.el 写一个自定义后端 - 暗无天日 读:AI 时代的敏捷开发 - 暗无天日 TIL: 给 dired 异步命令加 nohup,让外部程序活过 Emacs 退出 TIL: elisp-fontify-semantically——让 Emacs 看懂你的 Elisp 代码 TIL-etags扫描外部库头文件 - 暗无天日 读:The Many Faces of flet——Elisp 局部函数的三种写法 读:df 与 du——为什么两个磁盘用量命令数字对不上 - 暗无天日 TIL-可观测性工具的成本盲区 - 暗无天日 TIL: 把 Emacs Buffer 打印成图片 读:gamegrid.el——Emacs 内置游戏是怎么写出来的 - 暗无天日 TIL: MCP 服务器不到 20 行 Python 就能写出来 TIL-AI 工具普及后的组织观察 - 暗无天日 读:当 Agent 开始写数据库——六个防御模式 - 暗无天日 读:右键菜单——Elisp 开发的隐藏利器 - 暗无天日 读:Git 仓库里的隐藏配置文件 - 暗无天日 读:20条软件工程定律 - 暗无天日 读:编译高性能 Emacs - 暗无天日 读:为什么我在终端里待了十年——一个 Emacs 用户的 GUI 观察 读:Event Sourcing——让你的数据库记住每一次变更 - 暗无天日 读:数据管道中Schema变更的四种形状 - 暗无天日 读:SES——Emacs内置的简易电子表格 - 暗无天日 TIL: 用 parallel 加速 rsync 迁移海量小文件 TIL:给 AI 一个更小的世界——技术选型的上下文窗口约束 - 暗无天日 读:emacs chat 技巧拾遗——从 bandali 的配置里捡到的那些技巧 读:当 Agent 成为生产调用者——四个被打破的运维假设 - 暗无天日 TIL: describe-personal-keybindings 查看你的自定义按键 - 暗无天日 读:Linux 创建指定大小文件的三种方式——dd、fallocate 与 truncate - 暗无天日 读:Yazi——在终端里管理文件的新选择 - 暗无天日 读:软件测试的反馈视角——CLEAR 原则从测试到运维 - 暗无天日 MobileOrg Android:从 API 17 迁移到 API 34 的实战记录 TIL:微服务与复杂度守恒——从单体到分布式的代价转移 - 暗无天日 读:MCP 时代的安全威胁——幻觉权限与三道防线 - 暗无天日 读:超越对话——用 Skills 和 Agents 工程化上下文 TIL:用 :box 给 mode-line 加内边距 org-mobile-push 卡顿排查实战:从黑盒到字节码反编译 - 暗无天日 读:用 LLM 重构遗留代码——三个陷阱与一套方法 - 暗无天日 读:AI 辅助编程的三种错误用法 - 暗无天日 读:Before GitHub - 暗无天日 读:AI in Software Architecture - 暗无天日 读:把成本当作 SLI - 暗无天日 TIL: 用进程树展开定位被脚本包装的 JVM 进程 - 暗无天日 读:Linux 删文件的真相——用 /proc 恢复被进程持有的已删除文件 - 暗无天日 读:Choosing a Python Logging Library in 2026 Emacs buffer 导出:五种方案对比 - 暗无天日 TIL: flymake 错误跳转加入 Evil 跳转列表 TIL: 用 Org-mode 列表管理选择题题库 - 暗无天日 读:sysstat 诊断链——从 sar 到 pidstat 的排查路径 读:理论靠谱,生产翻车的六个集成模式 - 暗无天日 读:双写问题——@Transactional 给不了的跨系统一致性 - 暗无天日 读:PostgreSQL 随机测试数据生成——从快速造数到自动化填充 - 暗无天日 读:逆萨丕尔-沃夫假说与编程语言 - 暗无天日 读:理解 MCP 架构——LLM 直接调 API 与 MCP 协议的对比 读:Emacs 连接数据库时密码放哪里 - 暗无天日 TIL:watch 命令的几个遗漏技巧 - 暗无天日 TIL:Python 3.15 的 sentinel() 内置函数 读:7 Techniques That Supercharged My Claude-Assisted Development 读:AI 编码代理的四种工作流 - 暗无天日 读:Agent 的瓶颈不在模型,在基础设施 - 暗无天日 读:EvoForge——用群体进化优化 AI Agent - 暗无天日 TIL:Google Stitch 的 DESIGN.md,给 AI 读的设计系统说明书 Emacs 批量搜索替换:从场景到命令 - 暗无天日 TIL: image-mode 的 header-line 中显示图片尺寸 dotfile仓库大扫除:清理过时的配置 - 暗无天日 读:The Art of Logging——日志规范清单 - 暗无天日 从CSS选择器到自然语言:网页自动化的两种范式与取舍框架 - 暗无天日 TIL-用 curl + w3m + awk 从 HTML 表格提取数据 读:Shell脚本安全编码的五条铁律 - 暗无天日 读:Emacs newcomers-presets theme —— 30+ 项新手预设一览 读:Protesilaos 的 Emacs 合理默认配置 —— 兼与 newcomers-presets 对比 控制 Bash 历史记录的 6 个场景 读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 读:AI Agent 生产化——一份从原型到上线的速查清单 - 暗无天日 读:LLM 生产环境六种失败原型——基准测试无法预测的那些故障 - 暗无天日 读:Prompt Injection 五层纵深防御——从输入过滤到审计追踪 - 暗无天日 读:为什么所有 Prompt Injection 防御都会被攻破——以及架构上该怎么办 - 暗无天日 读:JVM 后端性能调优备忘——从一次生产事故中学到的优化要点 - 暗无天日 读:Java 容器化——从 Fat JAR 到高效 Docker 镜像 读:整洁代码的几个通用原则——从 Go 生态看起 - 暗无天日 读:规则引擎——从 if-else 到业务规则管理 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日
读:从端点到行动——面向 AI 代理的后端设计 - 暗无天日
2026-05-24 · via 暗无天日

传统 API 设计有一个很少被明说、但无处不在的前提: 调用者知道自己在干什么 。调用者知道该调哪个端点,知道请求体长什么样,知道怎么处理返回的错误。前端调后端、服务间调用,这套假设基本成立,毕竟调用代码是人写的,人会看文档。

但 AI 代理不是这样。Satyam Nikhra 在 DZone 上的文章 From APIs to Actions: Rethinking Back-End Design for Agents 问了一个值得认真对待的问题:当你的 API 调用者是一个会推理、会猜测、会犯错的 AI 代理时,传统的端点驱动设计还够用吗?

传统 API 的隐含假设,AI 代理不满足

REST、GraphQL、RPC,具体形式不同,但都基于同一套假设:调用者知道端点( /users/graphql );调用者知道输入 schema,包括字段名、类型、必填还是可选;调用者知道输出格式,能从中提取需要的信息;调用者知道怎么处理各种错误码,400、401、500。

这四条对确定性客户端来说理所当然。确定性客户端,就是每次行为可预测的调用者:前端代码、后端微服务、定时任务,同样的输入一定产生同样的请求。人写的代码按文档来。文档写漏了怎么办?开发者打开浏览器 DevTools 看网络请求、多试几组参数、读错误响应里的提示,怎么都能摸出正确的调用方式。

但 AI 代理不是确定性客户端。它不是在"调用 API",它是在"尝试完成一个目标"。Nikhra 总结了代理容易犯的四类错误:

  1. 误解 API 合约。把字段的含义搞错,或者不理解字段之间的约束关系。
  2. 发送不完整或格式错误的输入。少传了必填字段,或传了后端不认识的值。
  3. 为任务选择了错误的端点。该调 PATCH /users/{id} 却调了 POST /users
  4. 收到错误后不知道如何恢复。遇到HTTP CODE 400 就停住了,不会根据错误信息调整请求。

问题不在模型质量。即使是最好的模型也会犯这些错,因为根子在 接口设计 上。传统 API 是为"不会犯错的调用者"设计的,当调用者是一个概率性推理系统,接口本身就是不匹配的。

核心转变:从"暴露什么端点"到"代理能执行什么行动"

Nikhra 的方案很简单:换一个问题。不要问"我该暴露哪些 API",问"代理应该能执行哪些行动"。

传统设计是这样思考的:

POST /users
PATCH /users/{id}
POST /users/{id}/verify

三个端点,调用者需要知道:先调第一个创建用户,拿到 ID,再调第二个更新资料,最后调第三个触发验证。顺序不能错,参数要自己拼。

而行动驱动的设计是这样命名:

CreateUser
UpdateUserProfile
VerifyUserIdentity

每个行动封装了一个完整的意图,内部可以跨多个服务、包含异步流程、处理重试和异常。外部看来就是一个操作:"请你验证这个用户的身份"。代理不需要知道内部有几个步骤、调了几个下游服务。

行动代表意图,不代表实现细节 。这个区分是整篇文章的基石。

为什么行动更适合 agent?作者给了三个理由:

  1. 行动匹配意图。代理的思维是目标驱动的("创建一个已验证身份的用户"),不是步骤驱动的("先调 A,再调 B,再调 C")。行动把意图封装成一个操作单元,跟代理的推理方式对齐。
  2. 行动隐藏实现复杂性。后端可能有多个微服务、异步工作流、外部集成、重试逻辑。人类工程师能把它们串起来,但代理在多个端点之间管理状态很容易出错。行动在内部编排这些复杂性,对外只暴露一个干净的接口。
  3. ,代理管理的步骤越多,失败概率越高。三个独立 API 调用,每一步都可能出错,代理需要在步骤之间维护状态。一个 OnboardUserWithVerification 行动把 N 个步骤合成一个操作,出错的环节也随之减少。

构建行动接口的 5 项实践

这 5 条大部分是 API 设计的已知原则,但放在 AI 代理的语境下,每条都有了新的含义。

行动合约要清晰。

每个行动需要一个意图驱动的命名(名字本身就说清楚这个行动做什么)、严格定义的输入 schema、可预测的输出 schema。Nikhra 给了一个贷款申请的示例:

Action: CreateLoanApplication
Input: {
  "userId": "string",
  "income": "number",
  "loanAmount": "number"
}
Output: {
  "applicationId": "string",
  "status": "pending | approved | rejected"
}

这不只是文档注释。这是代理用来推理的接口定义。代理读完这个合约就知道:创建一个贷款申请,需要用户 ID、收入和贷款金额,返回申请 ID 和状态。不用翻 API 文档猜哪个端点对应哪个操作。

Schema 是唯一真相源。

自然语言描述对代理不可靠,自然语言的歧义太多。严格用 JSON Schema 定义输入输出,在执行业务逻辑之前先验证输入,拒绝模糊或不完整的数据。

在行动层建立防护。

代理会犯错,系统必须预期这一点。每个行动都应该加上输入验证、鉴权检查、速率限制、安全默认值。这层是后端外层的"安全包装",就算代理传了奇怪的东西进来,也不会穿透到核心业务逻辑。

让行动幂等。

这点在agent语境下尤其关键,因为代理会大量重试。如果一个行动不是幂等的(每次执行都产生不同的副作用),重试可能产生重复记录、触发意外副作用、破坏数据一致性。行动设计的目标是:重复执行同一操作,产生相同的结果。

错误信息要有用。

传统 API 返回 400 Bad Request 就完事了。但代理不是人,它看到 400 不知道该改什么。代理需要的错误格式是结构化的:错误码 InvalidIncomeValue 、可读消息"收入必须大于 0"、以及这个错误是否可以重试。有了这些,代理才能根据错误信息调整请求,而不是直接失败。

编排层:被忽略的关键一环

切换到行动设计后,一个之前不太显眼的层变得不可或缺:编排层。它负责把代理的意图映射到具体行动的执行,处理复杂工作流的编排(多行动组合、顺序依赖),应用业务规则和策略。

没有编排层,你只是在暴露更好的 API。有了它,系统才真正是"代理感知"的。它理解代理想做什么,并负责把意图变成正确的结果。

现实中的 action-driven design

原文是概念分析,没有给代码示例。但"行动驱动"这个模式在现实中早有实际应用,只是不一定叫这个名字。

MCP 协议(Model Context Protocol)是 Anthropic 定义的 AI 代理与外部工具交互的协议。它的 tool 定义格式就是一个标准的 action contract:

{
  "name": "get_weather",
  "description": "获取指定城市的当前天气信息",
  "inputSchema": {
    "type": "object",
    "properties": {
      "city": { "type": "string", "description": "城市名称" },
      "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
    },
    "required": ["city"]
  }
}

每个 tool 由三部分组成: name (意图驱动的命名)、 description (代理用来判断该不该调用)、 inputSchema (严格定义输入格式)。这就是一个 action contract,和 Nikhra 的 CreateLoanApplication 示例结构完全一致。

OpenAI 的 function calling 也是同样的模式:定义 function 的 name、description、parameters(JSON Schema),模型决定是否调用、传什么参数。两家设计思路高度一致,说明这不是某一家公司的偏好,代理与后端交互的通用模式正在形成。

在运维领域,Ansible module 的设计也有类似的思路。一个好的 Ansible module,命名表达了操作意图( ansible.builtin.user 而不是 POST /user ),幂等(同一个 playbook 跑两次不会创建两个用户),返回值结构化( changed 告诉调用者是否做了实际变更, failed 说明是否出错)。这些正好对应 Nikhra 5 项实践中的要点。区别只在于,Ansible 是为人设计的(人写 playbook),MCP 和 function calling 是为代理设计的(代理决定调哪个 tool)。底层的基本原则是共通的。

什么情况下传统 API 仍然够用

原文的论证偏向"代理时代必须转向行动设计",但这个转变有成本,每个行动都需要在 API 之上增加编排层。不是所有场景都该这么做。

调用者是确定性的(前端页面渲染、定时任务、服务间 RPC),或者操作是简单 CRUD 不需要跨多个服务编排,传统端点驱动的 API 完全够用。

action-driven design 是在 REST/GraphQL 之上加一层面向代理的封装。底层仍然是 API,上层是行动。代理通过行动层与后端交互,人和传统服务继续通过 API 调用。

什么时候值得加这层封装?一个简单的判断标准: 如果你的 API 需要代理在多个端点之间做决策和编排,就应该把这部分逻辑收到行动层里 。不要让会犯错的推理系统去管理多步骤的状态,把它做成一个行动,让可靠的后端代码处理编排。

思维转变

Nikhra 在结尾问了一个问题:代理能不能调你的 API?这不是个好问题,更好的问题应该是:你的系统能不能理解 agent 想干什么?

这个问题的背后是后端工程师需要做的四个思维转变:

  1. 按能力而非端点来思考(不是我暴露了什么数据,而是代理能完成什么任务)
  2. 为不可靠的概率性客户端设计(不要假设调用者会按你的文档来)
  3. 构建自纠正、有弹性的系统(代理会出错,系统应该能消化而不是崩溃)
  4. 把 schema 当合约而非建议(自然语言描述对代理来说等于没有描述)。