ARTICLE DETAIL

资讯详情

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

四文件跑通端侧 LLM:RunAnywhere Electron SDK 最小示例应用深度解析

四文件跑通端侧 LLM:RunAnywhere Electron SDK 最小示例应用深度解析 AI模型推理服务推理引擎本地部署多模态【免费下载链接】runanywhere-sdksProduction ready toolkit to run AI locally项目地址https://gitcode.com/gh_mirrors/ru/runanywhere-sdks点击查看免费下载runanywhere-minimal是 RunAnywhere 仓库中bindings/electron绑定下的最小 Electron 示例应用一个输入框、一个 Generate 按钮、一段流式输出纯 DOM、无框架、无打包器全部代码只有 4 个 TypeScript 文件。它不承担产品展示的职责而是作为贡献者测试平台存在——验证我的 C/SDK 改动是否仍然工作同时它完整地演示了 Electron 场景下 RunAnywhere SDK 的标准接入姿势主进程注册后端插件、fork 工具宿主进程utility host、preload 预置模型目录、渲染进程以流式接口生成文本。读完本文你将掌握如何在 Electron 应用中接入 RunAnywhere 端侧推理 SDK理解其四进程架构、模型目录catalog机制、CJS/ESM 双发射目标约束以及示例中每个文件存在的底层原因。示例定位测试平台而非展示应用文档开篇即明确这是最小的能证明 Electron SDK 可用的应用The smallest app that proves the Electron SDK works。它与完整桌面应用的分工是——完整功能模型选择器、设置、打包、主题等属于独立的完整应用项目本示例刻意缺席VLM、STT、TTS、语音、RAG、模型选择器、设置、打包、主题等能力那些能力要么已经在 SDK 中提供要么属于完整应用的范畴。这种定位决定了它的工程取向尽可能薄、尽可能贴近 SDK 原貌。它不引入 React/Vue、不引入打包器、不引入设计系统目的就是让每一次 SDK 行为变化都能在这个最小载体上被直接观察和验证。从本地源码消费 SDKfile: 依赖与符号链接示例应用从不从 npm 安装 SDK而是以file:协议直接引用本仓库内的源码包。这是它作为monorepo 内测试平台的关键设计// bindings/electron/example/package.json dependencies: { runanywhere/electron: file:.., runanywhere/electron-llamacpp: file:../packages/llamacpp }其中runanywhere/electron指向 SDK 本体bindings/electronrunanywhere/electron-llamacpp指向 llama.cpp 后端插件包packages/llamacpp 导出LlamaCPP类。bindings/electron/example目录下的.npmrc将install-linksfalse固定下来npm 因而对这两个包建立符号链接而非复制。这意味着重新构建bindings/electron后下一次启动示例应用即可感知改动无需任何 restage 步骤——这正是贡献者测试平台所需要的极短反馈环。文档特别提醒了一个易混淆点SDK 自己的.npmrc对其自身依赖设置了install-linkstrue但该设置是按项目生效的不会传导到示例应用这里。前置条件构建 SDK 与 native addon 的查找顺序运行示例前SDK 必须已构建、原生插件native addon必须存在。在bindings/electron目录下执行# From bindings/electron — TypeScript facade backend package. npm install npm run build (cd packages/llamacpp npm install npm run build)SDK 包的构建脚本在 package.json 中定义为tsc -p tsconfig.json后端包同理。示例应用本身不硬编码任何原生路径addon 的定位按以下顺序自动完成环境变量RUNANYWHERE_NATIVE_PATHbindings/electron/prebuilds/platform-arch/runanywhere_native.node仓库 CMake 构建目录如build/electron-macos/...、build/windows-release/...。若以上都不存在则需要使用electron-macos或windows-releasepreset 自行构建原生插件耗时较长。关于预构建平台bindings/electron/AGENTS.md 给出了更精确的边界目前提供darwin-arm64llamacpp / ONNX / Sherpa与win32-x64llamacpp / ONNX / Sherpa的预构建win32-arm64仅提供 QHexRTHexagon NPULinux 目前没有预构建产物。package.json中os/cpu字段保持宽松是有意为之——TypeScript facade 本身确实跨平台收窄字段会阻碍纯 TS 消费者与 Linux CI剩余的平台缺口由src/bridge.ts中的resolveAddon()依据PREBUILT_PLATFORMS声明。运行示例cd bindings/electron/example npm install npm run typecheck # both projects: node (CJS) renderer (ESM) npm start # build, then launch Electron也可以从仓库根目录使用统一入口./run example electron {build|start|clean}。typecheck会同时检查两套编译目标CJS 与 ESM见下文Emit targetsstart先构建再启动 Electron。窗口打开后点击Generate首次运行会下载 SmolLM2 360M约 386 MB到~/.runanywhere因此第一次需要稍等片刻后续运行会直接开始生成。它实际调用了 SDK 的哪些 API文档以表格形式给出了该示例覆盖的 SDK 能力链路这也是一个最小 Electron 应用接入 RunAnywhere 的标准五步步骤API后端注册主进程LlamaCPP.register()工具宿主 fork 端口代理new RunAnywhereMain({ catalogPath }).connect(webContents)目录条目preloadregisterCatalog(CATALOG)SDK 启动渲染进程window.runanywhere.initialize()流式生成window.runanywhere.llm.generateStream(prompt, { model })其中两个设计要点值得强调下载与加载是全自动的——只需传入options.modelSDK 会自行完成下载 → 加载 → 生成。示例的渲染进程中调用为generateStream(prompt, { model: modelId(), maxOutputTokens: 128 })没有一行手动下载或加载逻辑。目录catalog不是自动播种的——SDK 不内置任何模型表因此 src/catalog.ts 中这一行模型条目是必需的若目录中从未见过某个 id生成请求会在到达后端之前就失败。要换模型编辑该文件即可。为什么是四个文件四进程架构拆解Electron 运行此应用涉及三个进程外加 SDK 的 utility host工具宿主进程。这个拆分正是示例四个文件而非一个文件的全部原因src/main.ts主进程三个职责——记录后端插件仅主进程可注册路径在 fork 时通过RUNANYWHERE_PLUGIN_PATHS传给宿主绝不经过渲染进程 RPC这是安全约束fork 工具宿主并将其MessagePort代理进窗口打开一个窗口。src/preload.ts预加载先预置目录、再导入runanywhere/electron/preload。这个顺序是承重的文件注释明确警告不可为整洁而调整SDK 的 preload 负责通过 contextBridge 发布window.runanywhere。src/catalog.ts模型表本应用的一行模型表被工具宿主通过裸require()加载——这是它只能import type类型导入在编译后擦除的原因。src/renderer.ts页面手动驱动next()迭代流因为 contextBridge 的结构化克隆会丢弃 symbol 键for await无法迭代桥接后的流。推理绝不在主进程或渲染进程中执行只发生在持有原生 addon 的 utility host 中。SDK 侧的实现印证了这一架构RunAnywhereMain.connect()通过MessageChannelMain创建port1/port2一对端口分别投递给宿主进程与webContents建立渲染进程 ↔ 宿主的直连通道当宿主异常退出崩溃或被 kill它会向所有已连接的渲染进程发送runanywhere-host-exited事件preload 借此让所有在途调用快速失败而不是永久挂起并在下一次connect()时惰性重新 fork详见 src/process/main.ts。connect()挂在did-finish-load上页面刷新会触发一次新的连接——这正好符合刷新即需要新端口的语义。main.ts主进程的三个职责示例 main.ts 的实现非常克制import { RunAnywhereMain } from runanywhere/electron/main; import { LlamaCPP } from runanywhere/electron-llamacpp; // Before any connect(): the fork reads the queue this fills. LlamaCPP.register(); const runAnywhere new RunAnywhereMain({ catalogPath: path.join(__dirname, catalog.js), });catalog.js是 tsc 从src/catalog.ts编译出的 CommonJS 产物——宿主需要磁盘上的 CommonJS 模块才能require()。窗口创建时注意两点webPreferences.sandbox: false因为 preload 需要 require SDK 模块沙箱化 preload 做不到但contextIsolation保持默认开启页面依然只能拿到 contextBridge 发布的内容。preload.ts顺序即契约import { registerCatalog } from runanywhere/electron; import { CATALOG } from ./catalog; registerCatalog(CATALOG); import runanywhere/electron/preload; // 副作用导入发布 window.runanywheretsc 会在 import 所在位置输出对应的 CommonJSrequire因此副作用导入确实最后执行——不要为了整洁把它上提。原因在于注册是按进程的SDK 的initialize()会把已预置的目录播种进 commons 注册表所以必须先有目录、后有初始化。catalog.ts应用拥有哪些模型SDK 拥有条目的形状export const CATALOG: Catalog { smollm2-360m-q8_0: { type: llm, files: [ { url: https://huggingface.co/prithivMLmods/SmolLM2-360M-GGUF/resolve/main/SmolLM2-360M.Q8_0.gguf, as: model.gguf, }, ], primary: model.gguf, label: SmolLM2 360M Q8_0, sizeMB: 386, }, };这个SDK 定义形状、应用决定内容的分割与仓库内所有其他平台一致iOS、Android、Web 的示例应用各自维护模型表SDK 本体都不内置也是两个应用可以基于同一个 SDK 构建提供不同模型列表的原因。SDK 侧的 src/catalog.ts 给出了完整的条目字段语义对编写自己的目录非常有参考价值typellm|vlm|embedder|stt|tts|diarization|segmentation。它同时决定默认推理框架——FRAMEWORK_OF_TYPE表显示llm/vlm默认 llama.cppembedder/diarization/segmentation默认 ONNXstt/tts默认 Sherpafiles{ url, as }列表as为保存到模型目录内的文件名primary相对模型目录、传给loadLLM/loadSTT等的主文件路径其扩展名参与格式推断.gguf→ GGUF、.onnx→ ONNX、.ort→ ORT、.bin→ BIN否则 FOLDERarchive为 true 时每个下载文件是需就地解压的.tar.bz2mmprojVLM 的视觉投影器路径label/params/sizeMB/heavy供 UI 展示与资源提示使用license/licenseUrl权重许可声明注意 Gemma、Llama 等带使用限制提供模型的 UI 必须能展示对应许可chatTemplatechatml|llama3|gemma|mistral——配错会让模型无视多轮对话、把每一轮都当首轮回答framework显式钉住引擎而非按type推断例如 QHexRT 的 bundle 是预构建的 QNN context 二进制其他后端根本无法解析钉在行上还能让 commons 上报的actualBackend保持可校验钉了 QHEXRT 却回 LLAMA_CPP 就是可见的回退而非静默回退。值得注意的是framework字段刻意不用生成的 proto 枚举而用 SDK 的领域枚举InferenceFrameworkplain string因为目录表由应用编写、被工具宿主以裸require()加载若引入生成的 message 模块会连带拉入bufbuild/protobuf/wire而runanywhere/proto-ts是符号链接依赖Node 会将其 realpath 出应用的node_modules导致窗口出现前就 MODULE_NOT_FOUND——且类型检查、lint、单测、渲染进程打包全部照常通过只有运行时才暴露。这是示例目录只 import type之外的又一层约束。条目注册后catalogModelInfo()会将其映射为 commons 注册表存储的ModelInfo单文件条目生成singleFile清单含requiredPatterns以强制冷启动对账校验文件名多文件条目生成multiFile清单primary/mmproj 对应 PRIMARY_MODEL/VISION_PROJECTOR 角色archive 条目声明ARCHIVE_TYPE_TAR_BZ2并让 commons 在解压后自行扫描目录。local_path刻意不在此设置——它由 commons 的三个权威来源填写下载完成自愈、冷启动重链、加载时自愈避免出现下载落在 A 处、加载却找 B 处的第二答案。renderer.ts手写 next() 驱动流const stream window.runanywhere.llm.generateStream(prompt, { model: modelId(), maxOutputTokens: 128, }); for (;;) { const step await stream.next(); if (step.done) break; render(step.value); }for await无法迭代桥接后的流因为 contextBridge 的结构化克隆丢弃 symbol 键异步迭代器依赖Symbol.asyncIterator因此示例手动驱动next()。事件处理上渲染进程只关心三个分支textDelta追加文本、completed展示 token 数与tokensPerSecond、failed展示错误信息而started、usage、工具调用等流事件同样会被推送只是这个极简 UI 无需渲染它们。SDK 的 preload 把 API 暴露为函数而非 facade 的 getter同样源于 contextBridge 的克隆语义——getter 会在initialize()有任何可报告内容之前就被读取一次。页面还做了合理的防错initialize()完成前禁用 Generate 按钮initialize()等待的正是主进程代理进来的 MessagePort因此之前无需任何额外编排。Emit targets一个应用、两套编译输出主进程、preload 与目录模块编译为CommonJStsconfig.jsonmodule: node16因为 Electron 以 CJS 加载 main 与 preload、工具宿主require()目录模块渲染进程编译为ESMtsconfig.renderer.jsonmodule: ES2022页面用script typemodule加载见 index.html。由于渲染进程运行时不 import 任何东西只有编译期擦除的import typetsc 单独编译即可无需打包器。这与 bindings/electron/AGENTS.md 中Emit targets一节记载的 SDK 契约完全一致消费者输出原因dist/SDK 包CommonJStype: commonjsmodule: node16Electron main 与 preload 以 CJS 加载Electron preloadCommonJSsandbox: false的 preload 以 CJS 加载工具宿主目录磁盘上的 CommonJS.jshost.ts做裸require(RUNANYWHERE_CATALOG_PATH)渲染进程 bundleESM属于打包器的职责而非本包index.html还设置了Content-Security-Policy: default-src self这是示例中唯一的安全头配置也是 Electron 应用的最小 CSP 实践。AGENTS.md 进一步补充了一个与运行时直接相关的原生契约插件文件名决定被解析的入口符号——rac_registry_load_plugin()从文件名剥离lib/runanywhere_前缀与扩展名后前置rac_plugin_entry_实现见 core/src/plugin/plugin_loader.cpp 的entry_symbol_from_path()因此以 CMake 目标名发布的插件会解析出不存在符号llamacpp/onnx/sherpa 通过链接rac_backend_id的瘦runanywhere_idcarrier 满足契约而 QHexRT 没有 carrier引擎即插件。常见坑位速查综合文档与源码接入同类应用时最容易踩的坑preload 导入顺序必须先registerCatalog再导入 SDK preload否则initialize()播种时目录为空生成请求在到达后端前即失败目录模块的导入约束只能import type任何会残留在运行时的导入尤其 proto message 模块都会在宿主require()时炸出 MODULE_NOT_FOUND不要在渲染进程用for await迭代桥接流contextBridge 结构化克隆丢 symbol 键手写next()循环后端注册只在主进程路径经RUNANYWHERE_PLUGIN_PATHS在 fork 时传递渲染进程 RPC 没有注册通道安全设计勿自行放开目录不自动播种SDK 无内置模型表换模型请编辑 src/catalog.ts。小结runanywhere-minimal用最小的表面积完整演示了 RunAnywhere Electron SDK 的生产接入路径LlamaCPP.register()声明后端 →RunAnywhereMainfork 宿主并代理 MessagePort → preload 预置目录 →initialize()拉起原生运行时 →generateStream流式生成其中下载与加载全自动。四个文件的职责划分并非随意为之而是四进程架构、按进程注册的目录机制、contextBridge 克隆语义与 CJS/ESM 双发射目标共同作用下的必然结果。对任何想在 Electron 中接入端侧 LLM 的开发者而言这份示例既是可复制的模板也是理解 SDK 内部契约的最佳入口对仓库贡献者而言它就是那个回答我的改动还工作吗的测试平台。赞分享AI模型推理服务推理引擎本地部署多模态【免费下载链接】runanywhere-sdksProduction ready toolkit to run AI locally项目地址https://gitcode.com/gh_mirrors/ru/runanywhere-sdks点击查看免费下载相关推荐Electron 功能示例指南总览Examples Overview用最小化示例与 Fiddle 一键跑通常用功能实战Electron 功能示例指南总览Examples Overview用最小化示例与 Fiddle 一键跑通常用功能实战 导读 Electron 的「功能示桌面应用跨平台前端RunAnywhere Flutter 端侧 AI 示例应用深度解析架构设计、SDK 集成与构建实战RunAnywhere Flutter 端侧 AI 示例应用深度解析架构设计、SDK 集成与构建实战 导读 本文基于 bindings/flutter/exaAI模型推理服务推理引擎本地部署多模态PowerInfer smallthinker 最小示例解析用 llama-simple 跑通本地 LLM 文本生成全流程PowerInfer smallthinker 最小示例解析用 llama simple 跑通本地 LLM 文本生成全流程 llama simple 是 Po人工智能大模型推理引擎本地部署上一篇Bambu Studio多语言本地化深度解析与最佳实践指南下一篇告别数字垃圾AntiDupl.NET智能图片去重工具的终极解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表