下面的每种模式都使用相同的 createAILogger(log) 设置。用 ai.wrap() 包裹模型,middleware 会自动在 wide event 上累积 token、工具和耗时。
streamText
最常见的模式——带完整可观测性的流式聊天:
server/api/chat.post.ts
import { streamText } from 'ai'
import { createAILogger } from 'evlog/ai'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const ai = createAILogger(log)
const { messages } = await readBody(event)
log.set({ action: 'chat', messagesCount: messages.length })
const result = streamText({
model: ai.wrap('anthropic/claude-sonnet-4.6'),
messages,
onFinish: ({ text }) => {
saveConversation(text)
},
})
return result.toTextStreamResponse()
})
middleware 不会触碰你的 onFinish 回调——你的代码会照常运行。
generateText
同步生成。middleware 会自动捕获结果:
server/api/summarize.post.ts
import { generateText } from 'ai'
import { createAILogger } from 'evlog/ai'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const ai = createAILogger(log)
const result = await generateText({
model: ai.wrap('anthropic/claude-sonnet-4.6'),
prompt: '总结这份文档',
})
return { text: result.text }
})
多步骤代理
middleware 会自动为每一步触发。步骤、工具调用和 token 会在整个 agent 循环中累积:
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, {
toolInputs: { maxLength: 500 },
})
const agent = new ToolLoopAgent({
model: ai.wrap('anthropic/claude-sonnet-4.6'),
tools: { searchWeb, queryDatabase },
stopWhen: stepCountIs(5),
})
return createAgentUIStreamResponse({
agent,
uiMessages: messages,
})
})
3 步代理运行后的 wide event:
Wide Event
{
"ai": {
"calls": 3,
"steps": 3,
"model": "claude-sonnet-4.6",
"provider": "anthropic",
"inputTokens": 4500,
"outputTokens": 1200,
"totalTokens": 5700,
"finishReason": "stop",
"toolCalls": [
{ "name": "searchWeb", "input": { "query": "TypeScript 6.0 features" } },
{ "name": "queryDatabase", "input": { "sql": "SELECT * FROM docs WHERE topic = 'typescript'" } },
{ "name": "searchWeb", "input": { "query": "TypeScript 6.0 release date" } }
],
"responseId": "msg_01XFDUDYJgAACzvnptvVoYEL",
"stepsUsage": [
{ "model": "claude-sonnet-4.6", "inputTokens": 1200, "outputTokens": 300, "toolCalls": ["searchWeb"] },
{ "model": "claude-sonnet-4.6", "inputTokens": 1500, "outputTokens": 400, "toolCalls": ["queryDatabase", "searchWeb"] },
{ "model": "claude-sonnet-4.6", "inputTokens": 1800, "outputTokens": 500 }
],
"msToFirstChunk": 312,
"msToFinish": 8200,
"tokensPerSecond": 146
}
}
结合
createEvlogIntegration 一起使用,还可以捕获每个工具的执行耗时以及 agent 的总墙钟时间。RAG(embed + generate)
Embedding 模型使用不同的类型,不能通过 middleware 包裹。请改用 captureEmbed:
server/api/rag.post.ts
import { embed, generateText } from 'ai'
import { useLogger } from 'evlog'
import { createAILogger } from 'evlog/ai'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const ai = createAILogger(log)
const { embedding, usage } = await embed({
model: openai.embedding('text-embedding-3-small'),
value: query,
})
ai.captureEmbed({
usage,
model: 'text-embedding-3-small',
dimensions: 1536,
})
const docs = await findSimilar(embedding)
const result = await generateText({
model: ai.wrap('anthropic/claude-sonnet-4.6'),
prompt: buildPrompt(docs),
})
return { text: result.text }
})
对于 embedMany,传入批量数量:
const { embeddings, usage } = await embedMany({
model: openai.embedding('text-embedding-3-small'),
values: documents,
})
ai.captureEmbed({ usage, model: 'text-embedding-3-small', count: documents.length })
多个模型
分别包裹每个模型——它们共享同一个累加器。当使用超过一个模型时,wide event 会同时包含 model(最后一个模型)和 models(所有唯一模型):
const ai = createAILogger(log)
const fast = ai.wrap('anthropic/claude-haiku-4.5')
const smart = ai.wrap('anthropic/claude-sonnet-4.6')
const classification = await generateText({ model: fast, prompt: classifyPrompt })
const response = await generateText({ model: smart, prompt: detailedPrompt })
{
"ai": {
"calls": 2,
"model": "claude-sonnet-4.6",
"models": ["claude-haiku-4.5", "claude-sonnet-4.6"],
"provider": "anthropic",
"inputTokens": 450,
"outputTokens": 300,
"totalTokens": 750
}
}
模型对象支持
如果你更喜欢显式导入,wrap() 也接受来自 provider SDK 的模型对象:
server/api/chat.post.ts
import { anthropic } from '@ai-sdk/anthropic'
const model = ai.wrap(anthropic('claude-sonnet-4.6'))