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):增量更新

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

  • 在多步骤代理运行中每个步骤触发一次
  • 每次 captureEmbed 调用触发一次
  • 模型发生错误时触发
  • createEvlogIntegrationonEnd(v7)或 onFinish(v6)时触发

每次调用都会接收一个全新的快照。返回一个取消订阅函数。订阅者错误会被隔离,绝不会破坏 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所有调用的输入令牌总数
ai.outputTokensusage.outputTokens.total所有调用的输出令牌总数
ai.totalTokens计算得出inputTokens + outputTokens
ai.cacheReadTokensusage.inputTokens.cacheRead从提示缓存中提供的令牌数
ai.cacheWriteTokensusage.inputTokens.cacheWrite写入提示缓存的令牌数
ai.reasoningTokensusage.outputTokens.reasoning推理令牌(扩展思考)
ai.finishReasonfinishReason.unified生成结束的原因(stoptool-calls 等)
ai.toolCalls内容/流块默认是工具名称的 string[],启用 toolInputs 时则为 Array<{ name, input }>
ai.responseIdresponse.id提供商分配的响应 ID(例如 Anthropic 的 msg_...
ai.prompt首次调用的 params.prompt发送给首次模型调用的提示文本格式化内容(仅在启用 prompt 选项时)
ai.output内容/流块上次模型调用生成的文本(仅在启用 output 选项时)
ai.steps步骤计数LLM 调用次数(仅在数量大于 1 时)
ai.stepsUsage按步骤累积每个步骤的令牌和工具调用明细(仅在步骤数大于 1 时)
ai.msToFirstChunk流计时首个文本块所需时间(仅流式传输时)
ai.msToFinish流计时流总耗时(仅流式传输时)
ai.tokensPerSecond计算得出每秒输出令牌数(仅流式传输时)
ai.error错误捕获模型调用失败时的错误消息
ai.toolsTelemetry 集成每个工具的 { name, durationMs, success, error? }(需要 createEvlogIntegration
ai.totalDurationMsTelemetry 集成生成总耗时(需要 createEvlogIntegration
ai.embeddingcaptureEmbed{ model?, tokens, dimensions?, count? }——嵌入元数据
ai.estimatedCost计算得出以美元计的预估成本(需要 cost 选项)