【免费下载链接】evlog
Digging through logs is not observability. It's hope — wide events, structured errors, TypeScript-first, every runtime.
evlog 是一个 TypeScript 优先的结构化日志库,核心理念是"每个请求一条宽事件(wide event)"。配合官方 AI SDK,evlog/ai会把每次 AI 调用的 token 用量、工具调用、流式速度与成本自动汇总进同一条宽事件——无需手写埋点,也无需额外追踪。你是否遇到过这样的场景:AI 接口跑了一分钟,你却不知道它消耗了多少 token、调了哪些工具、花了多少钱?本文带你用 3 步完成接入,让 AI 调用可观测变得简单。
一、为什么 AI 调用需要"一条完整事件" 🔍
AI 应用里的传统埋点方式,痛点很明显:
- token 是散的:输入、输出、推理 token 藏在响应各处,翻账单要逐个对;
- 成本是黑盒:单次请求花了多少钱无法实时知道,做预算告警和计费都很吃力;
- 工具调用难复盘:一个多步 Agent 调了 5 次工具,却看不出哪一步慢、哪一步失败;
- 日志是碎的:一次聊天请求散落几十行日志,凌晨三点只能靠 grep 找信号。
evlog 的思路正好相反:与其翻日志找线索,不如每次只留一条完整事件。
二、三步接入:两行代码开始记录 AI 调用 ⚡
2.1 安装依赖
添加 AI SDK 即可(evlog 通常在项目中已就绪):
npm install ai2.2 用中间件包裹模型
加两行、改一个参数,接入即完成:
import { useLogger } from 'evlog' import { createAILogger } from 'evlog/ai' const log = useLogger(event) // evlog 的请求日志器 const ai = createAILogger(log) // AI 可观测日志器 const result = streamText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), messages, })ai.wrap()返回一个带中间件的模型,之后generateText、streamText、ToolLoopAgent的每次调用都会自动累计数据。中间件不触碰你的onFinish回调、提示词和响应,属于纯"旁观式"记录。
2.3 拿回完整事件
请求结束时,宽事件自动携带ai字段发出:
{ "method": "POST", "path": "/api/chat", "status": 200, "durationMs": 4512, "ai": { "calls": 1, "model": "claude-sonnet-4.6", "provider": "anthropic", "inputTokens": 3312, "outputTokens": 814, "totalTokens": 4126, "reasoningTokens": 225, "finishReason": "stop", "msToFirstChunk": 234, "msToFinish": 4500, "tokensPerSecond": 180 } }三、evlog/ai 会自动捕获哪些数据 📊
不做任何额外配置,ai.wrap()就已经记录了这些内容:
| 数据 | 宽事件字段 | 说明 |
|---|---|---|
| token 用量 | ai.inputTokens/ai.outputTokens/ai.totalTokens | 同一请求内多次调用自动累计 |
| 缓存命中 | ai.cacheReadTokens/ai.cacheWriteTokens | 提示词缓存的读写 token |
| 推理 token | ai.reasoningTokens | 扩展思考产生的 token |
| 模型信息 | ai.model/ai.provider/ai.models | 最后使用的模型、提供方、多模型场景的全部模型 |
| 工具调用 | ai.toolCalls | 默认记录工具名,开启后可附带调用入参 |
| 流式指标 | ai.msToFirstChunk/ai.tokensPerSecond | 首 token 时延、输出速度 |
| 结束原因 | ai.finishReason | stop、tool-calls、error等 |
| 错误 | ai.error | 模型调用失败信息,先写入事件再重新抛出 |
对流式响应,evlog 在 Nuxt、Nitro、Next.js 等框架上会把宽事件的发出延迟到流结束后,保证最终的 token 数据和请求上下文落在同一条事件上。
四、估算 AI 调用成本:加一张价格表 💰
cost选项接受按"每百万 token 美元"计价的价格表,宽事件会自动算出ai.estimatedCost:
const ai = createAILogger(log, { cost: { 'claude-sonnet-4.6': { input: 3, output: 15 }, 'gpt-4o': { input: 2.5, output: 10 }, }, }) // 在处理器里随时读取本次调用的成本 const cost = ai.getEstimatedCost() console.log(`本次调用花费 $${cost?.toFixed(4)}`)由此可以实现"昂贵调用前先提醒用户"、按用户汇总 AI 支出等场景。小建议:把价格表和模型选择放在同一个文件里维护,换模型时价格同步更新,避免多条路由各自维护导致漂移。
五、多步 Agent、RAG 与多模型场景 🔧
5.1 多步 Agent 全程留痕
使用ToolLoopAgent时,中间件对每一步自动记录,宽事件里能看到逐步明细:
{ "ai": { "calls": 3, "steps": 3, "toolCalls": ["searchWeb", "queryDatabase", "searchWeb"], "stepsUsage": [ { "model": "claude-sonnet-4.6", "inputTokens": 1200, "outputTokens": 300, "toolCalls": ["searchWeb"] }, { "model": "claude-sonnet-4.6", "inputTokens": 1500, "outputTokens": 400, "toolCalls": ["queryDatabase", "searchWeb"] } ] } }想看到每个工具收到的入参(调试 Agent 行为时非常有用),开启toolInputs:
const ai = createAILogger(log, { toolInputs: { maxLength: 500 } })5.2 RAG 里的 embedding 调用
embedding 模型不能通过中间件包裹,用专门方法手动上报即可:
const { embedding, usage } = await embed({ model: embeddingModel, value: query }) ai.captureEmbed({ usage, model: 'text-embedding-3-small', dimensions: 1536 })5.3 多模型路由
每个模型各自wrap一次即可,它们共享同一个累计器。事件里会同时出现ai.model(最后一次使用的模型)和ai.models(出现过的全部模型),路由行为一目了然。
六、更深度的遥测:工具执行耗时与总生成时长 🚀
ai.wrap()覆盖 token、模型与流式指标;若还想拿到每个工具的执行耗时、成功/失败以及整次生成的总墙上时间,再叠加一个集成即可:
const result = await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), tools: { getWeather, searchDB }, telemetry: { integrations: [createEvlogIntegration(ai)], }, })宽事件随之多出这些字段:
{ "ai": { "tools": [ { "name": "getWeather", "durationMs": 150, "success": true }, { "name": "searchDB", "durationMs": 45, "success": true } ], "totalDurationMs": 2340 } }在 AI SDK v7 上,该集成还会自动捕获 embedding、流式中止(ai.finishReason: 'abort')和不可恢复错误,两者组合即完整的 AI 可观测。
七、新手避坑:4 条最佳实践 ✅
- 敏感内容默认不开启:
prompt、output、toolInputs三个选项默认关闭,模型收发过的内容不会流入日志目的地,除非你点名要; - 要捕获就先加
maxLength和transform:两者内置支持截断与脱敏。工具入参常含 SQL、密钥和用户数据,生产环境建议先开截断再开捕获; - 价格表单一来源:
cost表与模型配置放一起维护,改模型和改价格发生在同一处; - 错误也在同一条事件:模型调用失败会先写入
ai.error再重新抛出,你的try/catch照常工作,排查时一条事件同时看到错误、token 和工具调用。
八、相关资源 📚
- 官方文档 · AI SDK 集成总览:apps/docs/content/5.use-cases/2.ai-sdk/01.overview.md
- 用法模式(流式、Agent、RAG、多模型):apps/docs/content/5.use-cases/2.ai-sdk/02.usage.md
- 全部选项(工具入参、提示词/输出捕获、成本表):apps/docs/content/5.use-cases/2.ai-sdk/03.options.md
- 在处理器内读取元数据(
getMetadata/getEstimatedCost/onUpdate):apps/docs/content/5.use-cases/2.ai-sdk/04.metadata.md - 深度遥测(工具耗时、总生成时长):apps/docs/content/5.use-cases/2.ai-sdk/05.telemetry.md
- 核心实现(
createAILogger入口):packages/evlog/src/ai/index.ts#L490-L531 - 宽事件概念详解:apps/docs/content/2.learn/2.wide-events.md
从今天起,让每次 AI 调用都留下一条完整、可检索、可分析的事件——token、工具调用与成本,尽收眼底。
【免费下载链接】evlog
Digging through logs is not observability. It's hope — wide events, structured errors, TypeScript-first, every runtime.
相关推荐
evlog × eve agent可观测性:每轮对话一条宽事件,token与工具调用全透明
evlog × eve agent可观测性:每轮对话一条宽事件,token与工具调用全透明 evlog 是 TypeScript 优先的宽事件(Wide Eve
DLSS Swapper 完整教程:不等游戏更新,一键切换 DLSS 版本
DLSS Swapper 完整教程:不等游戏更新,一键切换 DLSS 版本 你想让老游戏用上新版 DLSS,但游戏自带的 DLL 还停在旧版本,甚至不知道文件藏
桌面应用PostHog 前端中的 Tool 调用事件总线:用 toolStreamEventsLogic 让页面实时响应 AI Agent 的每一次工具调用
PostHog 前端中的 Tool 调用事件总线:用 toolStreamEventsLogic 让页面实时响应 AI Agent 的每一次工具调用 导读 当用
数据分析后端前端数据可视化大数据
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考