授权信封:教会 agent 自己向用户要凭据

Flashduty AI SRE 技术揭秘第 12 篇:通过结构化授权信封向 Agent 和用户传递授权指引,并在用户完成授权后继续任务。

作者 快猫星云

暖色手绘封面:服务人员递出带有指引的信封

缺手续不打回,给一个信封,指引贴在信封上。

Flashduty AI SRE是一个专注 SRE 领域、帮助团队分析故障根因和完成日常运维工作的 AI Agent 产品,注册即可免费试用。这是系列文章《Flashduty AI SRE 技术揭秘》第 12 篇,本系列会持续分享我们在 agent 工程上的真实设计、踩过的坑和做过的取舍。

我们的 agent 平台接入了不少 MCP 工具,其中很多需要用户个人的凭据,比如你名下的日历、你名下的工单系统。于是经常出现这样的情况:agent 排查到一半,调用某个工具,才发现当前用户还没有配置对应的凭据。

这时最容易想到的有两种处理方式,但都有问题。

第一种是直接报错,把「401 Unauthorized」返回给模型。模型由此只知道调用失败了,不知道接下来该做什么。它会重试,会换各种参数再试,会开始猜凭据在哪,甚至编造一个并不存在的配置入口,让用户去找。本来一句话就能跟用户说清楚的事,变成了一段很长的无效循环。

第二种是把「怎么向用户要授权」写进系统提示。这份指引本身不复杂:告诉用户缺了哪个连接,引导去授权,授权完成后继续任务。问题在于,写进系统提示后,每个会话、每一轮请求都要带上这段话,而真正用到它的场景可能一百轮才出现一次。第 5 篇讲过,系统提示属于缓存前缀,是请求里最贵的位置,不适合放低频场景的说明。

我们最后的做法是:工具缺凭据时不报错,而是返回一个结构化的信封,把「怎么要授权」的指引放在信封里,在模型最需要的时候交给它。

信封长什么样

信封是一段 JSON,作为普通的工具结果返回:

{
  "type": "authorization_required",
  "resource_type": "mcp",
  "resource_name": "calendar",
  "method": "oauth",
  "oauth": { "authorize_url": "https://...", "state": "..." },
  "agent_instructions": "Before relaying this authorization request, ..."
}

同一份 JSON 有三个消费方。前端根据 type 把工具结果渲染成授权卡片:OAuth 场景显示授权链接,secret 场景显示粘贴框和帮助链接。IM 侧的服务读取同样的结构,做同样的渲染。模型则读 agent_instructions 字段,前端和 IM 都忽略这个字段,它只写给模型看。

既然有三方在解析,字段就要守一条纪律:可以新增,不能改名。信封的字段名在三个仓库的解析器里各写了一份,改一个名字要同步改三处,代价比加一个字段高得多。

缺少凭据后通过授权信封引导用户授权并重试工具的流程

一次授权往返:信封带着指引交给用户,用户授权后主动继续任务,agent 再重试工具。

指引怎么写

agent_instructions 的内容来自平台统一的模板,不由 connector 的作者自己写。授权话术是否正确,不应该取决于每个接入方的写作水平。模板里有几条内容,是看到模型的真实行为之后才加上的。

第一条:先交付已有结论,再要授权。授权会打断一个进行到一半的任务,模型很容易只丢给用户一个授权链接,把前面已经查到的东西都扔掉。所以指引第一段就要求模型先看看,凭已经收集到的证据能交付什么。如果缺的凭据只对应一个补充信息源,就不能让它卡住整个交付。

第二条:不同渠道的说法都写进信封,由模型自己选。在 Web 端,卡片上有一个「继续任务」按钮,模型应引导用户授权后点它。在 IM 端,模型应把链接原样发给用户,请用户在桌面浏览器中打开,完成后回来回复一句「已授权」。两种说法都放在信封里,构建信封时不判断当前渠道。模型知道当前会话来自 Web 还是 IM,从第 2 篇开始,这个信息就一直在会话信封里。如果构建信封时再传一份渠道信息,就多了一个两边不一致的可能。

第三条:secret 类凭据,永远不要让用户把密钥粘贴到聊天框里。聊天记录不是存放凭据的地方。指引要求模型引导用户去 Web 控制台完成配置,而不是在 IM 里直接发 token。

第四条:自动化场景单独处理。定时任务触发的会话没有人在看,所以指引写得很明确:不要等待,不要提问,任何话都不要写成问句;点名被卡住的 connector,在最终报告里把它记为阻塞项,然后交付现有证据能支持的结论。没人看的授权卡片起不到引导作用,只会让任务一直卡着。

文案上我们也栽过一次。连接类信封的早期版本告诉模型「控制台会渲染一张连接卡片」,结果模型把卡片长什么样、出现在哪里原样转述给了用户,还附上了一份连接完成后的查询计划。卡片本身就能说明问题,用户不需要模型再解说一遍。现在的指引只让模型说两件事:缺了什么,连接之后任务会继续。

继续任务要由用户触发

授权完成后,任务怎么接着做?我们的做法是不自动续跑。Web 端用户点卡片上的「继续任务」按钮,IM 端用户回复一句「已授权」,这两种操作最后都会变成一条普通的用户消息,agent 在新的一轮里重试刚才失败的调用。没有回调在后台悄悄重启任务,会话的每一步推进都对应一个用户能看到的操作。

授权也会在会话里留下记录:OAuth 回调成功后,系统往会话里写一条标记事件,回看聊天记录时能看到这里完成过一次授权。凭据按用户维度加密存储,之后的调用会自动带上。OAuth token 快要过期时,系统用 refresh token 换新的 token。这个刷新请求从本轮的执行环境发出,和用户实际使用的 runner 处在同一个网络里,而不是从云端直接请求,因为有些 token 服务只能在客户内网访问。

远端返回 401 时,处理方式取决于凭据类型。如果是 OAuth 凭据,就吊销本地保存的凭据,重新发起授权流程。如果是静态 secret,则不吊销:同一个 secret 对同一服务的其他工具可能仍然有效,一次 401 不能说明这个凭据已经失效。

不止授权这一种信封

同样的模式我们还用在另外两个地方。账号根本没有连接某个 connector 时,返回 connection_required 信封,里面带着连接卡片的深链。A2A 子 agent 的使用说明第一次投递时,用 agent_instructions_required 信封送达。

这三种信封的类型刻意区分开。前端按类型渲染不同的卡片,如果几种场景共用一个类型,前端就只能去猜该显示授权框还是说明卡。

指引跟着上下文走

授权信封也是我们一条 prompt 治理规则的起点:和特定上下文绑定的指引,跟着那个上下文出现,不写进系统提示。系统提示里关于授权只有一句元规则:authorization_required 的结果带有 agent_instructions,读它,照做。具体的指引从不出现在系统提示里。

这条规则在本系列里已经出现过一次。第 8 篇讲工具输出截断时,截断后的恢复指引附在截断标记上,系统提示里一个字都没提。agent 产品普遍会遇到 prompt 膨胀(prompt accretion):每修一个失败案例,就想往系统提示里加一句话,时间长了,系统提示就成了一本没人读完的规章。我们用一套归置规则来控制它:绑定在特定结果上的指引放进结果信封,工具用法写在工具描述里,领域方法论放进知识包,系统提示只保留动手前必须知道的内容。系列后面讲 prompt 治理的那一篇会展开这个话题。

下一篇换一个方向,讲多租户平台里的权限问题。agent 的知识分为三层:广告面、落盘面和加载面。bash 本身不做权限检查,所以我们把权限边界放在「文件有没有落到盘上」这一步。


这是「Flashduty AI SRE技术揭秘」系列的第十二篇。我们在做一个专注 SRE 领域、帮助团队分析故障根因的 AI Agent 产品,本系列会持续分享我们在 agent 工程上的真实设计、踩过的坑和做过的取舍。

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云