---
title: "访问元数据"
description: "从你的处理器中读取 AI 元数据——将其持久化、展示给最终用户、据此计费，或将增量进度流式传输给客户端。"
canonical_url: "https://evlog.zhcndoc.com/use-cases/ai-sdk/metadata"
---
# 访问元数据

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

宽事件已经包含完整的 `ai` 元数据，但你经常希望在处理器中获取相同的数据：将其持久化、展示给最终用户、据此计费，或将增量进度流式传输给客户端。

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

## `getMetadata()`：最终快照

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

```typescript [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`。

```typescript
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` 调用触发一次
- 模型发生错误时触发
- 在 `createEvlogIntegration` 的 `onEnd`（v7）或 `onFinish`（v6）时触发

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

```typescript [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 })
})
```

用于一次性清理：

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

## `AIMetadata` 形状

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

```typescript
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.*` 下的字段：

<table>
<thead>
  <tr>
    <th>
      宽事件字段
    </th>
    
    <th>
      来源
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ai.calls
      </code>
    </td>
    
    <td>
      调用次数
    </td>
    
    <td>
      此请求中的 AI 调用次数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.model
      </code>
    </td>
    
    <td>
      <code>
        response.modelId
      </code>
    </td>
    
    <td>
      提供响应的模型
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.models
      </code>
    </td>
    
    <td>
      所有模型 ID
    </td>
    
    <td>
      所有使用过的模型数组（仅在数量大于 1 时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.provider
      </code>
    </td>
    
    <td>
      <code>
        model.provider
      </code>
    </td>
    
    <td>
      提供商（<code>
        anthropic
      </code>
      
      、<code>
        openai
      </code>
      
      、<code>
        google
      </code>
      
       等）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.inputTokens
      </code>
    </td>
    
    <td>
      <code>
        usage.inputTokens.total
      </code>
    </td>
    
    <td>
      所有调用的输入令牌总数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.outputTokens
      </code>
    </td>
    
    <td>
      <code>
        usage.outputTokens.total
      </code>
    </td>
    
    <td>
      所有调用的输出令牌总数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.totalTokens
      </code>
    </td>
    
    <td>
      计算得出
    </td>
    
    <td>
      <code>
        inputTokens + outputTokens
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.cacheReadTokens
      </code>
    </td>
    
    <td>
      <code>
        usage.inputTokens.cacheRead
      </code>
    </td>
    
    <td>
      从提示缓存中提供的令牌数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.cacheWriteTokens
      </code>
    </td>
    
    <td>
      <code>
        usage.inputTokens.cacheWrite
      </code>
    </td>
    
    <td>
      写入提示缓存的令牌数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.reasoningTokens
      </code>
    </td>
    
    <td>
      <code>
        usage.outputTokens.reasoning
      </code>
    </td>
    
    <td>
      推理令牌（扩展思考）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.finishReason
      </code>
    </td>
    
    <td>
      <code>
        finishReason.unified
      </code>
    </td>
    
    <td>
      生成结束的原因（<code>
        stop
      </code>
      
      、<code>
        tool-calls
      </code>
      
       等）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.toolCalls
      </code>
    </td>
    
    <td>
      内容／流块
    </td>
    
    <td>
      默认是工具名称的 <code>
        string[]
      </code>
      
      ，启用 <code>
        toolInputs
      </code>
      
       时则为 <code>
        Array<{ name, input }>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.responseId
      </code>
    </td>
    
    <td>
      <code>
        response.id
      </code>
    </td>
    
    <td>
      提供商分配的响应 ID（例如 Anthropic 的 <code>
        msg_...
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.prompt
      </code>
    </td>
    
    <td>
      首次调用的 <code>
        params.prompt
      </code>
    </td>
    
    <td>
      发送给首次模型调用的提示文本格式化内容（仅在启用 <code>
        prompt
      </code>
      
       选项时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.output
      </code>
    </td>
    
    <td>
      内容／流块
    </td>
    
    <td>
      上次模型调用生成的文本（仅在启用 <code>
        output
      </code>
      
       选项时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.steps
      </code>
    </td>
    
    <td>
      步骤计数
    </td>
    
    <td>
      LLM 调用次数（仅在数量大于 1 时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.stepsUsage
      </code>
    </td>
    
    <td>
      按步骤累积
    </td>
    
    <td>
      每个步骤的令牌和工具调用明细（仅在步骤数大于 1 时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.msToFirstChunk
      </code>
    </td>
    
    <td>
      流计时
    </td>
    
    <td>
      首个文本块所需时间（仅流式传输时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.msToFinish
      </code>
    </td>
    
    <td>
      流计时
    </td>
    
    <td>
      流总耗时（仅流式传输时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.tokensPerSecond
      </code>
    </td>
    
    <td>
      计算得出
    </td>
    
    <td>
      每秒输出令牌数（仅流式传输时）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.error
      </code>
    </td>
    
    <td>
      错误捕获
    </td>
    
    <td>
      模型调用失败时的错误消息
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.tools
      </code>
    </td>
    
    <td>
      Telemetry 集成
    </td>
    
    <td>
      每个工具的 <code>
        { name, durationMs, success, error? }
      </code>
      
      （需要 <code>
        createEvlogIntegration
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.totalDurationMs
      </code>
    </td>
    
    <td>
      Telemetry 集成
    </td>
    
    <td>
      生成总耗时（需要 <code>
        createEvlogIntegration
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.embedding
      </code>
    </td>
    
    <td>
      <code>
        captureEmbed
      </code>
    </td>
    
    <td>
      <code>
        { model?, tokens, dimensions?, count? }
      </code>
      
      ——嵌入元数据
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.estimatedCost
      </code>
    </td>
    
    <td>
      计算得出
    </td>
    
    <td>
      以美元计的预估成本（需要 <code>
        cost
      </code>
      
       选项）
    </td>
  </tr>
</tbody>
</table>

---

- [概览](https://evlog.zhcndoc.com/use-cases/ai-sdk/overview)
- [选项](https://evlog.zhcndoc.com/use-cases/ai-sdk/options)


## Sitemap

See the full [sitemap](https://evlog.zhcndoc.com/sitemap.md) for all pages.
