首页
/ Electron LanguageModelMessage 结构详解:本地 AI(Prompt API)消息模型与多模态内容设计

Electron LanguageModelMessage 结构详解:本地 AI(Prompt API)消息模型与多模态内容设计

2026-09-06 13:37:36作者:滕妙奇

在 Electron 的实验性本地 AI 能力(Local AI Handler / Prompt API)中,LanguageModelMessage 是贯穿渲染进程、主进程与 Utility 进程的消息数据骨架:Web 页面通过 Chromium 的 LanguageModel API 发起提示,请求经代理层转换后,最终以 LanguageModelMessage[] 的形式传入你在 Utility 进程中实现的 LanguageModelUtility。读完本文,你将完全掌握该结构的字段语义(rolecontentprefix)、它与 LanguageModelMessageContent 的多模态内容模型的关系、它在 prompt / append / measureContextUsage 等 API 中的使用方式,以及 Electron 底层 Mojo 转换的实现细节和测试验证行为。

一、LanguageModelMessage 字段定义

根据 LanguageModelMessage Object 文档,该结构定义如下:

  • role string — 消息的角色,取值为以下之一:
    • system — 系统指令消息,用于设定模型的行为准则
    • user — 用户消息
    • assistant — 模型(助手)的回复消息
  • content LanguageModelMessageContent[] — 消息内容的数组
  • prefix boolean(可选)— 标记该消息是否为前缀消息

一个典型的完整消息示例如下(与 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 传入,你的实现代码主要消费 rolecontent 即可,无需手动维护该字段。

二、content 的多模态内容模型

content 字段的元素类型是 LanguageModelMessageContent,其定义为:

  • type string — 内容类型,取值为以下之一:
    • text — 文本内容
    • image — 图像内容
    • audio — 音频内容
  • value ArrayBuffer | 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 参考):

  1. 渲染进程(Renderer):Web 页面按标准 Prompt API 调用 LanguageModel.create()model.prompt(),输入可以是字符串、单条消息或 LanguageModelMessage[]
  2. 主进程(Main):通过 ses.registerLocalAIHandler(handler) 把由 utilityProcess.fork() 派生的 Utility 进程注册为该 session 的 AI 处理器,负责代理转发;
  3. Utility 进程:你的脚本通过 localAIHandler.setPromptAPIHandler() 注册 LanguageModelUtility 子类,其方法接收 LanguageModelMessage[] 并返回响应。

在 Utility 进程中,所有接收 LanguageModelMessage[] 参数的 API(见 LanguageModelUtility API 参考)包括:

  • languageModelUtility.prompt(input, options)inputLanguageModelMessage[]optionsLanguageModelPromptOptions(含可选的 responseConstraint JSON 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.tsinitialPrompts: [{ 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.ccutility_ai_manager.cc,请求沿"渲染进程 → 浏览器进程代理 → Utility 进程 → 返回渲染进程"的路径流转,LanguageModelMessage[] 在每一跳都保持同一语义结构。

五、使用注意事项与适用前提

  • 实验性 APILanguageModelMessage 及其相关结构(LanguageModelUtilitylocalAIHandlerregisterLocalAIHandler)均为 Electron 实验性特性,官方文档明确提示其可能在未来版本中变更或移除,生产使用需锁定 Electron 版本并跟进变更;
  • 启用条件:使用该消息模型的功能要求目标 BrowserWindowenableBlinkFeatures: 'AIPromptAPI' 开启 Blink 特性,多模态 content(image / audio 类型)还需 AIPromptAPIMultimodalInput
  • prefix 无需手工维护:由框架在消息转换时注入,handler 侧只需按 role + content 组装给底层模型;
  • 按来源隔离setPromptAPIHandler 的回调参数 detailswebContentsIdsecurityOrigin)用于决定是否为不同站点复用模型实例——由于 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 则暴露了框架层的前缀消息元信息。结合 LanguageModelUtilityprompt / append / measureContextUsagelocal-ai-handler 教程 给出的三进程接入方案,开发者可以在不改动 Web 页面的前提下,把任意本地模型(如 node-llama-cpp 加载的 GGUF 模型)接入 Chromium 标准 Prompt API 生态。相关实现与测试可进一步查阅 shell/utility/ai/ 目录与 spec/api-local-ai-handler-spec.ts

登录后查看全文
热门项目推荐
相关项目推荐