AI SDK 集成

访问元数据

概览选项
从你的处理器中读取 AI 元数据——将其持久化、展示给最终用户、据此计费,或将增量进度流式传输给客户端。

宽事件已经包含完整的 ai 元数据,但你通常也希望在处理器内部获得相同的数据——用于持久化、展示给最终用户、据此计费,或将增量进度流式传输给客户端。

AILogger 为此提供了三个方法,无需触碰内部状态。

getMetadata() — 最终快照

返回一个结构化的 AIMetadata 对象,它与宽事件中的 ai 字段一致。可在任何时间调用,包括运行完成后或在 AI SDK 的 onFinish 中:

server/api/chat.post.ts
import { useLogger } from 'evlog'
import { createAILogger } from 'evlog/ai'
import { generateText } from 'ai'

export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const ai = createAILogger(log, {
    cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },
  })

  await generateText({
    model: ai.wrap('anthropic/claude-sonnet-4.6'),
    prompt: '总结这份文档',
  })

  const metadata = ai.getMetadata()

  await db.aiRuns.insert({
    userId: event.context.userId,
    model: metadata.model,
    inputTokens: metadata.inputTokens,
    outputTokens: metadata.outputTokens,
    estimatedCost: metadata.estimatedCost,
    finishReason: metadata.finishReason,
    responseId: metadata.responseId,
  })

  return { ok: true }
})

该快照是一个全新的副本:对它的修改永远不会影响底层状态或后续调用。

getEstimatedCost() — 快速成本检查

getMetadata().estimatedCost 的便捷封装。返回美元成本;如果未提供 cost 映射,或模型不在映射中,则返回 undefined

const ai = createAILogger(log, {
  cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },
})

await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })

const cost = ai.getEstimatedCost()
console.log(`这次调用花费了 $${cost?.toFixed(4)}`)

onUpdate(callback) — 增量更新

订阅元数据更新。每当底层状态刷新时,回调就会触发:

  • 多步骤 agent 运行中每一步一次
  • 每次 captureEmbed 调用一次
  • 模型出错时
  • createEvlogIntegrationonFinish

每次调用都会接收一个全新的快照。返回一个取消订阅函数。订阅者错误会被隔离,绝不会破坏 AI 流程。

server/api/agent.post.ts
import { ToolLoopAgent, createAgentUIStreamResponse, stepCountIs } from 'ai'
import { useLogger } from 'evlog'
import { createAILogger } from 'evlog/ai'

export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const { messages } = await readBody(event)
  const ai = createAILogger(log)

  ai.onUpdate((metadata) => {
    pushToClient(event, {
      type: 'ai-progress',
      step: metadata.steps,
      tokens: metadata.totalTokens,
      cost: metadata.estimatedCost,
    })
  })

  const agent = new ToolLoopAgent({
    model: ai.wrap('anthropic/claude-sonnet-4.6'),
    tools: { searchWeb, queryDatabase },
    stopWhen: stepCountIs(5),
  })

  return createAgentUIStreamResponse({ agent, uiMessages: messages })
})

用于一次性清理:

const off = ai.onUpdate((metadata) => { /* ... */ })
// later
off()

AIMetadata 形状

AIMetadatagetMetadata() 返回并传递给 onUpdate 监听器的快照所对应的公开类型别名。它与宽事件中的 ai 字段具有相同的形状。

import type { AIMetadata, AIMetadataListener } from 'evlog/ai'

function handleProgress(metadata: AIMetadata) {
  console.log(`${metadata.calls} 次调用,$${metadata.estimatedCost ?? 0}`)
}

const listener: AIMetadataListener = handleProgress
ai.onUpdate(listener)

捕获数据参考

每个可能出现在 ai.* 下的字段:

宽事件字段来源描述
ai.calls调用次数本次请求中的 AI 调用次数
ai.modelresponse.modelId提供响应的模型
ai.models所有模型 ID使用过的所有模型的数组(仅当 > 1 时)
ai.providermodel.provider提供方(anthropicopenaigoogle 等)
ai.inputTokensusage.inputTokens.total所有调用的输入 token 总数
ai.outputTokensusage.outputTokens.total所有调用的输出 token 总数
ai.totalTokens计算得出inputTokens + outputTokens
ai.cacheReadTokensusage.inputTokens.cacheRead从提示缓存中提供的 token
ai.cacheWriteTokensusage.inputTokens.cacheWrite写入提示缓存的 token
ai.reasoningTokensusage.outputTokens.reasoning推理 token(扩展思考)
ai.finishReasonfinishReason.unified生成结束的原因(stoptool-calls 等)
ai.toolCalls内容 / 流块默认是工具名称的 string[],或在启用 toolInputs 时为 Array<{ name, input }>
ai.responseIdresponse.id由提供方分配的响应 ID(例如 Anthropic 的 msg_...
ai.steps步数LLM 调用次数(仅当 > 1 时)
ai.stepsUsage按步累积每一步的 token 与工具调用明细(仅当 > 1 步时)
ai.msToFirstChunk流式时序到第一个文本块的时间(仅流式)
ai.msToFinish流式时序流式总时长(仅流式)
ai.tokensPerSecond计算得出每秒输出 token 数(仅流式)
ai.error错误捕获模型调用失败时的错误消息
ai.toolsTelemetryIntegration每个工具的 { name, durationMs, success, error? }(需要 createEvlogIntegration
ai.totalDurationMsTelemetryIntegration总生成墙钟时间(需要 createEvlogIntegration
ai.embeddingcaptureEmbed{ model?, tokens, dimensions?, count? } — 嵌入元数据
ai.estimatedCost计算得出以美元计的估算成本(需要 cost 选项)