ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Electron localAIHandler 深度解析:把页面 Prompt API 代理到本地 LLM 的 Utility 进程机制

Electron localAIHandler 深度解析:把页面 Prompt API 代理到本地 LLM 的 Utility 进程机制 Electron localAIHandler 深度解析把页面 Prompt API 代理到本地 LLM 的 Utility 进程机制【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electronElectron 提供了localAIHandler模块运行在 Utility 进程中其核心职责是把渲染进程发出的浏览器内建 AIPrompt API请求代理到你自行实现的本地大语言模型后端。读完本文你将理解session.registerLocalAIHandler(handler)与localAIHandler.setPromptAPIHandler(handler)的完整调用链、LanguageModelUtility的接口契约create、availability、prompt、append、measureContextUsage、clone、destroy以及请求排队、按webContentsId securityOrigin去重、返回null拒绝建会话等行为细节从而能落地一个可运行的本地 AI 桥接层。模块定位与运行进程根据 localAIHandler 文档该模块属于Utility 进程参见 进程模型术语表并且明确说明This module is intended to be used by a script registered to a session viases.registerLocalAIHandler(handler)也就是说localAIHandler不是浏览器主进程BrowserAPI也不是渲染进程RendererAPI——它的宿主是一个通过Session.registerLocalAIHandler()注册到某个Session上的UtilityProcess实例所加载的脚本。这个设计把本地推理往往计算密集、甚至需要加载本地模型权重隔离在独立进程中避免阻塞浏览器主进程也避免让模型权重进入渲染进程的可信域。注册入口在 session 文档 中定义ses.registerLocalAIHandler(handler) _Experimental_ * handler UtilityProcess | nullRegisters a local AI handlerUtilityProcess. To clear the handler, callregisterLocalAIHandler(null), which will disconnect any existing Prompt API sessions and destroy anyLanguageModelUtilityinstances.在主进程侧的实现位于 Session.prototype.registerLocalAIHandlerSession.prototype.registerLocalAIHandler function (handler: UtilityProcess | null) { // ... return this._registerLocalAIHandler(handler ! null ? (handler as any)._unwrapHandle() : null); };从源码结构看主进程把UtilityProcess的内部 handle 透传给原生_registerLocalAIHandler绑定由 C 侧建立 Session 与 Utility 进程之间的 Mojo 通道这正是后续setPromptAPIHandler所处理的Prompt API 绑定请求的来路。核心方法localAIHandler.setPromptAPIHandler(promptAPIHandler)ExperimentallocalAIHandler模块当前暴露的方法只有一个setPromptAPIHandler文档原文。其签名为localAIHandler.setPromptAPIHandler(promptAPIHandler) _Experimental_ * promptAPIHandler Functiontypeof LanguageModelUtility | null * details Object * webContentsId Integer - The unique id of the WebContents calling the Prompt API. * securityOrigin string - Origin of the page calling the Prompt API. * frameToken string - The frame token of the frame calling the Prompt API. * renderProcessId Integer - The process id of the renderer process hosting the frame.参数与语义要点webContentsId调用 Prompt API 的WebContents唯一 id对应 WebContents.id。securityOrigin调用页面源origin是安全边界的关键字段。frameToken调用所在 frame 的 token对应 webFrame.frameToken可用于区分同一 origin 下的不同 frame。renderProcessId承载该 frame 的渲染进程 pid对应 webFrame.processId。方法行为文档原文语义注册回调设置一个处理渲染进程新 Prompt API 绑定请求的回调。该回调按webContentsId与securityOrigin的配对触发——即同一 WebContents 的同一源只触发一次。返回null即拒绝如果回调返回null则拒绝在该渲染进程中创建新的 Prompt API 会话。这是把是否允许某页面调用本地 AI作为策略点交给应用层的机制。失效已有会话若要作废当前已存在的 Prompt API 会话应在主进程侧调用ses.registerLocalAIHandler(null)它会断开所有 Prompt API 会话并销毁所有LanguageModelUtility实例。源码层面的印证JS 层的模块导出非常薄见 local-ai-handler.tsimport { EventEmitter } from events; const binding process._linkedBinding(electron_utility_local_ai_handler); Object.setPrototypeOf(binding, EventEmitter.prototype); module.exports binding;真正的实现是原生绑定注册入口为 electron_api_local_ai_handler.ccvoid SetPromptAPIHandler(v8::Isolate* isolate, v8::Localv8::Value val) { PromptAPIHandler handler; if (!gin::ConvertFromV8(isolate, val, handler)) { isolate-ThrowException(v8::Exception::TypeError( gin::StringToV8(isolate, Must pass a function))); return; } GetPromptAPIHandler() handler; auto cb GetHandlerChangedCallbackStorage(); if (cb) { cb.Run(); } }可以确认几个实现事实setPromptAPIHandler要求参数是一个function否则抛TypeError: Must pass a function。回调被存为进程内单例GetPromptAPIHandler()基于base::NoDestructorstd::optionalPromptAPIHandler即每个 Utility 进程只保存一个 handler。设置成功后会触发GetHandlerChangedCallbackStorage()中注册的base::RepeatingClosure——从源码结构看该回调由 AI 管理器在等待 handler 就绪时注入用来在 handler 可用时冲刷排队中的请求见下节请求排队。类型定义在 electron_api_local_ai_handler.husing PromptAPIHandler base::RepeatingCallbackv8::Localv8::Value(gin_helper::Dictionary);即PromptAPIHandler接收一个gin_helper::Dictionary对应文档中的details对象返回一个v8::Localv8::Value文档中约定为typeof LanguageModelUtility或null。请求排队机制务必尽早调用setPromptAPIHandler文档原文 NOTEIf a renderer calls the Prompt API beforesetPromptAPIHandler()has been called, the request is queued. Once the handler is set, all queued requests are flushed. If too many requests are queued, the oldest pending request is dropped and pending promises in the renderer will be rejected. To avoid this, be sure to callsetPromptAPIHandler()as early as possible.要点早到的请求会被排队不会直接丢失。handler 一旦设置所有排队请求立即冲刷。队列溢出保护排队过多时最老的待处理请求被丢弃渲染进程侧对应的 pending Promise 被 reject。最佳实践在 Utility 进程脚本一加载完成就调用setPromptAPIHandler例如// 运行于 UtilityProcess 中被 registerLocalAIHandler 注册的脚本 const { localAIHandler } require(electron); const { LanguageModelUtility } require(./my-local-llm-bridge); // 你实现的桥接 // 尽早注册避免渲染进程的 Prompt API 请求堆积到队列上限 localAIHandler.setPromptAPIHandler(async (details) { // details: { webContentsId, securityOrigin, frameToken, renderProcessId } if (!isOriginAllowed(details.securityOrigin)) { return null; // 拒绝该 origin 创建 Prompt API 会话 } return LanguageModelUtility.create({ contextWindow: 8192, // 其他 LanguageModelCreateOptions 字段 }); });注意上例中LanguageModelUtility.create(...)的具体参数形态以 LanguageModelCreateOptions 结构 为准返回null表示拒绝创建会话这是文档中明确定义的策略点。返回对象LanguageModelUtilitysetPromptAPIHandler的回调返回一个LanguageModelUtility实例或null。这个类是本地模型实现的契约载体其文档见 LanguageModelUtility。JS 侧默认实现位于 language-model-utility.ts方法体大多为空实现或返回固定值——它的意义在于定义接口形状真实推理逻辑由你在 Utility 脚本中自行补全通过覆写或继承。构造函数new LanguageModelUtility(initialState) * initialState Object * contextUsage number * contextWindow number[!NOTE] Do not use this constructor directly outside of the class itself, as it will not be properly connected to thelocalAIHandler即不要绕过LanguageModelUtility.create()直接new。这一点与 源码实现 一致——create会走一次带contextUsage: 0, contextWindow: 0初始状态的构造static async create(): PromiseLanguageModelUtility { return new LanguageModelUtility({ contextUsage: 0, contextWindow: 0 }); }静态方法LanguageModelUtility.create(options)Experimental入参optionsLanguageModelCreateOptions返回PromiseLanguageModelUtility用途用给定options创建一个LanguageModelUtility。这是你在setPromptAPIHandler回调里应当调用的入口。LanguageModelUtility.availability([options])Experimental入参optionsLanguageModelCreateCoreOptions可选返回Promisestring用途判定语言模型可用性返回以下四种字符串之一返回值含义available模型已可用downloadable可下载模型尚未就绪downloading正在下载unavailable不可用从 C 侧看这四种状态由 Mojo 的ModelAvailabilityCheckResult枚举映射而来见 utility_ai_manager.cc 中的Converterblink::mojom::ModelAvailabilityCheckResult其中available → kAvailable、unavailable → kUnavailableUnknown、downloading → kDownloading、downloadable → kDownloadable。实例属性languageModelUtility.contextUsageExperimental当前上下文窗口已占用的 token 数number。languageModelUtility.contextWindowExperimental上下文窗口总大小token 数。实例方法languageModelUtility.prompt(input, options)ExperimentalinputLanguageModelMessage[]optionsLanguageModelPromptOptions返回Promisestring | PromiseReadableStreamstring用途向模型发出一次提问可以一次性返回字符串也可以返回流式结果。languageModelUtility.append(input, options)ExperimentalinputLanguageModelMessage[]optionsLanguageModelAppendOptions返回Promiseundefined用途追加一条消息但不触发推理用于维持上下文。languageModelUtility.measureContextUsage(input, options)Experimental返回Promisenumber用途测量给定 input 会消耗多少 token便于在做上下文裁剪前预先估算。languageModelUtility.clone(options)ExperimentaloptionsLanguageModelCloneOptions返回PromiseLanguageModelUtility用途克隆一份保留当前上下文与初始 prompt 的副本便于做分支推理。languageModelUtility.destroy()Experimental用途销毁模型并中止所有在途执行。从 utility_ai_manager.cc 中引入的 Blink Mojo 头ai_common.mojom.h、ai_language_model.mojom.h、ai_proofreader.mojom.h、ai_rewriter.mojom.h、ai_summarizer.mojom.h、ai_writer.mojom.h可以推断Utility 进程内维护了一个UtilityAIManager通过 Mojo 与浏览器侧的 Blink AI 抽象对接AILanguageModelPromptType的枚举包含text/image/audio/unknown即prompt(input, ...)的输入类型可能覆盖多模态场景。具体类型映射见 PromptType Converter。一次完整调用链从渲染进程到 Utility 进程综合 session.md、localAIHandler.md、language-model-utility.md 与源码可以还原出一条清晰的调用链主进程session.defaultSession.registerLocalAIHandler(utilProc)其中utilProc是一个加载了你 Utility 脚本的UtilityProcess。实现见 session.ts透传内部 handle 到原生绑定。Utility 脚本脚本加载后尽早调用localAIHandler.setPromptAPIHandler(handler)。该调用落到 SetPromptAPIHandler把 handler 存入进程级单例并触发HandlerChangedCallback冲刷队列。渲染进程页面在某个WebContentssecurityOrigin组合下首次调用浏览器内建 Prompt API如LanguageModel相关接口。浏览器侧按webContentsId securityOrigin去重向 Utility 进程投递一个绑定请求。Utility 进程setPromptAPIHandler注册的handler(details)被调用details包含webContentsId、securityOrigin、frameToken、renderProcessId。回调返回LanguageModelUtility实例允许创建 Prompt API 会话null拒绝创建会话。会话存续期页面持续通过LanguageModelUtility实例上的prompt/append/measureContextUsage/clone与本地模型交互。清理主进程调用ses.registerLocalAIHandler(null)会断开所有 Prompt API 会话并销毁所有LanguageModelUtility实例。安全与策略建议以securityOrigin作为策略门在 handler 内维护一个允许的 origin 白名单命中即返回LanguageModelUtility.create(...)未命中返回null。这是文档给出的返回null即拒绝策略点的直接落地方式。以webContentsIdframeToken做更细粒度控制同一 origin 下不同 frame 可能承载不同可信度例如嵌入的第三方 iframe可以利用frameToken拒绝非顶层 frame 的推理请求。尽早注册Utility 脚本第一行就调用setPromptAPIHandler避免渲染进程 Prompt API 请求堆积触发最老请求被丢弃、pending Promise 被 reject。模型清理页面卸载或业务主动断开时主进程调用ses.registerLocalAIHandler(null)是最彻底、语义最明确的清理方式避免残留会话与模型资源。参考实现骨架下面给出一个可以直接在 Utility 进程脚本中使用的最小骨架接口形状以 language-model-utility.md 为准真实推理逻辑需你按本地模型 SDK 自行实现// utility-local-ai.js —— 由 UtilityProcess 加载 const { localAIHandler } require(electron); // 以 origin 作为策略门 const ALLOWED_ORIGINS new Set([ https://app.example.com, http://localhost:5173, ]); class MyLocalLanguageModel extends (require(electron).LanguageModelUtility) { async prompt(input, options) { // 将 inputLanguageModelMessage[]转换为你本地模型可接受的格式 // 支持返回 Promisestring 或 PromiseReadableStreamstring return echo: JSON.stringify(input); } async measureContextUsage(input, options) { // 用本地分词器估算 token 数 return input.reduce((n, m) n Math.max(0, (m.content || ).length / 4 | 0), 0); } } // 尽早注册避免排队溢出 localAIHandler.setPromptAPIHandler(async (details) { const { webContentsId, securityOrigin, frameToken, renderProcessId } details; if (!ALLOWED_ORIGINS.has(securityOrigin)) { return null; // 拒绝 } return require(electron).LanguageModelUtility.create({ contextWindow: 8192, expectedInputs: [text], }); });提示LanguageModelUtility的静态方法create参数类型是 LanguageModelCreateOptionsavailability参数类型是 LanguageModelCreateCoreOptions。请以此结构文档为准调整字段的实际字段名与取值范围上述骨架仅为接口形状示意。小结与要点回顾localAIHandler是运行在Utility 进程的模块唯一入口是setPromptAPIHandler(promptAPIHandler)Experimental文档。promptAPIHandler回调按webContentsIdsecurityOrigin组合触发一次details携带webContentsId、securityOrigin、frameToken、renderProcessId四个字段构成安全策略的输入。回调返回LanguageModelUtility即允许创建 Prompt API 会话返回null即拒绝。未注册 handler 前渲染进程请求会被排队handler 设置后统一冲刷排队过多时最老请求被丢弃并 reject 对应 Promise。主进程通过ses.registerLocalAIHandler(handler | null)注册或清空 handler传null会断开所有 Prompt API 会话并销毁所有LanguageModelUtility实例。相关文档与源码入口docs/api/local-ai-handler.mddocs/api/language-model-utility.mddocs/api/session.mdregisterLocalAIHandler 章节lib/utility/api/local-ai-handler.tslib/utility/api/language-model-utility.tsshell/utility/api/electron_api_local_ai_handler.ccshell/utility/api/electron_api_local_ai_handler.hshell/utility/ai/utility_ai_manager.cclib/browser/api/session.ts该 API 目前标记为_Experimental_接口在 Electron 后续版本中可能调整落地前请对照当前版本 localAIHandler 文档 与 LanguageModelUtility 文档 中的签名核对一次。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表