Electron LanguageModelMessageContent 对象详解:Prompt API 多模态输入的内容载体
Electron 的实验性 Prompt API 让 Web 内容可以像调用 Chromium 标准 LanguageModel 接口一样,把请求路由到开发者自己实现的本地 AI 处理器;而 LanguageModelMessageContent 对象正是这套机制中每一条消息的"内容单元",用于承载 text、image、audio 三类多模态输入。读完本文,你将掌握该对象两个字段的完整定义与取值组合规则、它与 LanguageModelMessage 的嵌套关系、从渲染进程 LanguageModel API 到 utility 进程处理器的底层数据转换链路,以及在实际代码中构造合法内容对象的实操方式。
对象结构:type 与 value 两个字段
LanguageModelMessageContent 对象(结构文档)只有两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string |
内容类型,取值为 text、image、audio 之一 |
value |
ArrayBuffer | string |
内容的实际数据 |
从 utility 进程的 gin 转换器源码 可以确认两种字段的配合关系:
type: 'text':value为string,直接携带文本内容。源码中对应dict.Set("type", "text"); dict.Set("value", val->get_text());type: 'image':value为ArrayBuffer,承载位图的原始像素数据。源码先将 Chromium 内部的SkBitmap转换为 N32 预乘(premultiplied)RGBA 格式再写入ArrayBuffer,若像素读取失败会抛出TypeError: Invalid bitmap content in prompt;type: 'audio':value为ArrayBuffer,承载float32音频采样序列(源码中按sizeof(float) * raw_data.size()计算缓冲区大小并拷贝原始std::vector<float>数据)。
需要注意:虽然类型签名写作 ArrayBuffer | string,但按 type 与 value 的组合约束,实际应为"text 用 string、image/audio 用 ArrayBuffer"。转换器源码中还留有 TODO 注释,说明图像像素与音频采样的具体数据布局(形状、采样率等)目前没有正式承诺,自定义处理器在解析二进制数据时应以防御式处理为前提。
该对象是 LanguageModelMessage(结构文档)的组成部分:每条消息由 role(system / user / assistant)、content(LanguageModelMessageContent[] 数组)以及可选的 prefix 布尔字段构成。prompt() 等 API 允许传入多个内容项,因此一条消息中可以混排文本、图像与音频,这是多模态输入能力的直接来源。
渲染进程侧:LanguageModel API 如何消费该对象
LanguageModelMessageContent 面向的是 Web 内容一侧。要启用这套 API,渲染进程需要打开 Blink 实验特性,官方教程 local-ai-handler 给出了标准配置:
const win = new BrowserWindow({
webPreferences: {
enableBlinkFeatures: 'AIPromptAPI,AIPromptAPIMultimodalInput'
}
});
其中 AIPromptAPI 启用 LanguageModel 全局接口,AIPromptAPIMultimodalInput 启用图像/音频等多模态输入支持。窗口启用后,页面内即可使用标准 API:
// 文本消息会被规范化为 { role: 'user', content: [{ type: 'text', value: '...' }] }
const model = await LanguageModel.create();
const reply = await model.prompt('hello world');
一个值得注意的规范化行为:当 prompt() 接收字符串简写输入时,Electron 会把它转换为标准的消息数组。测试用例 明确验证了处理器最终收到的输入形态:
// spec/api-local-ai-handler-spec.ts 中的断言
expect(message.input).to.deep.equal([
{ role: 'user', content: [{ type: 'text', value: 'hello world' }], prefix: false }
]);
同时该测试文件验证了直接传 LanguageModelMessage[] 的用法——content 数组中每项就是本文主角 LanguageModelMessageContent 对象:
const input = [
{ role: 'user', content: [{ type: 'text', value: 'hello' }] },
{ role: 'assistant', content: [{ type: 'text', value: 'hi' }] }
];
// model.prompt(input)
// 测试断言:每条消息的 prefix 都会被补为 false
此外 LanguageModel.create() 的 initialPrompts 选项也接受同样的消息结构(测试中有 { role: 'system', content: [{ type: 'text', value: 'You are Electron AI' }] } 的实例),因此 LanguageModelMessageContent 同样适用于系统提示词的定义。
底层链路:mojo 结构如何转换成 JS 对象
从源码结构看,整个 Prompt API 的调用链是:渲染进程页面调用 LanguageModel 接口 → Blink 通过 mojo 把 AILanguageModelPrompt 结构发到浏览器进程 → ProxyingAIManager 将请求代理转发到通过 session.registerLocalAIHandler() 注册的 utility 进程 → utility 进程的转换器 把 mojo 结构转成 JS 字典,交给开发者代码。
其中 Converter<blink::mojom::AILanguageModelPromptContentPtr> 就是 LanguageModelMessageContent 的生成点:mojo 侧的 oneof 结构(text / bitmap / audio)被逐一映射为 { type, value } 字典,外层 Converter<blink::mojom::AILanguageModelPromptPtr> 再组装出 role、content、prefix 三个字段。也就是说,你在 utility 进程处理器里拿到的每个 content 对象,其字段名和取值('text' / 'image' / 'audio')都由这层转换器固定下来。
浏览器进程侧还有一个可用性兜底:ProxyingAIManager::CanCreateLanguageModel 使用 WrapCallbackWithDefaultInvokeIfNotRun 把未响应的检查默认置为不可用。对应的测试行为是:utility 进程崩溃或未注册处理器时,LanguageModel.availability() 一律返回 'unavailable',页面侧不会挂起。
utility 进程侧:LanguageModelUtility 中的使用
在主进程侧,LanguageModelUtility 类是本地 AI 模型的 JS 实现载体,它的三个接受消息数组的方法都以 LanguageModelMessage[] 为输入,即间接消费 LanguageModelMessageContent:
languageModelUtility.prompt(input, options) // Promise<string> | Promise<ReadableStream<string>>
languageModelUtility.append(input, options) // Promise<undefined>
languageModelUtility.measureContextUsage(input, options) // Promise<number>
prompt(input, options):对模型发起提示并获取响应;append(input, options):仅追加消息到上下文,不产生响应;measureContextUsage(input, options):计算输入将占用的 token 数。
因此,在 utility 进程的处理器脚本中,你可以按 item.type 分支处理不同内容项——text 直接读取字符串,image / audio 则通过 item.value(ArrayBuffer)拿到原始字节,交由自建的本地模型推理引擎消费。
适用前提与注意事项
- 实验特性:
LanguageModelUtility的全部方法与渲染进程LanguageModelAPI 均标注为 Experimental(见 LanguageModelUtility 文档),接口仍可能调整; - 双端启用:完整链路需要渲染进程开启
AIPromptAPI等 Blink 特性、utility 进程实现并注册 handler(session.registerLocalAIHandler(aiHandler)),缺一不可; - 二进制格式未定型:image 与 audio 的
ArrayBuffer数据布局目前以源码实现为准(N32 预乘像素 / float32 采样),源码中的 TODO 表明官方尚未承诺稳定格式,跨版本使用二进制内容时建议做兼容性校验; - 默认值行为:消息的
prefix字段在从 API 传入后被统一补为false(测试断言可验证),处理器实现无需自己补全该字段。
相关资源
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