RH
RouterHub AI API 中转站导航与平台推荐
博客

AI SDK 遥测为何要先脱敏

AI SDK 7.0.165 把供应商请求 URL 的清理前移到遥测观察点,避免凭据先被追踪集成读到再返回脱敏结果。

muchacha 2026-11-09 02:26:19

正文

先说结论:修复的是观察时序

AI SDK 7.0.165 的关键修复不是新增一个遥测开关,而是改变请求元数据被观察的时机。generateText 和 streamText 现在会在供应商结果进入遥测集成以及 Node.js tracing channel 订阅者之前,先清理请求 URL;应用最终拿到的结果仍保持原有行为。对接了 OpenTelemetry、日志系统或自定义 telemetry integration 的开发者来说,这意味着“应用结果已经脱敏”不再等于“观察者已经脱敏”,升级后还应把集成层的回归测试补上。

这个差异很具体。假设模型客户端的 baseURL 是 https://user:password@model.example.test:8443/private?key=secret,修复前,应用侧结果可能只剩 https://model.example.test:8443,但遥测包装器和 tracing subscriber 在更早的回调中仍可能看到完整 URL。7.0.165 将清理动作放进实际执行供应商调用的回调里,使这些观察点和应用侧看到同一份已处理元数据。修复提交还保留请求体、可选元数据和供应商对象,不改变真正发出的请求。

如果项目刚好在排查工具调用并发问题,可以参考站内的 AI SDK 工具调用并发文章,但两者属于不同边界:toolCallConcurrency 管的是工具执行调度,这次修复管的是模型请求结果被遥测观察时的敏感信息暴露。

原来的泄露窗口在哪里

AI SDK 的一次文本生成调用至少有三个值得区分的观察位置:供应商适配器发出 HTTP 请求,SDK 的遥测集成包装这次模型调用,应用代码读取生成结果。Node.js tracing channel 还可以让独立订阅者监听异步调用结束事件。问题不在供应商是否支持某个模型,也不在模型输出内容本身,而在第二和第三个位置之间的元数据处理顺序。

修复合并请求描述了旧路径:executeLanguageModelCall 返回结果后,SDK 才对 result.request 做 URL 清理。因此,遥测集成在等待 execute() 返回值时,可能先收到带有认证信息、路径或查询参数的请求元数据;Node tracing channel 的 asyncEnd 订阅者也可能从同一次执行事件中读取未清理的结果。随后 SDK 再把更简化的请求对象交给公开结果,所以只检查 result.request.url 的应用测试会误以为整个链路都已安全。

修复后的结构把清理放入 generateText 的 doGenerate 执行回调,以及 streamText 的 doStream 执行回调。换句话说,供应商适配器返回结果后、结果离开 telemetry 执行上下文之前,request 字段就已经经过处理。官方回归测试同时覆盖了普通生成和流式生成,并且分别检查 tracing subscriber、telemetry integration 和应用结果,而不是只检查最后一层。

这里还有一个容易误判的细节:URL 清理并不是“把 URL 字符串中的密码替换成星号”。测试期望的是,对于有效 HTTP URL,只保留协议、主机名和端口组成的 origin;路径、查询串、片段以及用户密码会消失。对于无效 URL、data: 这类非 HTTP URL 或缺少 URL 的请求元数据,测试期望省略 url,而不是把原字符串原样传递下去。请求体仍可保留,因此这项修复不是把整个 request 对象丢弃。

recordInputs 和 URL 清理不是一回事

AI SDK 官方遥测配置把 recordInputs 和 recordOutputs 定义为独立选项,默认值都是启用;关闭它们可以避免记录模型输入或输出,也可以减少传输数据。但这两个选项解决的是内容记录范围,这次 7.0.165 修复解决的是请求元数据在观察点之间的时序。不能因为设置了 recordInputs: false,就假定端点信息完全不会出现在遥测里:官方文档明确说明,完成的模型调用仍可记录 server.address 和 server.port,只是原始 provider body 和相关敏感内容会被过滤。也不能因为 URL 已清理,就把模型输入和模型输出当作已经脱敏。

在生产代码中,建议把两类控制分别写出来。例如,应用不需要保存提示词和模型输出时,可以显式关闭内容记录,同时在自定义集成入口再次检查请求元数据:

import { generateText } from 'ai';

const result = await generateText({
  model,
  prompt,
  telemetry: {
    isEnabled: true,
    recordInputs: false,
    recordOutputs: false,
    integrations: [
      {
        async executeLanguageModelCall({ execute }) {
          const providerResult = await execute();

          // 集成边界仍应拒绝把完整 URL 写入日志或事件。
          const requestUrl = providerResult.request?.url;
          if (requestUrl) {
            const safeUrl = new URL(requestUrl).origin;
            emitTelemetry({ requestUrl: safeUrl });
          }

          return providerResult;
        },
      },
    ],
  },
});

示例中的第二次清理不是为了替代 SDK 修复,而是为了让集成边界具备独立的防线。集成可能被多个 SDK 版本、不同调用路径或其他供应商适配器复用,接收方不应该把“上游已经处理过”当成永远成立的契约。若希望不采集 URL,也可以在 emitTelemetry 前直接丢弃该字段;要保留定位能力时,只保留 origin,并禁止把原始 baseURL、请求头或错误对象串进同一条日志。recordInputs 和 recordOutputs 的断言应另写测试,分别确认内容过滤与 endpoint host/port 的保留策略。

如何验证集成真的看到了安全结果

不要只调用 generateText 后检查最终返回值。至少需要构造一个带合成凭据的本地 URL,并在三个观察点分别断言:应用结果的 URL 是 origin,telemetry integration 看到的也是 origin,tracing subscriber 看到的仍是 origin。测试中的凭据必须是虚构字符串,服务端可以使用 loopback HTTP 响应,不需要访问真实模型或线上 API。

最小回归矩阵可以按下面的维度组织:

场景 应检查的结果
generateText,有效 HTTP URL 只保留 origin,认证信息、路径、查询串和片段消失
streamText,有效 HTTP URL telemetry integration、tracing subscriber 和公开结果一致
无效 URL 不把原字符串继续传递给观察者
data: 等非 HTTP URL 不把数据内容当作请求地址记录
没有 request 元数据 保持 undefined,不制造一个假 URL
带 request body body 按原有契约保留,不能因清理 URL 而丢失

伪代码如下,重点是把断言放在 execute 返回之后、集成写日志之前:

const observed: string[] = [];
const model = createProvider({
  apiKey: 'local-test-only',
  baseURL: 'https://user:password@model.example.test:8443/private?key=secret',
});

const result = await generateText({
  model,
  prompt: 'Hello',
  telemetry: {
    integrations: [{
      async executeLanguageModelCall({ execute }) {
        const value = await execute();
        observed.push(value.request?.url ?? 'missing');
        return value;
      },
    }],
  },
});

expect(observed[0]).toBe('https://model.example.test:8443');
expect((await result.finalStep).request?.url)
  .toBe('https://model.example.test:8443');

实际项目还应对 streamText 单独写测试,因为流式路径使用 doStream,不能仅凭非流式测试推断两条路径始终一致。若应用使用 Node 的 diagnostics_channel.tracingChannel('ai:telemetry'),也要在 asyncEnd 中保存快照后再断言;不要在异步回调结束后读取可能已被后续逻辑修改的同一个对象。这样的测试既能检查字段内容,也能检查事件到达顺序。

升级边界与剩余注意点

这次发布是 ai 包的 patch 版本,官方合并请求明确写出没有改变 provider interface、依赖或公开 API。已有代码通常不需要新增参数才能获得 URL 清理,升级 ai 包后,重点是确认最终安装树确实使用了 7.0.165 或包含该修复的版本。若项目通过框架或内部封装间接依赖 AI SDK,不能只看顶层 package.json,应检查锁文件和运行时解析到的包版本。

修复范围也不能被夸大。合并请求明确讨论的是 generateText 和 streamText 的供应商执行路径,并指出独立的 WorkflowAgent 路径存在相似的后续工作,不属于该变更范围。因此,使用 Workflow 相关包的项目仍应在自己的 integration 入口做脱敏和回归验证;不能因为普通文本生成测试通过,就推断所有 Agent 工作流都已获得相同处理。

最后,URL 脱敏不等于完整的隐私治理。请求头、请求体、模型输入、模型输出、工具参数和错误对象都可能含有敏感信息,具体采集范围取决于集成实现和 TelemetryOptions 配置。可以先在 模型与平台目录 确认应用实际使用的模型入口,再为每个 provider 适配器建立同样的“应用结果、集成回调、追踪订阅”三层回归测试。只要测试把敏感字段放进真实的合成 URL,并同时覆盖非流式和流式调用,未来更换供应商或升级 SDK 时,问题就不会只在生产日志里暴露。

参考来源

以下来源于 2026 年 11 月 9 日打开并核对;版本号与修复范围以官方仓库发布记录和合并请求为准。