AI SDK 遥测为何要先脱敏
AI SDK 7.0.165 把供应商请求 URL 的清理前移到遥测观察点,避免凭据先被追踪集成读到再返回脱敏结果。
正文
先说结论:修复的是观察时序
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 日打开并核对;版本号与修复范围以官方仓库发布记录和合并请求为准。
- AI SDK
ai@7.0.165发布记录 —— 确认版本、发布时间以及请求 URL 脱敏和 transcription telemetry 两项 patch change。 - 修复请求 URL 遥测时序的合并请求 #24708 —— 确认旧泄露窗口、修复范围、回归矩阵、前后行为和 WorkflowAgent 未覆盖的边界。
- 修复提交
6d21e8e—— 确认generateText、streamText的执行回调与测试如何把 URL 清理前移。 - AI SDK 官方遥测文档 —— 确认输入/输出记录开关、生命周期事件过滤,以及 endpoint host/port 在关闭内容记录时的保留规则。
- AI SDK 遥测配置类型定义 —— 确认
isEnabled、recordInputs、recordOutputs、集成和运行时上下文是独立配置项。 - Node.js
diagnostics_channel文档 —— 确认tracingChannel、订阅者和异步结束事件的观察机制。