Electron LanguageModelMessage 结构详解:本地 AI(Prompt API)消息模型与多模态内容设计
在 Electron 的实验性本地 AI 能力(Local AI Handler / Prompt API)中,LanguageModelMessage 是贯穿渲染进程、主进程与 Utility 进程的消息数据骨架:Web 页面通过 Chromium 的 LanguageModel API 发起提示,请求经代理层转换后,最终以 LanguageModelMessage[] 的形式传入你在 Utility 进程中实现的 LanguageModelUtility。读完本文,你将完全掌握该结构的字段语义(role、content、prefix)、它与 LanguageModelMessageContent 的多模态内容模型的关系、它在 prompt / append / measureContextUsage 等 API 中的使用方式,以及 Electron 底层 Mojo 转换的实现细节和测试验证行为。
一、LanguageModelMessage 字段定义
根据 LanguageModelMessage Object 文档,该结构定义如下:
rolestring — 消息的角色,取值为以下之一:system— 系统指令消息,用于设定模型的行为准则user— 用户消息assistant— 模型(助手)的回复消息
contentLanguageModelMessageContent[] — 消息内容的数组prefixboolean(可选)— 标记该消息是否为前缀消息
一个典型的完整消息示例如下(与 Electron 测试用例 spec/api-local-ai-handler-spec.ts 中构造的输入一致):
const messages = [
{ role: 'system', content: [{ type: 'text', value: 'You are Electron AI' }] },
{ role: 'user', content: [{ type: 'text', value: 'hello' }] },
{ role: 'assistant', content: [{ type: 'text', value: 'hi' }] }
]
prefix 字段的实际语义
prefix 是可选字段,在 Electron 的实现中它并不是由你的处理器代码设置,而是由框架侧管理。从 spec/api-local-ai-handler-spec.ts 的测试断言可以看到:当渲染进程把 LanguageModelMessage[] 传入 model.prompt() 后,Utility 进程 handler 实际收到的每一条消息都会被补齐 prefix: false:
// 测试断言:handler 收到的 input 等于原消息 + prefix: false
expect(message.input).to.deep.equal(input.map((msg) => ({ ...msg, prefix: false })));
同理,LanguageModel.create() 的 initialPrompts 在 测试代码 中也会被映射为 { ...prompt, prefix: false } 再交给处理器。也就是说:prefix 是 Electron 向 handler 暴露的消息元信息位,标识该消息是否属于"前缀"(如初始提示)类别,普通会话消息统一以 false 传入,你的实现代码主要消费 role 与 content 即可,无需手动维护该字段。
二、content 的多模态内容模型
content 字段的元素类型是 LanguageModelMessageContent,其定义为:
typestring — 内容类型,取值为以下之一:text— 文本内容image— 图像内容audio— 音频内容
valueArrayBuffer | string — 内容值;文本类型为string,二进制类型(图像/音频)为ArrayBuffer
一条消息的 content 是数组而非单个值,因此可以同时组合多种内容。例如带图像输入的多模态消息(前提是在 BrowserWindow 中启用了多模态特性,见下文):
{
role: 'user',
content: [
{ type: 'text', value: '描述这张图片' },
{ type: 'image', value: imageArrayBuffer }
]
}
值得注意的是:文档定义的三种内容类型由 Chromium Prompt API 的 AIPromptAPIMultimodalInput 特性决定。根据 Local AI Handler 教程 的说明,要让渲染进程传入多模态输入,需要在 webPreferences 中同时启用相关 Blink 特性:
const win = new BrowserWindow({
webPreferences: {
// 启用 Prompt API;如需多模态输入,再追加 'AIPromptAPIMultimodalInput'
enableBlinkFeatures: 'AIPromptAPI,AIPromptAPIMultimodalInput'
}
})
你的 LanguageModelUtility.prompt() 实现在收到 image / audio 类型的 content 后,需要自行将其交给底层模型(例如 llama.cpp 的视觉编码器)。
三、LanguageModelMessage 在 Local AI Handler 架构中的位置
LanguageModelMessage 不是孤立结构,它嵌入在三进程架构的数据流中(详见 Local AI Handler 教程 与 localAIHandler API 参考):
- 渲染进程(Renderer):Web 页面按标准 Prompt API 调用
LanguageModel.create()与model.prompt(),输入可以是字符串、单条消息或LanguageModelMessage[]; - 主进程(Main):通过
ses.registerLocalAIHandler(handler)把由utilityProcess.fork()派生的 Utility 进程注册为该 session 的 AI 处理器,负责代理转发; - Utility 进程:你的脚本通过
localAIHandler.setPromptAPIHandler()注册LanguageModelUtility子类,其方法接收LanguageModelMessage[]并返回响应。
在 Utility 进程中,所有接收 LanguageModelMessage[] 参数的 API(见 LanguageModelUtility API 参考)包括:
languageModelUtility.prompt(input, options)—input为LanguageModelMessage[],options为 LanguageModelPromptOptions(含可选的responseConstraintJSON Schema / RegExp 约束和signal),返回Promise<string>或Promise<ReadableStream<string>>;languageModelUtility.append(input, options)— 追加消息而不请求响应,options 为 LanguageModelAppendOptions(仅含signal);languageModelUtility.measureContextUsage(input, options)— 测量LanguageModelMessage[]输入将占用的 token 数;- 创建入口
LanguageModelUtility.create(options)的 LanguageModelCreateOptions 中还有可选的initialPrompts: LanguageModelMessage[],用于在模型创建时注入初始提示(测试中的典型用法见 spec/api-local-ai-handler-spec.ts:initialPrompts: [{ role: 'system', content: [{ type: 'text', value: 'You are Electron AI' }] }])。
一个可直接运行的最小 handler 实现(来自官方教程)展示了 LanguageModelMessage[] 在 prompt() 中的消费方式:
// ai-handler.js (Utility Process)
const { localAIHandler, LanguageModelUtility } = require('electron/utility')
localAIHandler.setPromptAPIHandler((details) => {
// details.webContentsId / details.securityOrigin 可用于按来源隔离模型实例
return class MyLanguageModel extends LanguageModelUtility {
static async create (options) {
return new MyLanguageModel({ contextUsage: 0, contextWindow: 4096 })
}
static async availability () {
return 'available'
}
async prompt (input) {
// input 即 LanguageModelMessage[]:
// [{ role, content: [{ type: 'text' | 'image' | 'audio', value }] }]
return 'This is a response from your local LLM!'
}
async clone () {
return new MyLanguageModel({
contextUsage: this.contextUsage,
contextWindow: this.contextWindow
})
}
destroy () { /* 清理模型资源 */ }
}
})
主进程侧的注册与 LanguageModelMessage 的字符串快捷形式(model.prompt('What is Electron?') 会被自动包装为单条 role: 'user' 消息,测试断言见 spec/api-local-ai-handler-spec.ts):
// main.js (Main Process)
const { app, BrowserWindow, utilityProcess } = require('electron')
const path = require('node:path')
app.whenReady().then(() => {
const aiHandler = utilityProcess.fork(path.join(__dirname, 'ai-handler.js'))
const win = new BrowserWindow({
webPreferences: { enableBlinkFeatures: 'AIPromptAPI' }
})
win.webContents.session.registerLocalAIHandler(aiHandler)
win.loadFile('index.html')
})
四、底层实现:从 Mojo 消息到 V8 对象
LanguageModelMessage 的跨进程传输基于 Chromium 的 Mojo IDL。在 Utility 进程中,UtilityAILanguageModel 实现了 blink::mojom::AILanguageModel 接口,其 Prompt / Append / MeasureInputUsage 方法接收的都是 std::vector<blink::mojom::AILanguageModelPromptPtr>——即 mojo 层的消息数组,并维护一组 mojo::RemoteSet<blink::mojom::ModelStreamingResponder> 支持流式响应与中途中止(abort_controllers_ 映射记录了每个进行中的 Prompt/Append 响应器对应的 JS 侧 AbortController)。
关键的类型转换发生在 utility_ai_language_model.cc 中的 gin Converter:mojo 消息被逐字段展开为 JS 对象,其中文档定义的两个核心字段在源码中对应如下(约第 136–138 行):
dict.Set("role", val->role); // role: 'system' | 'user' | 'assistant'
dict.Set("prefix", val->is_prefix); // prefix: 由 mojo 字段 is_prefix 映射而来
这印证了前文的结论:prefix 在 mojo 消息中的原始字段名是 is_prefix,由 Blink/Chromium 侧在消息构造时设定,Electron 只是透传。转换逻辑位于 UtilityAILanguageModel.cc 的 gin 命名空间 Converter 特化中,将 AILanguageModelPromptPtr 整体(含 content 数组与 is_prefix)转为可被你的 prompt(input) 直接使用的 V8 数组。
浏览器端的代理入口在 shell/browser/ai/proxying_ai_manager.cc 与 utility_ai_manager.cc,请求沿"渲染进程 → 浏览器进程代理 → Utility 进程 → 返回渲染进程"的路径流转,LanguageModelMessage[] 在每一跳都保持同一语义结构。
五、使用注意事项与适用前提
- 实验性 API:
LanguageModelMessage及其相关结构(LanguageModelUtility、localAIHandler、registerLocalAIHandler)均为 Electron 实验性特性,官方文档明确提示其可能在未来版本中变更或移除,生产使用需锁定 Electron 版本并跟进变更; - 启用条件:使用该消息模型的功能要求目标
BrowserWindow以enableBlinkFeatures: 'AIPromptAPI'开启 Blink 特性,多模态 content(image/audio类型)还需AIPromptAPIMultimodalInput; - prefix 无需手工维护:由框架在消息转换时注入,handler 侧只需按
role+content组装给底层模型; - 按来源隔离:
setPromptAPIHandler的回调参数details(webContentsId、securityOrigin)用于决定是否为不同站点复用模型实例——由于LanguageModelMessage[]携带完整的对话上下文(含assistant历史),跨来源复用实例可能导致上下文串扰,建议按 origin 返回独立实例; - 流式与中止:
prompt()可返回ReadableStream<string>实现流式输出,配合LanguageModelPromptOptions.signal可中止进行中的执行,底层由UtilityAILanguageModel中的 abort controller 映射保障 JS 侧中止信号能真正中断 mojo 响应器。
六、小结
LanguageModelMessage 虽然字段极少(role / content / prefix),但它是 Electron 本地 AI 体系中信息密度最高的结构:role 三元组对齐了主流 LLM 对话范式,content 数组借 LanguageModelMessageContent 实现了 text/image/audio 多模态混排,prefix 则暴露了框架层的前缀消息元信息。结合 LanguageModelUtility 的 prompt / append / measureContextUsage 与 local-ai-handler 教程 给出的三进程接入方案,开发者可以在不改动 Web 页面的前提下,把任意本地模型(如 node-llama-cpp 加载的 GGUF 模型)接入 Chromium 标准 Prompt API 生态。相关实现与测试可进一步查阅 shell/utility/ai/ 目录与 spec/api-local-ai-handler-spec.ts。
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