Cherry Studio ai-core 提供商调用观测(Per-Provider-Call Observation)指南
Cherry Studio ai-core 提供商调用观测Per-Provider-Call Observation指南【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio引言Cherry Studio 的多提供商桌面客户端依赖自研的cherrystudio/ai-core运行时来统一调度文本、图像、嵌入和重排序调用。本指南围绕该包最新的 patch 变更——observe-ai-core-provider-calls——展开它为图像、嵌入和重排序运行时引入了可选的按提供商调用per-provider-call观测钩子携带调用身份invocation identity、可用时的用量usage以及按调用计量的耗时和性能投影。通过本文你将掌握如何通过onProviderCall回调捕获每次提供商调用的元数据理解其底层实现与事件模型并将其接入 Cherry Studio 的用量与计费体系。变更内容一览变更包cherrystudio/ai-corepackages/aiCore变更类型patch向后兼容的增量增强核心能力为以下三种运行时新增可选的观测钩子不改变现有调用行为generateImage图像生成embedMany批量嵌入rerank重排序事件携带字段调用身份requestId、提供商与模型标识、可用时的用量usage、完成耗时timeCompletionMs与完成时间戳completedAt用于按调用级别的用量和性能投影。该变更对应的 changeset 文件为.changeset/observe-ai-core-provider-calls.md声明了对cherrystudio/ai-core的 patch 级改动。钩子类型与事件模型在packages/aiCore/src/core/runtime/types.ts中定义了完整的观测契约RuntimeProviderCallHandler观测回调类型签名(event: RuntimeProviderCallEvent) void。RuntimeProviderCallEvent按模态区分的判别联合discriminated union分别对应嵌入、图像与重排序export type RuntimeProviderCallEvent | { modality: embedding requestId: string providerId: string modelId: string usage?: { tokens: number } metrics: { timeCompletionMs: number } completedAt: number } | { modality: image requestId: string providerId: string modelId: string imageCount: number usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number } metrics: { timeCompletionMs: number } completedAt: number } | { modality: rerank requestId: string providerId: string modelId: string metrics: { timeCompletionMs: number } completedAt: number }各字段含义字段说明modality调用模态embedding/image/rerankrequestId调用身份格式ai-core:modality:uuid由运行时生成providerId执行器配置的提供商 IDmodelId实际执行调用的模型 IDusage提供商返回的用量可用时imageCount图像模态特有本次调用生成的图片数量metrics.timeCompletionMs从调用开始到完成的耗时毫秒completedAt完成时间戳毫秒嵌入模态的usage.tokens表示批次令牌数图像模态的usage为可选的输入/输出/总令牌数重排序模态目前不携带 usage。参数接入方式onProviderCall作为可选参数注入到三个方法的高阶参数类型中见packages/aiCore/src/core/runtime/types.tsexport type generateImageParams OmitParameterstypeof generateImage[0], model { model: string | ImageModelV3 experimental_download?: Experimental_DownloadFunction onProviderCall?: RuntimeProviderCallHandler } export type EmbedManyParams OmitParameterstypeof embedMany[0], model { model: string | EmbeddingModelV3 onProviderCall?: RuntimeProviderCallHandler } export type RerankParamsVALUE extends JSONObject | string string Omit Parameterstypeof rerankVALUE[0], model { model: string | RerankingModelV3 onProviderCall?: RuntimeProviderCallHandler }这些类型都保留了对model的字符串或模型对象二选一支持字符串 ID 会通过执行器的提供商注册表解析为具体模型。底层实现原理RuntimeExecutorpackages/aiCore/src/core/runtime/executor.ts负责实际的钩子注入三个方法的实现各有特点图像生成generateImage通过 AI SDK 的wrapImageModel中间件包装解析后的图像模型const observedModel onProviderCall ? wrapImageModel({ model: resolvedModel, middleware: { specificationVersion: v3, wrapGenerate: async ({ doGenerate, model: activeModel }) { const startedAt performance.now() const result await doGenerate() emitProviderCall(onProviderCall, { modality: image, requestId: ai-core:image:${crypto.randomUUID()}, providerId: this.config.providerId, modelId: activeModel.modelId, imageCount: result.images.length, ...(result.usage ? { usage: result.usage } : {}), metrics: { timeCompletionMs: Math.max(0, Math.round(performance.now() - startedAt)) }, completedAt: Date.now() }) return result } } }) : resolvedModel批量嵌入embedMany通过wrapEmbeddingModel中间件实现同样的计时与事件发射模式const observedModel onProviderCall ? wrapEmbeddingModel({ model: embeddingModel, middleware: { specificationVersion: v3, wrapEmbed: async ({ doEmbed, model }) { const startedAt performance.now() const result await doEmbed() emitProviderCall(onProviderCall, { modality: embedding, requestId: ai-core:embedding:${crypto.randomUUID()}, providerId: this.config.providerId, modelId: model.modelId, ...(result.usage ? { usage: result.usage } : {}), metrics: { timeCompletionMs: Math.max(0, Math.round(performance.now() - startedAt)) }, completedAt: Date.now() }) return result } } }) : embeddingModel重排序rerank与上述两个不同重排序直接在_rerank调用成功后发射事件目前 AI SDK 尚未提供等价的 wrap 中间件const startedAt performance.now() const result await _rerankVALUE({ model: rerankingModel, ...options }) emitProviderCall(onProviderCall, { modality: rerank, requestId: ai-core:rerank:${crypto.randomUUID()}, providerId: this.config.providerId, modelId: rerankingModel.modelId, metrics: { timeCompletionMs: Math.max(0, Math.round(performance.now() - startedAt)) }, completedAt: Date.now() })注意rerank仅在提供商调用成功返回后才发射事件若调用抛错事件不会发出。安全发射机制所有模态的事件发射都经由统一的emitProviderCall辅助函数function emitProviderCall(handler: RuntimeProviderCallHandler | undefined, event: RuntimeProviderCallEvent): void { try { handler?.(event) } catch { // Usage observation is best-effort and must never change a successful AI result. } }这段注释明确了设计意图用量观测是尽力而为best-effort的绝不允许改变已经成功的 AI 调用结果。即使观测处理器内部抛出异常也会被静默吞掉原调用结果不受任何影响。测试用例验证packages/aiCore/src/core/runtime/__tests__/providerCall.test.ts使用test-utils提供的 mock 模型createMockProviderV3、createMockEmbeddingModel、createMockImageModel、createMockRerankingModel对观测行为进行了系统性验证嵌入每次 SDK 批次发射一个事件。向embedMany传入 5 条文本、模型maxEmbeddingsPerCall 2AI SDK 内部拆分为 3 次底层doEmbed调用观测事件同样为 3 个且每个事件的usage.tokens分别为[2, 2, 1]3 个requestId互不相同。这印证了事件是按每次实际提供商调用而非整个高层请求粒度发射的。图像每次 SDK 批次发射一个事件。n 5、maxImagesPerCall 2时底层调用 3 次事件 3 个imageCount为[2, 2, 1]。重排序仅在成功后发射。正常调用发射 1 个事件当doRerankmock 为 reject抛provider failed时事件列表为空。观测处理器抛错不影响结果。onProviderCall内主动throw new Error(analytics unavailable)embedMany依然正常 resolve返回的embeddings与usage完整保留。在 Cherry Studio 主进程中的接入实践观测钩子并非孤立能力而是已被 Cherry Studio 主进程的 AI 服务src/main/ai/AiService.ts实际消费。其接入模式如下捕获上下文的工厂函数createProviderCallHandler将每次事件落盘为一条用量记录function createProviderCallHandler(context: AiUsageCaptureContext): RuntimeProviderCallHandler { return (event: RuntimeProviderCallEvent) { aiUsageRecordService.recordInvocation({ requestId: event.requestId, context, modality: event.modality, ...(event.modality embedding event.usage ? { usage: { inputTokens: event.usage.tokens, totalTokens: event.usage.tokens } } : event.modality image event.usage ? { usage: { ...(event.usage.inputTokens ! undefined ? { inputTokens: event.usage.inputTokens } : {}), ...(event.usage.outputTokens ! undefined ? { outputTokens: event.usage.outputTokens } : {}), ...(event.usage.totalTokens ! undefined ? { totalTokens: event.usage.totalTokens } : {}) } } : {}), ...(event.modality image ? { imageCount: event.imageCount } : {}), metrics: event.metrics, completedAt: event.completedAt }) } }它在generateImage、embedMany、rerank三处调用点分别以imageUsageContext和usageContext传入AiService.ts第 975、1150、1197 行附近。计费与用量记录的完整性src/main/ai/hooks/billingHook.ts定义了可计费操作的覆盖矩阵AI_USAGE_RECORD_OPERATION_COVERAGE明确标注了三种模态的捕获路径为ai-core-handlerexport const AI_USAGE_RECORD_OPERATION_COVERAGE { streamText: { status: recorded, modality: language, capture: language-middleware }, generateText: { status: recorded, modality: language, capture: language-middleware }, embedMany: { status: recorded, modality: embedding, capture: ai-core-handler }, generateImage: { status: recorded, modality: image, capture: ai-core-handler }, rerank: { status: recorded, modality: rerank, capture: ai-core-handler } } as const语言模态streamText/generateText通过语言模型中间件捕获而嵌入、图像、重排序正是通过本文介绍的ai-core-handler路径捕获。请求 ID 命名空间docs/references/ai/ai-usage-records.md记录了请求 ID 的命名空间约定其中 aiCore 提供商处理器的事件 ID 格式为ai-core:modality:uuid这与此前executor.ts中的实现完全吻合。自定义接入示例若要自行接入观测钩子可按以下模式调用import { RuntimeExecutor } from cherrystudio/ai-core const executor RuntimeExecutor.create(openai, provider, { apiKey: sk-... }) // 图像生成观测 await executor.generateImage({ model: dall-e-3, prompt: a cat, n: 1, onProviderCall: (event) { console.log(event.modality, event.requestId, event.imageCount, event.metrics.timeCompletionMs) } }) // 批量嵌入观测 await executor.embedMany({ model: text-embedding-3-small, values: [document 1, document 2], onProviderCall: (event) { if (event.modality embedding event.usage) { console.log(tokens: ${event.usage.tokens}) } } }) // 重排序观测仅在成功后触发 await executor.rerank({ model: reranker, query: query, documents: [a, b], onProviderCall: (event) { console.log(event.modality, event.metrics.timeCompletionMs, event.completedAt) } })局限性与注意事项rerank模态目前不携带usage字段事件仅在调用成功后发射失败调用不会产生观测事件。embedding/image事件仅在提供商返回了usage时才会带上usage字段通过条件展开...(result.usage ? { usage: result.usage } : {})。观测处理器为同步函数建议在其中执行轻量逻辑如记录日志、写入用量库重活应异步化避免拖慢主流程。钩子是可选的不传onProviderCall时wrapImageModel/wrapEmbeddingModel包装与计时逻辑整体跳过对调用路径零开销。相关文件索引变更声明.changeset/observe-ai-core-provider-calls.md运行时实现packages/aiCore/src/core/runtime/executor.ts事件与参数类型定义packages/aiCore/src/core/runtime/types.ts观测行为测试packages/aiCore/src/core/runtime/__tests__/providerCall.test.ts主进程接入src/main/ai/AiService.ts计费覆盖矩阵src/main/ai/hooks/billingHook.ts用量记录文档docs/references/ai/ai-usage-records.md【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

VeighNa(vnpy)OptionMaster 期权波动率交易模块实战指南:从组合配置到电子眼自动套利

VeighNa(vnpy)OptionMaster 期权波动率交易模块实战指南:从组合配置到电子眼自动套利

VeighNa(vnpy)OptionMaster 期权波动率交易模块实战指南:从组合配置到电子眼自动套利 【免费下载链接】vnpy 基于Python的开源量化交易平台开发框架 项目地址: https://gitcode.com/vnpy/vnpy OptionMaster(期权波动率交易…

2026/9/22 2:58:53 阅读更多 →
免费不限量AI绘画工具实测:Raphael AI零成本出图全流程指南

免费不限量AI绘画工具实测:Raphael AI零成本出图全流程指南

最近在整理自己的 AIGC 工具箱,本意是找一个备用的免费出图工具,结果发现 Raphael AI 这个名字反复出现在各类分享帖里。它的卖点非常直接:免费、不限量、全球首个。说实话,我对"首个"这种说法一向保持警惕,…

2026/9/22 2:59:47 阅读更多 →
Node.js 8.9.2 (LTS) 发布公告深度解读:console 监听器修复、http2 头校验改进与发布文档生成链路

Node.js 8.9.2 (LTS) 发布公告深度解读:console 监听器修复、http2 头校验改进与发布文档生成链路

Node.js 8.9.2 (LTS) 发布公告深度解读:console 监听器修复、http2 头校验改进与发布文档生成链路 【免费下载链接】nodejs.org The Node.js Website 项目地址: https://gitcode.com/GitHub_Trending/no/nodejs.org 本篇技术指南以 Node.js 官方站点 nodejs.…

2026/9/21 9:22:18 阅读更多 →

最新新闻

搞定圣诞邮件发送报错:图解原理与实战避坑指南

搞定圣诞邮件发送报错:图解原理与实战避坑指南

搞定圣诞邮件发送报错:图解原理与实战避坑指南 盯着屏幕上一行行红色的 StackTrace,是不是脑子都炸了? ConnectionRefused 、 TimeoutException 、 AuthenticationFailed…

2026/9/22 4:16:45 阅读更多 →
一文搞懂如何去除

一文搞懂如何去除

5个实战技巧教你彻底去除冗余逻辑实现性能优化 刚接手一个老项目,配置环境就卡半天。依赖冲突、版本不匹配,光 npm install 和 pip install 就得耗去两小时。等你终于跑通 Hello World,打开代码一看,满屏的…

2026/9/22 4:16:45 阅读更多 →
唱吧ipad版保姆级教程:3步搞定面试高频原理

唱吧ipad版保姆级教程:3步搞定面试高频原理

唱吧ipad版保姆级教程:3步搞定面试高频原理 面试被问原理答不上来?别慌,今天这篇【唱吧ipad版】保姆级教程,带你从0到1拆解其核心音频处理逻辑。…

2026/9/22 4:16:44 阅读更多 →
5分钟搞懂fgo童谣:保姆级教程带你拆解源码

5分钟搞懂fgo童谣:保姆级教程带你拆解源码

5分钟搞懂fgo童谣:保姆级教程带你拆解源码 报错一堆看不懂,StackTrace像天书一样滚过屏幕,这是无数开发者在深夜调试时的真实写照。特别是当涉及到图形化界面或者复杂的依赖注入时,那个熟悉的 fgo童谣…

2026/9/22 4:16:44 阅读更多 →
面试必问:搞懂pr打包工程文件,告别只会看教程

面试必问:搞懂pr打包工程文件,告别只会看教程

面试必问:搞懂pr打包工程文件,告别只会看教程 看了一堆教程还是不会写项目?这大概是每个转行或初学者的噩梦。你以为学会了语法,敲了两百行 Hello World,结果面试官一句“pr打包工程文件怎么配?”,你直接大脑一片空白。这不仅是…

2026/9/22 4:16:43 阅读更多 →
三星相册实战项目:解决版本升级API全变导致的卡顿与内存溢出

三星相册实战项目:解决版本升级API全变导致的卡顿与内存溢出

三星相册实战项目:解决版本升级API全变导致的卡顿与内存溢出 版本升级后 API 全变了,你的三星相册加载速度是不是又慢了一倍?别急,这不是玄学,是代码没跟上底层逻辑。在最近的 实战项目…

2026/9/22 4:15:41 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →