@indexyz/worker-ai-base
v0.4.0
Published
Privacy-conscious AI usage analytics and OpenTelemetry tracing for Cloudflare Workers
Readme
@indexyz/worker-ai-base
Cloudflare Workers 的 AI 遥测公共包,提供统一的 usage analytics 与分布式 tracing;不依赖具体业务环境、路由、D1 或套餐枚举。
- Analytics:Anthropic Messages JSON/SSE usage → Analytics Engine,每次上游调用最多写一条。
- Tracing:真正的 OpenTelemetry SERVER/CLIENT spans、W3C
traceparent/tracestate、显式父子 context;不是仅记录 request ID。 - 安全默认:不自动采集 URL、headers、正文、异常 message/stack;模型名仅记录到用量数据,不加入 tracing;遥测故障不改变业务结果。
- 工具链:Vite 8(Oxc/Rolldown)、oxfmt、oxlint recommended correctness + type-aware/type-check、严格 TypeScript、Vitest + workerd E2E。
安装与接入
npm install @indexyz/worker-ai-base @opentelemetry/api包为 ESM,包含声明文件,无 Node.js 运行时依赖。构建/测试环境使用 Node.js 22.12+,推荐 Node.js 24。
import { createTelemetry } from "@indexyz/worker-ai-base";
import type { AnalyticsDataset } from "@indexyz/worker-ai-base";
interface Env {
USAGE?: AnalyticsDataset;
USAGE_ENABLED: string;
}
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const telemetry = createTelemetry({
analytics: {
enabled: env.USAGE_ENABLED === "true",
...(env.USAGE ? { dataset: env.USAGE } : {}),
},
// tracing 可独立启用,见下一节。
});
// 鉴权、参数校验、解析模型别名之后再进入;不要将 API key 当作 keyId。
return telemetry.handle(
request,
{
operation: "POST /v1/messages", // 静态路由模板,不能是实际 URL
keyId: "internal-key-id",
model: "resolved-model-id", // 包内 SHA-256,原文不进入 telemetry
mode: "personal_plan", // 应用定义的静态 provider/plan 标签
},
async (scope) => {
return scope.fetch("https://provider.example/v1/messages", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ model: "resolved-model-id", messages: [] }),
signal: request.signal,
});
},
ctx,
);
},
};使用 scope.fetch 而不是全局 fetch 记录 AI 调用;包不会包装全局 fetch。一次 handler 可以发起多个上游调用,每个调用独立记录,使用同一 request.id 关联。非 AI 请求不要通过 scope.fetch。
handle 为返回的 HTTP 响应设置新生成的 x-request-id,忽略入站同名 header。上游 CLIENT span 持续至其响应体 EOF/错误/取消;SERVER span 持续至最终响应体终止。必须返回、消费或取消每个上游 body,否则不会产生完整遥测。无 body 的响应立即结束。不支持 WebSocket upgrade。
原始 fetch/handler/流异常保留身份并向调用方传播,不生成业务错误响应;需要安全错误响应时由应用处理。没有内置重试或超时策略,使用 RequestInit.signal(例如 AbortSignal.timeout),以及显式取消响应体。
OpenTelemetry
传入应用已有的 SDK tracer 和 provider 的 flush:
const telemetry = createTelemetry({
tracing: {
tracer: provider.getTracer("my-worker"),
flush: () => provider.forceFlush(),
acceptRemoteParent: false, // 公网入口推荐;可信内部入口可设 true
},
});provider 是应用创建的 OpenTelemetry SDK provider,例如 @opentelemetry/sdk-trace-base 的 BasicTracerProvider,配置应用选择的 SpanProcessor / Workers-compatible SpanExporter。包不创建全局 provider/context manager,不绑定供应商或 OTLP endpoint,也不自动开启 Cloudflare fetch/D1 instrumentation。没有 SDK tracer 时只启用 analytics;只有 API 的 no-op tracer 不会导出 spans。
生产使用供应商提供的 Workers exporter,或经 workerd 验证、使用 fetch 的 OTLP exporter;不要直接使用依赖 Node HTTP、XHR 或 DOM 的 exporter。采样、认证、批量上限、重试、出口脱敏均由 SDK/exporter 管理。通过每次 invocation 的 ctx.waitUntil 调度 flush,拒绝不会变成 unhandled rejection;未传 ctx 时只能 best-effort,不能保证响应结束后的导出。
入站默认提取 W3C trace context(可用 acceptRemoteParent: false 禁用);不提取 baggage。出站默认不自动注入,仅对明确可信且支持 tracing 的上游启用:
const response = await scope.fetch(url, init, { propagateTrace: true });启用注入时强制 redirect: "manual",避免通过重定向把 trace context 带到其他域。应用自行提供的 headers 不会被清洗;不要复制未经审查的入站 headers。可用 tracing.propagator 替换传播协议,但自定义 propagator 的隐私策略由调用方负责。
其他业务 span 显式传 scope.context:
const span = tracer.startSpan("business.operation", {}, scope.context);
try {
// ...
} finally {
span.end();
}包创建的 span 只携带 request.id、http.response.status_code,使用静态名称和 ERROR status;不调用 recordException。自动结束和去重,且不使用 ambient context,避免并发 invocation 串联。SDK 自行添加的 resource/attributes 不在包的隐私保证范围内。
Analytics Engine
{
"analytics_engine_datasets": [{ "binding": "USAGE", "dataset": "my_worker_usage_v1" }],
"vars": { "USAGE_ENABLED": "true" },
}缺失 binding、未启用或同步写入抛错均 fail-open,不重试、不写 D1,也不需要为 writeDataPoint 调用 waitUntil。
固定 v1 列
v1 使用固定列顺序;不要修改已存在 dataset 的列语义。
| 列 | 含义 |
| --------- | ----------------------------------------------------------------------------------------------------------------------- |
| index1 | 内部 API key ID(不是 key/prefix/hash;须符合 AE 的 96-byte index 限制) |
| blob1 | v1 |
| blob2 | 本地生成的 request ID |
| blob3 | 兼容旧版的模型哈希;新接入不计算哈希,此列为 omitted |
| blob4 | 应用定义的静态 mode |
| blob5 | json / sse / unknown |
| blob6 | completed / http_error / upstream_error / incomplete / cancelled / timeout / read_error / network_error |
| blob7 | complete / partial / missing |
| double1 | 上游 status,无 headers 为 0 |
| double2–5 | input、output、cache creation、cache read tokens |
| double6 | 上游调用至本地终止的毫秒数(受消费速度影响) |
| double7 | 首个非空响应 chunk 等待毫秒数,无数据为 -1 |
| double8 | 已知字段 bitmask:input=1、output=2、creation=4、read=8 |
SSE message_delta 是累计值,不逐 chunk 相加。拒绝负值、非安全整数和倒退计数;零与未知通过 bitmask 区分。complete 要求 input/output 已知、正常 EOF、无协议错误;SSE 还要求 start、带 output 的 delta 和 stop。取消/错误仅保留已观察到的 partial。
JSON 观察最多 1 MiB;SSE 每行/事件最多 64 Ki UTF-16 字符。明确的 content/tool 事件不影响 usage 完整性。无 tee/clone,无主动排空,无更改响应字节;保留背压及取消传播。目前仅理解 Anthropic Messages,不是 OpenAI Responses/Chat Completions usage 适配器。未知协议仍记录请求结果,但 usage 为 missing。
Analytics Engine 会采样,不是计费账本;查询必须用 _sample_interval:
SELECT index1 AS key_id,
SUM(_sample_interval) AS estimated_requests,
SUM(IF(blob7 = 'complete', double2 * _sample_interval, 0)) AS input_tokens,
SUM(IF(blob7 = 'complete', double3 * _sample_interval, 0)) AS output_tokens
FROM my_worker_usage_v1
WHERE timestamp >= NOW() - INTERVAL '1' DAY
GROUP BY key_id自 0.3.0 起,blob8 保存模型名(最多 256 字符,不含控制字符;无效值为 omitted)。旧版没有此列,无法从历史哈希恢复原名。新接入向 writer 传入 model,不再计算哈希;前七个 blob 和所有 double 保持兼容。模型名不加入 tracing 属性。
高级接入可以独立使用 MessageUsage、createUsageWriter;每个上游调用新建 observer/writer,observer 依序 feed 后只 finish 一次。writer 的输入为受信任的已验证数据;它在首次写入尝试后关闭,不因失败重试。
隐私和运行时边界
operation、mode、keyId 是调用方维护的静态/内部元数据,不是任意客户端输入。这里只保证包自身的 telemetry 不采集敏感内容;无法脱敏 Cloudflare 平台附加元数据、SDK 自动 instrumentation 或原始异常被应用重新抛出后的平台日志。特别是 provider URL 路径可能含 key,勿直接开启自动 fetch spans。
平台强制终止、未消费 body、网络或采样均可能丢记录。analytics 与 tracing 可独立启停。库不会改变任何远程配置,不保证 Cloudflare 的实际日志保留或数据送达。
开发与验证
npm ci
npm run check
npm pack --dry-runnpm run lint:oxlint 默认 recommended correctness 规则、type-aware、--type-check,警告失败;额外启用 Promise 正确性规则。npm run typecheck:TypeScript strict、unchecked index、exact optional、unused、isolated/verbatim modules。npm test:先构建 ESM/声明,Vite 打包真实的包消费者,在 Miniflare/workerd 运行 Vitest E2E。artifacts/vitest.json:机器可读测试结果;artifacts/telemetry.json:实际采集的 AE points 和 SDK 导出的 spans;artifacts/worker/worker.js:运行过的 Worker bundle。
测试包含 JSON/SSE、累计计数、截断/超限/错误、取消/背压、并发 context、W3C 父子关系、可选/失败的遥测、敏感字段不进入 telemetry。AE 使用内存 binding adapter,tracing 使用真实 SDK 的 InMemorySpanExporter;不写远程 dataset,也不证明生产 OTLP collector 可达。
Miniflare 固定为 5.20260921.0-alpha(仅开发依赖),使用其官方 v4 配置转换器;旧 v4 的传递依赖存在已知安全公告。锁文件固定全部依赖版本。发布仅包含 dist/、本说明和 package metadata,不包含业务代码、测试或测试产物。
