Electron LanguageModelUtility 完全解析:在 Utility 进程中构建本地 AI 语言模型能力
Electron 通过实验性的 Prompt API 让渲染进程可以调用本地大语言模型(LLM),而 LanguageModelUtility 是这条链路上运行在 Utility 进程中的核心类。本文基于当前仓库中 docs/api/language-model-utility.md 的完整 API 定义展开,逐一讲清构造器、静态方法、实例属性与实例方法的语义,结合 结构类型文档 与 lib/utility/api/language-model-utility.ts 的实现源码,说明它如何与 localAIHandler 协作,帮助你在 Electron 应用中落地本地 AI 推理能力。
一、类定位:Utility 进程中的本地 AI 语言模型实现
LanguageModelUtility 的官方定位是 “Implement local AI language models”(实现本地 AI 语言模型),其所属进程为 Utility 进程(见 glossary 中对 Utility 进程的定义)。
这个设计符合 Electron 的多进程模型:重计算、可能崩溃的模型推理被隔离在主进程与渲染进程之外的独立 Utility 进程中运行,主进程通过 session 注册本地 AI 处理器,渲染进程通过 Prompt API 发起请求,三者通过 IPC/Mojo 边界解耦。从源码结构看,该类的 JavaScript 侧实现由以下文件组织:
- lib/utility/api/language-model-utility.ts:
LanguageModelUtility类的 TS 实现; - lib/utility/api/module-list.ts:将
'LanguageModelUtility'与'localAIHandler'两个模块注册进 Utility 进程的模块清单; - lib/utility/api/local-ai-handler.ts:
localAIHandler模块,直接包装 C++ 侧绑定electron_utility_local_ai_handler并挂上EventEmitter原型; - lib/utility/init.ts:Utility 进程初始化时通过
v8Util.setHiddenValue注册isLanguageModel/isLanguageModelClass判断函数,供框架识别传递到各进程中的LanguageModelUtility实例。
二、构造器:new LanguageModelUtility(initialState)
构造器接收一个 initialState 对象,包含两个数字字段:
| 字段 | 类型 | 说明 |
|---|---|---|
contextUsage |
number | 当前上下文窗口中已占用的 token 数 |
contextWindow |
number | 上下文窗口总容量(token 数) |
对应源码(language-model-utility.ts#L10-L13)中,构造器只是把这两个值直接赋给实例属性:
interface LanguageModelConstructorValues {
contextUsage: number;
contextWindow: number;
}
export default class LanguageModelUtility implements Electron.LanguageModelUtility {
contextUsage: number;
contextWindow: number;
constructor(values: LanguageModelConstructorValues) {
this.contextUsage = values.contextUsage;
this.contextWindow = values.contextWindow;
}
// ...
}
文档中有一条明确的注意事项(NOTE):不要在类之外直接调用该构造器,因为这样创建的实例不会与 localAIHandler 正确建立连接。创建实例的正确方式是下一节的静态方法 LanguageModelUtility.create()。
三、静态方法
3.1 LanguageModelUtility.create(options)(实验性)
options:LanguageModelCreateOptions- 返回值:
Promise<LanguageModelUtility>
使用提供的 options 创建一个新的 LanguageModelUtility 实例。
LanguageModelCreateOptions 结构继承自 LanguageModelCreateCoreOptions,完整字段如下(结合 language-model-create-options.md 与 language-model-create-core-options.md):
| 字段 | 类型 | 是否可选 | 说明 |
|---|---|---|---|
signal |
AbortSignal |
否 | 取消信号,用于中止创建过程 |
initialPrompts |
LanguageModelMessage[] | 是 | 创建时注入的初始提示消息列表 |
expectedInputs |
LanguageModelExpected[] | 是 | 声明模型预期接收的输入模态 |
expectedOutputs |
LanguageModelExpected[] | 是 | 声明模型预期产生的输出模态 |
其中 LanguageModelExpected 的字段为:
type(string):text/image/audio三者之一;languages(string[],可选):语言列表。
从当前仓库的 JS 侧实现看,create() 目前是一个占位实现,返回 contextUsage: 0, contextWindow: 0 的空上下文实例(language-model-utility.ts#L15-L20):
static async create(): Promise<LanguageModelUtility> {
return new LanguageModelUtility({
contextUsage: 0,
contextWindow: 0
});
}
这说明 JS 层只是整个调用链的端点之一,真正的模型装载发生在 C++ 侧的 Prompt API 基础设施中,JavaScript 实现负责承载上下文状态并暴露 API 形状。
3.2 LanguageModelUtility.availability([options])(实验性)
options(可选):LanguageModelCreateCoreOptions- 返回值:
Promise<string>
探测语言模型的可用性,返回以下四个字符串之一:
| 返回值 | 含义 |
|---|---|
available |
模型已就绪,可立即使用 |
downloadable |
模型尚未下载,可以下载 |
downloading |
模型正在下载中 |
unavailable |
当前环境下模型不可用 |
该状态机对应用端做 UI 引导非常关键:在展示“开始对话”之前,先调用 availability() 判断是否需要先触发模型下载流程。当前 JS 侧实现同样为占位逻辑,固定返回 'available'(language-model-utility.ts#L22-L24),真实判定逻辑在底层实现中完成。
四、实例属性
languageModelUtility.contextUsage(实验性)
number,表示当前上下文窗口中已使用的 token 数量。
languageModelUtility.contextWindow(实验性)
number,表示上下文窗口总大小(token 数)。
这两个属性与构造器参数一一对应,是衡量“还能再塞多少上下文”的直接依据:可用余量约为 contextWindow - contextUsage。
五、实例方法
5.1 languageModelUtility.prompt(input, options)(实验性)
input:LanguageModelMessage[]options:LanguageModelPromptOptions- 返回值:
Promise<string> | Promise<import('stream/web').ReadableStream<string>>
向模型发起提示并获取响应。返回值是 Promise<string> 或 Promise<ReadableStream<string>> 的联合类型,即调用方既可以拿到完整的字符串响应,也可以消费一个文本流(适合逐字渲染的对话界面)。
LanguageModelPromptOptions 的字段(见 language-model-prompt-options.md):
| 字段 | 类型 | 是否可选 | 说明 |
|---|---|---|---|
responseConstraint |
Object | RegExp | 是 | JSON Schema 对象或正则表达式,用于约束响应必须匹配指定结构(结构化输出) |
signal |
AbortSignal |
否 | 取消信号 |
LanguageModelMessage 的结构(见 language-model-message.md):
| 字段 | 类型 | 是否可选 | 说明 |
|---|---|---|---|
role |
string | 否 | 取值 system / user / assistant |
content |
LanguageModelMessageContent[] | 否 | 消息内容数组 |
prefix |
boolean | 是 | 标记是否为前缀消息 |
LanguageModelMessageContent 则支持多模态(见 language-model-message-content.md):
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string | 取值 text / image / audio |
value |
ArrayBuffer | string | 文本内容用 string,二进制内容用 ArrayBuffer |
一个符合上述结构的调用示例(参数形状以文档为准):
const response = await lm.prompt(
[
{ role: 'system', content: [{ type: 'text', value: 'You are a helpful assistant.' }] },
{ role: 'user', content: [{ type: 'text', value: '总结这段话的核心观点。' }] }
],
{
signal: new AbortController().signal,
responseConstraint: { type: 'object', properties: { summary: { type: 'string' } } }
}
);
5.2 languageModelUtility.append(input, options)(实验性)
input:LanguageModelMessage[]options:LanguageModelAppendOptions(仅含必选字段signal: AbortSignal)- 返回值:
Promise<undefined>
向模型追加消息但不触发响应生成,用于多轮对话中维护上下文(例如把上一轮的 assistant 回复回填进会话历史)。对应 JS 实现为空的 async 方法(language-model-utility.ts#L30)。
5.3 languageModelUtility.measureContextUsage(input, options)(实验性)
input:LanguageModelMessage[]options:LanguageModelPromptOptions- 返回值:
Promise<number>
测量给定输入会占用多少 token,但不实际发起推理。这是实现“输入框剩余 token 提示”“上下文超限预警”的配套 API:先 measureContextUsage,再决定是否 prompt。JS 侧占位实现返回 0(language-model-utility.ts#L32-L34)。
5.4 languageModelUtility.clone(options)(实验性)
options:LanguageModelCloneOptions(仅含必选字段signal: AbortSignal)- 返回值:
Promise<LanguageModelUtility>
克隆一个 LanguageModelUtility,克隆出的实例保留原有的上下文与初始提示。从源码实现看,克隆即把当前的 contextUsage 与 contextWindow 复制到新实例(language-model-utility.ts#L36-L41):
async clone() {
return new LanguageModelUtility({
contextUsage: this.contextUsage,
contextWindow: this.contextWindow
});
}
典型场景是从一个已预热上下文的会话派生出独立分支(如并行探索多个问题),分支之间互不污染。
5.5 languageModelUtility.destroy()(实验性)
销毁模型,同时中止所有正在进行中的执行。JS 侧实现为空操作(language-model-utility.ts#L43),资源释放由底层完成。调用方应在会话结束、窗口关闭时显式调用,避免遗留模型状态占用 Utility 进程资源。
六、与 localAIHandler 的协作关系
LanguageModelUtility 不是孤立使用的。文档对构造器的 NOTE 提示它必须与 localAIHandler 正确连接,而 docs/api/local-ai-handler.md 说明了这条链路的另一半:
- 主进程侧:通过
ses.registerLocalAIHandler(handler)把一个脚本注册到指定 session,该脚本即运行在 Utility 进程中; - Utility 进程侧:脚本调用
localAIHandler.setPromptAPIHandler(promptAPIHandler)注册 Prompt API 绑定处理器,处理器签名为Function<typeof LanguageModelUtility | null>,接收details对象,包含:webContentsId:发起 Prompt API 调用的 WebContents 唯一 id;securityOrigin:调用页面的 origin;frameToken:发起调用 frame 的 frame token;renderProcessId:承载该 frame 的渲染进程 id。
- 请求路由:每对
webContentsId与securityOrigin触发一次绑定请求。处理器返回null即拒绝该渲染进程创建新的 Prompt API 会话;若要使既有 Prompt API 会话失效,则用ses.registerLocalAIHandler(null)清除 handler。 - 排队语义:若渲染进程在
setPromptAPIHandler()调用之前就调用了 Prompt API,请求会被排队,handler 设置后统一冲刷;排队过多时会丢弃最旧的待处理请求并拒绝渲染进程中的 pending promise。文档因此建议尽早调用setPromptAPIHandler()。
从源码结构看,Utility 进程中的 localAIHandler 模块直接是 C++ 绑定 electron_utility_local_ai_handler 的 JS 包装(lib/utility/api/local-ai-handler.ts),而 lib/utility/init.ts 中注册的 isLanguageModel / isLanguageModelClass 隐藏值,则是框架在跨进程传递 LanguageModelUtility 实例时进行类型识别的机制。仓库中的功能测试 spec/api-local-ai-handler-spec.ts 也表明,localAIHandler 模块的行为受 Prompt API 特性开关控制(features.isPromptAPIEnabled() 为真时才执行)。
七、API 使用小结
将文档定义的 API 汇总为速查表:
| 成员 | 签名 | 返回值 | 用途 |
|---|---|---|---|
new LanguageModelUtility(initialState) |
initialState: { contextUsage, contextWindow } |
实例 | 仅限框架内部使用,勿直接构造 |
LanguageModelUtility.create(options) |
options: LanguageModelCreateOptions |
Promise<LanguageModelUtility> |
创建实例(推荐入口) |
LanguageModelUtility.availability([options]) |
options?: LanguageModelCreateCoreOptions |
Promise<string> |
返回 available / downloadable / downloading / unavailable |
instance.contextUsage |
— | number |
当前已用 token 数 |
instance.contextWindow |
— | number |
上下文窗口容量(token) |
instance.prompt(input, options) |
消息数组 + Prompt 选项 | Promise<string> | Promise<ReadableStream<string>> |
发起推理,支持结构化响应约束 |
instance.append(input, options) |
消息数组 + signal | Promise<undefined> |
仅追加上下文,不触发响应 |
instance.measureContextUsage(input, options) |
消息数组 + Prompt 选项 | Promise<number> |
预估算力/上下文占用 |
instance.clone(options) |
LanguageModelCloneOptions |
Promise<LanguageModelUtility> |
保留上下文与初始提示地克隆 |
instance.destroy() |
— | 无 | 销毁模型并中止进行中的执行 |
需要说明的是:该 API 族整体标注为 Experimental,当前仓库的 JavaScript 实现(lib/utility/api/language-model-utility.ts)对 create、availability、prompt、measureContextUsage 等均为形状正确的占位实现,真实的模型装载与推理由 C++ 侧 Prompt API 基础设施承担,且相关功能受 Prompt API 特性开关控制。在应用层使用这些 API 时,应以 availability() 的结果驱动 UI 流程,用 contextUsage / contextWindow 监控上下文水位,并在会话结束时调用 destroy() 释放资源。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00