local-ai-app-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLocal AI App Integration (Embeddable Lemonade)
本地AI应用集成(Embeddable Lemonade)
Add a local AI mode to an existing app that already talks to a cloud AI API
(OpenAI, Anthropic, or Ollama-compatible). The app launches , the
Embeddable Lemonade binary, as a private subprocess and the existing client
talks to it on . The user gets local, private,
hardware-optimized inference (CPU, AMD iGPU/dGPU, XDNA2 NPU) with no separate
install.
lemondhttp://localhost:PORT/api/v1What you'll end up with: one new launcher module (~30 lines), three mandatory changes to the existing HTTP client (, , and a 120-second HTTP timeout), one vendored binary under .
base_urlapi_keyvendor/lemonade/为已对接云AI API(OpenAI、Anthropic或兼容Ollama)的现有应用添加本地AI模式。应用会启动(Embeddable Lemonade的二进制文件)作为私有子进程,现有客户端通过与其通信。用户无需单独安装,即可获得本地、私有、硬件优化的推理能力(支持CPU、AMD iGPU/dGPU、XDNA2 NPU)。
lemondhttp://localhost:PORT/api/v1最终成果: 一个新的启动模块(约30行代码),对现有HTTP客户端的三处必填修改(、和120秒HTTP超时),以及目录下的一个内置二进制文件。
base_urlapi_keyvendor/lemonade/When this skill is the right tool
何时适合使用该技能
Use this skill when all of the following are true:
- The app already calls a cloud AI service over HTTP (OpenAI Chat Completions, Anthropic Messages, or Ollama).
- The user wants that AI to run on the end-user's PC, with the AI engine bundled into the app, not as a separate user install.
- The target platform is Windows x64 or Linux x64 (macOS embeddable is in beta).
If the user instead wants a system-wide Lemonade Server (one install,
shared across apps), do not use this skill; point them at
and the standard OpenAI base
URL .
https://lemonade-server.ai/install_options.htmlhttp://localhost:13305/api/v1当所有以下条件都满足时,使用该技能:
- 应用已通过HTTP调用云AI服务(OpenAI Chat Completions、Anthropic Messages或Ollama)。
- 用户希望AI运行在终端用户的PC上,且AI引擎被打包到应用中,而非让用户单独安装。
- 目标平台为Windows x64或Linux x64(macOS嵌入式版本处于测试阶段)。
如果用户需要的是系统级的Lemonade Server(一次安装,多应用共享),请勿使用该技能;请引导他们访问,并使用标准OpenAI基础URL 。
https://lemonade-server.ai/install_options.htmlhttp://localhost:13305/api/v1The opinionated path
标准化实施流程
This skill follows one fixed sequence. Do not deviate without a stated reason.
[ ] 1. Survey the app's current AI integration
[ ] 2. Pick a model + backend profile
[ ] 3. Place Embeddable Lemonade in the app's tree (full package, not just the binary)
[ ] 4. Add a `lemond` launcher (subprocess + API key + port + per-stage logging)
[ ] 5. Re-point the existing client at lemond (base_url, api_key, 120s timeout — all three required)
[ ] 6. Wait for /api/v1/health, install backend, then PULL the model before first use
[ ] 7. Wire shutdown and error recoveryTrack progress against this checklist. Move on only when each step verifies.
Log every stage. A local integration has many silent failure points — spawn, health, backend install, model download, first inference. Without a log line at each transition, "nothing happened" is indistinguishable from "broke at stage 3." Emit one clear line per stage as you build (see Step 4); the most common dead-end in this integration — a blank result with no error — is invisible without them.
该技能遵循固定的实施步骤,无特殊原因请勿偏离。
[ ] 1. 调研应用当前的AI集成情况
[ ] 2. 选择模型+后端配置
[ ] 3. 将Embeddable Lemonade放置到应用目录树中(完整包,而非仅二进制文件)
[ ] 4. 添加`lemond`启动器(子进程+API密钥+端口+分阶段日志)
[ ] 5. 将现有客户端指向lemond(base_url、api_key、120秒超时——三者均为必填)
[ ] 6. 等待/api/v1/health接口返回成功,安装后端,然后在首次使用前拉取模型
[ ] 7. 配置关闭流程与错误恢复机制对照此清单跟踪进度,只有当前步骤验证通过后才能进入下一步。
记录每个阶段的日志。 本地集成存在许多无提示的故障点——进程启动、健康检查、后端安装、模型下载、首次推理。如果每个阶段没有日志记录,“无响应”无法区分是“在第3阶段崩溃”还是其他问题。构建时为每个阶段输出清晰的日志行(参见步骤4);集成中最常见的死胡同——无错误提示的空白结果——没有日志就无法排查。
Step 1: Survey the app
步骤1:调研应用
Find every place the app currently calls a cloud AI API. Search the repo for:
- ,
openai,OpenAI(,chat.completionsresponses.create - ,
anthropic,Anthropic(messages.create - ,
api.openai.com,api.anthropic.com(Ollama)localhost:11434 - ,
OPENAI_API_KEYANTHROPIC_API_KEY
Record three things before continuing:
- Client library and language (e.g., ,
openai-python,openai-node,@anthropic-ai/sdk, rawgo-openai).fetch - Modalities used: text chat, tool calling, embeddings, image gen, transcription, TTS. This drives the model + backend choice in Step 2.
- One single place where the base URL and API key are constructed. If there isn't one, refactor to one before going further. Local-mode toggling must flip exactly one config object.
- Any API-key gating that blocks the app before a key is entered (onboarding walls, validators that reject empty keys, startup checks that disable AI until a key exists). Note each one — Step 5 bypasses them in local mode.
找出应用中所有调用云AI API的位置。在代码库中搜索:
- 、
openai、OpenAI(、chat.completionsresponses.create - 、
anthropic、Anthropic(messages.create - 、
api.openai.com、api.anthropic.com(Ollama)localhost:11434 - 、
OPENAI_API_KEYANTHROPIC_API_KEY
继续下一步前,记录以下三点:
- 客户端库与语言(例如:、
openai-python、openai-node、@anthropic-ai/sdk、原生go-openai)。fetch - 使用的模态:文本聊天、工具调用、嵌入、图像生成、转录、TTS。这将决定步骤2中的模型+后端选择。
- 构建base URL和API密钥的统一位置。如果没有,先重构为统一位置再继续。本地模式切换必须仅修改一个配置对象。
- 任何API密钥校验逻辑(引导页拦截、拒绝空密钥的验证器、启动时检查AI功能是否禁用直到密钥存在)。记录每一处——步骤5将在本地模式下绕过这些逻辑。
Step 2: Pick a model + backend profile
步骤2:选择模型+后端配置
Choose one default profile based on the app's primary modality. Do not
ship a buffet. Ship one good default and document how the user can override
it.
| App's primary need | Default model | Recipe | Why |
|---|---|---|---|
| General chat / assistant | | | Small, fast, good tool calling, fits 8GB systems |
| Coding assistant | | | Strong code, runs on iGPU |
| Vision / multimodal chat | | | Small multimodal default |
| NPU-first on Ryzen AI | | | XDNA2 NPU on Windows |
| Speech-to-text (Windows) | | | One model; probe picks NPU → iGPU/dGPU → CPU automatically |
| Speech-to-text (Linux NPU) | | | Linux NPU path; falls back to |
| Text-to-speech | | | CPU-only, low latency |
| Image generation | | | Single-step generation |
For the LLM backend, default to and let pick
→ → automatically by leaving
unset. Override only if the app has hard hardware requirements.
llamacpplemondrocmvulkancpullamacpp_backendScope: this skill selects a backend once at integration time on the
developer's machine. Runtime fallback based on the end user's hardware is
out of scope. Bundle as the universal fallback so the app works on
any machine. If the dev machine has an NPU and the chosen recipe supports it,
the skill will use the NPU backend — otherwise it falls back to .
vulkanvulkanNote: having an NPU does not mean every recipe supports NPU. Confirm the recipe/backend pair isorinstalledviainstallablebefore committing to it. See reference.md for per-recipe decision rules.GET /api/v1/system-info
For more options and tradeoffs, see reference.md.
根据应用的主要模态选择一个默认配置。不要提供多种选项,只需提供一个优质默认配置,并记录用户如何自定义配置。
| 应用主要需求 | 默认模型 | 方案 | 原因 |
|---|---|---|---|
| 通用聊天/助手 | | | 体积小、速度快、工具调用能力强,适配8GB内存的系统 |
| 编码助手 | | | 代码能力强,可在iGPU上运行 |
| 视觉/多模态聊天 | | | 轻量多模态默认选项 |
| 优先使用Ryzen AI的NPU | | | 适配Windows系统的XDNA2 NPU |
| 语音转文本(Windows) | | | 单模型;自动优先选择NPU → iGPU/dGPU → CPU |
| 语音转文本(Linux NPU) | | | Linux NPU路径;非NPU环境下 fallback 到 |
| 文本转语音 | | | 仅CPU运行,低延迟 |
| 图像生成 | | | 单步生成 |
对于LLM后端,默认使用,并通过留空让自动选择 → → 。仅当应用有严格硬件要求时才手动指定。
llamacppllamacpp_backendlemondrocmvulkancpu范围: 该技能在开发者机器上集成时一次性选择后端。基于终端用户硬件的运行时 fallback 不在本技能范围内。打包时包含作为通用 fallback,确保应用可在任意机器上运行。如果开发者机器有NPU且所选方案支持,则技能会使用NPU后端——否则 fallback 到。
vulkanvulkan注意: 拥有NPU不代表所有方案都支持NPU。在确定方案前,需通过确认方案/后端组合是已安装或可安装的。请查看reference.md了解各方案的决策规则。GET /api/v1/system-info
如需更多选项和权衡,请查看reference.md。
Step 3: Place Embeddable Lemonade in the app's tree and install backends
步骤3:将Embeddable Lemonade放置到应用目录树并安装后端
Get the embeddable artifact from the latest Lemonade release:
https://github.com/lemonade-sdk/lemonade/releases/latestDownload the file matching your target OS:
- Windows:
lemonade-embeddable-{VERSION}-windows-x64.zip - Linux:
lemonade-embeddable-{VERSION}-ubuntu-x64.tar.gz
Don't hand-build the download URL from the tag. The git tag carries a leading(e.g.v) but the asset filename strips it (v10.8.0), so using the tag verbatim 404s. Ask the GitHub API for the asset by its stable name pattern and use the URL it returns, as below — this stays correct across version and naming changes.lemonade-embeddable-10.8.0-...
First, create the target directory — it does not exist in a fresh repo:
powershell
undefined从Lemonade最新版本获取嵌入式包:
https://github.com/lemonade-sdk/lemonade/releases/latest下载匹配目标操作系统的文件:
- Windows:
lemonade-embeddable-{VERSION}-windows-x64.zip - Linux:
lemonade-embeddable-{VERSION}-ubuntu-x64.tar.gz
不要通过标签手动构建下载URL。 Git标签带有前缀(例如v),但资产文件名会去掉该前缀(v10.8.0),直接使用标签会导致404错误。请通过GitHub API按稳定名称模式获取资产URL,如下所示——这样可确保在版本和命名变更时依然正确。lemonade-embeddable-10.8.0-...
首先创建目标目录——全新代码库中该目录不存在:
powershell
undefinedWindows
Windows
New-Item -ItemType Directory -Force vendor\lemonade
```bashNew-Item -ItemType Directory -Force vendor\lemonade
```bashLinux
Linux
mkdir -p vendor/lemonade
Then download and unpack on Windows (PowerShell):
```powershell
$rel = Invoke-RestMethod https://api.github.com/repos/lemonade-sdk/lemonade/releases/latest
$asset = $rel.assets | Where-Object { $_.name -like "lemonade-embeddable-*-windows-x64.zip" } | Select-Object -First 1
Invoke-WebRequest $asset.browser_download_url -OutFile lemond.zip
Expand-Archive lemond.zip -DestinationPath "$env:TEMP\lemond-unpack"
$folder = $asset.name -replace '\.zip$','' # unpacked dir = asset name without .zip
Copy-Item -Recurse "$env:TEMP\lemond-unpack\$folder\*" vendor\lemonade\mkdir -p vendor/lemonade
然后在Windows(PowerShell)中下载并解压:
```powershell
$rel = Invoke-RestMethod https://api.github.com/repos/lemonade-sdk/lemonade/releases/latest
$asset = $rel.assets | Where-Object { $_.name -like "lemonade-embeddable-*-windows-x64.zip" } | Select-Object -First 1
Invoke-WebRequest $asset.browser_download_url -OutFile lemond.zip
Expand-Archive lemond.zip -DestinationPath "$env:TEMP\lemond-unpack"
$folder = $asset.name -replace '\.zip$','' # 解压后的目录 = 资产名称去掉.zip
Copy-Item -Recurse "$env:TEMP\lemond-unpack\$folder\*" vendor\lemonade\Sanity check: resources/ must be nested under vendor\lemonade\ (not flattened)
完整性检查:resources/必须嵌套在vendor\lemonade\下(不能扁平化)
if (-not (Test-Path vendor\lemonade\resources*.json)) { throw "resources/ missing — re-extract and copy again" }
On Linux (bash):
```bash
URL=$(curl -s https://api.github.com/repos/lemonade-sdk/lemonade/releases/latest \
| grep browser_download_url | grep ubuntu-x64.tar.gz | cut -d'"' -f4)
curl -L "$URL" | tar -xz --strip-components=1 -C vendor/lemonadeCopy the full package, not just the binary. The archive contains,lemond[.exe],lemonade[.exe], andLICENSE. Theresources/directory is required — without it lemond starts and passes the health check but fails on every model and backend request. Copying only the binary produces a server that looks healthy but cannot function.resources/
vslemondCLI:lemonadeis the embedded server binary that ships with the app. ThelemondCLI is a separate packaging tool used only during development/build time to install backends. The same embeddable archive unpacked above already contains a matchinglemonadenext tolemonade[.exe], so its version aligns with the bundledlemond[.exe]. Do notlemondto get it: the PyPI package is a separate, older release line whose ports, model names, and install API do not match thepip install lemonade-sdkbundled here, and mixing the two is a known source of silent version mismatches. Keep thelemondCLI,lemonade, and the backends all from the one release downloaded in this step so their versions stay aligned.lemond
The expected layout after setup (first run + backend install). A freshly
unzipped package contains only , , , and
— the items below are created later, as their comments note:
lemond[.exe]lemonade[.exe]LICENSEresources/vendor/lemonade/
lemond[.exe] # the only binary the app ships
LICENSE
config.json # generated on first run; commit a seed copy
resources/
server_models.json # do not edit; use GET /api/v1/models at runtime
backend_versions.json
bin/ # backends bundled at packaging time
llamacpp/vulkan/llama-server[.exe]
models/ # pre-bundled model weights (optional)
models--unsloth--Qwen3-4B-GGUF/: Do not edit or rely on this file. It can be stale. The only authoritative model list isserver_models.jsonon a runningGET /api/v1/modelsinstance with the backend already installed.lemond
Bundle decisions: pick deliberately
- Backends: Bundle at packaging time (works on every GPU). Install
llamacpp:vulkanat first run on supported AMD systems viallamacpp:rocmafter probingPOST /api/v1/install. Never ship every backend, or the artifact balloons.GET /api/v1/system-info - Models: Either bundle the default model under (offline install, larger installer) or pull on first run with
models/(smaller installer, needs network). Pick one and document it.POST /api/v1/pull - : Set to
models_dirin./modelsto keep weights private to the app. Leave asconfig.jsononly if the user explicitly wants to share weights with other apps.auto
Backend install timing — two distinct paths:
Packaging time (developer machine, before bundling). Use the lemonade CLI that shipped insideso it matches the bundledvendor/lemonade/version (prefix withlemondor the full path):./vendor/lemonade/lemonade backends install llamacpp:vulkan vendor/lemonade/lemonade backends install flm:npu # Windows NPU path onlyThis bakes the backend binaries intobefore the app ships.vendor/lemonade/bin/does not need to be running. Use a modernlemondCLI whose version matches the bundledlemonade(the copy in the archive you unpacked works); do notlemondfor it.pip install lemonade-sdkFirst-run / runtime (user's machine, afteris running):lemondhttpPOST /api/v1/install {"recipe": "llamacpp", "backend": "rocm"}Use this for hardware-specific backends (e.g.) that cannot be bundled universally.llamacpp:rocmmust already be running (Step 4 complete).lemond
if (-not (Test-Path vendor\lemonade\resources*.json)) { throw "resources/缺失——重新解压并复制" }
在Linux(bash)中:
```bash
URL=$(curl -s https://api.github.com/repos/lemonade-sdk/lemonade/releases/latest \
| grep browser_download_url | grep ubuntu-x64.tar.gz | cut -d'"' -f4)
curl -L "$URL" | tar -xz --strip-components=1 -C vendor/lemonade复制完整包,而非仅二进制文件。 压缩包包含、lemond[.exe]、lemonade[.exe]和LICENSE。resources/目录是必需的——没有它,lemond可以启动并通过健康检查,但所有模型和后端请求都会失败。仅复制二进制文件会导致服务器看似正常但无法运行。resources/
vslemondCLI:lemonade是随应用打包的嵌入式服务器二进制文件。lemondCLI是仅在开发/构建时使用的独立打包工具,用于安装后端。上述解压的嵌入式包中已包含与lemonade版本匹配的lemond,因此版本保持一致。请勿通过lemonade[.exe]获取该工具:PyPI包是独立的旧版本分支,其端口、模型名称和安装API与此处打包的pip install lemonade-sdk不匹配,混用会导致无提示的版本不兼容问题。确保lemondCLI、lemonade和后端均来自本步骤下载的同一版本,以保持版本一致。lemond
设置完成后的预期目录结构(首次运行+后端安装后)。刚解压的包仅包含、、和——以下项目会在后续创建,注释说明其创建时机:
lemond[.exe]lemonade[.exe]LICENSEresources/vendor/lemonade/
lemond[.exe] # 应用仅需打包该二进制文件
LICENSE
config.json # 首次运行时生成;提交一个初始副本
resources/
server_models.json # 请勿编辑;运行时使用GET /api/v1/models获取
backend_versions.json
bin/ # 打包时内置的后端
llamacpp/vulkan/llama-server[.exe]
models/ # 预打包的模型权重(可选)
models--unsloth--Qwen3-4B-GGUF/: 请勿编辑或依赖该文件,它可能已过期。唯一权威的模型列表是运行中的server_models.json实例上的lemond接口(需已安装后端)。GET /api/v1/models
打包决策:谨慎选择
- 后端: 打包时内置(适用于所有GPU)。在支持AMD系统的首次运行时,通过
llamacpp:vulkan探测后,调用GET /api/v1/system-info安装POST /api/v1/install。切勿打包所有后端,否则包体积会大幅增加。llamacpp:rocm - 模型: 要么将默认模型打包在下(离线安装,安装包更大),要么在首次运行时通过
models/拉取(安装包更小,需要网络)。二选一并记录决策。POST /api/v1/pull - : 在
models_dir中设置为config.json,使权重仅对当前应用私有。仅当用户明确希望与其他应用共享权重时,才保留为./models。auto
后端安装时机——两种不同路径:
打包时(开发者机器,打包前)。使用目录下的lemonade CLI,确保其与打包的vendor/lemonade/版本匹配(前缀加lemond或完整路径):./vendor/lemonade/lemonade backends install llamacpp:vulkan vendor/lemonade/lemonade backends install flm:npu # 仅适用于Windows NPU路径这会在应用打包前将后端二进制文件嵌入。无需运行vendor/lemonade/bin/。使用与打包的lemond版本匹配的最新lemonade CLI(解压包中的副本即可);请勿通过lemond获取。pip install lemonade-sdk首次运行/运行时(用户机器,启动后):lemondhttpPOST /api/v1/install {"recipe": "llamacpp", "backend": "rocm"}适用于无法通用打包的硬件特定后端(例如)。llamacpp:rocm必须已启动(步骤4完成)。lemond
Step 4: Add a lemond
launcher
lemond步骤4:添加lemond
启动器
lemondWrite the launcher as a new module named (or
for the app's language). It is a thin process
supervisor. Its only jobs:
lemond_launcher.pylemond_launcher.<ext>- Generate a fresh random API key:
key = secrets.token_urlsafe(32) - Pick a free localhost port: bind a to port 0, read back the assigned port, close it.
socket - Spawn lemond as a :
subprocesssubprocess.Popen([LEMOND_BIN, LEMOND_DIR, "--port", str(port)], env={**os.environ, "LEMONADE_API_KEY": key}) - Poll with
GET /api/v1/healthin a loop until HTTP 200 — this is the only correct readiness check.Authorization: Bearer {key} - Expose the chosen and
portto the rest of the app.key
Log one line per lifecycle stage. Build the logging in from the start — not as an afterthought when something breaks. Each silent transition needs a visible marker so a failure points at the exact stage. Aim for:[lemond] Starting on port <port> [lemond] Healthy on port <port> [lemond] <recipe>:<backend> installed (or: already installed / install failed) [lemond] Pulling model <name>... then: Model <name> ready (or: pull returned <status>) [local] <modality> result: <value> (first inference output — empty string here = unpulled model)Logging the first inference result verbatim is what turns the silent-empty failure (Step 6) from a multi-hour mystery into a one-line diagnosis. Route these through the app's normal logging so they can be quieted for release.
Dev-mode file watchers: If the app runs with a file watcher (Tauri, Electron, Next.js, Vite, etc.) that watches the source tree, ensureis excluded from the watched paths. Lemond writes config and cache files at runtime; a watcher that picks these up will restart the app, kill the lemond subprocess, and spawn a new one on a new port — silently breaking any in-flight transcription. Addvendor/lemonade/(or the equivalent) to the watcher's ignore list before testing.vendor/
Use the reference implementation from reference.md § Reference launchers directly — copy it verbatim and adapt only the path. Do not write a launcher from scratch. The reference Python launcher uses (for the API key), (for the free-port probe), and (to spawn lemond); the Node.js launcher uses the equivalent stdlib modules. Both handle port-race retries and health polling correctly.
LEMOND_DIRsecretssocketsubprocessReadiness is always determined by polling the exact endpoint
and checking for HTTP 200 — never
by reading 's stdout or stderr. Any health-check helper you write must
hit that path.
GET http://127.0.0.1:<port>/api/v1/healthlemond/api/v1/health将启动器编写为名为****(或对应应用语言的)的新模块。它是一个轻量级进程管理器,仅需完成以下工作:
lemond_launcher.pylemond_launcher.<ext>- 生成新的随机API密钥:
key = secrets.token_urlsafe(32) - 选择一个空闲的本地端口:将绑定到端口0,读取分配的端口,然后关闭。
socket - 启动lemond作为子进程:
subprocess.Popen([LEMOND_BIN, LEMOND_DIR, "--port", str(port)], env={**os.environ, "LEMONADE_API_KEY": key}) - 循环调用并携带
GET /api/v1/health,直到返回HTTP 200——这是唯一正确的就绪检查方式。Authorization: Bearer {key} - 向应用其他部分暴露所选的和
port。key
记录每个生命周期阶段的日志。 从一开始就加入日志功能——不要等到出现问题才添加。每个无提示的阶段转换都需要可见标记,以便故障能定位到具体阶段。目标日志如下:[lemond] 在端口<port>启动 [lemond] 端口<port>健康检查通过 [lemond] <recipe>:<backend>已安装 (或:已安装/安装失败) [lemond] 拉取模型<name>... 然后:模型<name>就绪 (或:拉取返回<status>) [local] <modality>结果:<value> (首次推理输出——此处为空字符串表示模型未拉取)记录首次推理结果原文可将无提示的空白结果(步骤6)从数小时的排查难题转化为一行诊断信息。将这些日志接入应用的常规日志系统,以便在发布版本中关闭日志输出。
开发模式文件监视器: 如果应用使用文件监视器(Tauri、Electron、Next.js、Vite等)监视源码目录,请确保被排除在监视路径之外。Lemond运行时会写入配置和缓存文件;监视器检测到这些文件变更会重启应用,杀死lemond子进程,并在新端口启动新进程——这会无声地中断正在进行的转录。测试前将vendor/lemonade/(或等效目录)添加到监视器的忽略列表。vendor/
直接使用reference.md § Reference launchers中的参考实现——原样复制并仅修改路径。请勿从头编写启动器。参考Python启动器使用(生成API密钥)、(探测空闲端口)和(启动lemond);Node.js启动器使用等效的标准库模块。两者均正确处理端口冲突重试和健康检查轮询。
LEMOND_DIRsecretssocketsubprocess就绪状态始终通过轮询精确的端点并检查HTTP 200来确定——绝不要通过读取的stdout或stderr判断。任何自定义的健康检查工具都必须调用该路径。
GET http://127.0.0.1:<port>/api/v1/healthlemond/api/v1/healthStep 5: Re-point the existing client at lemond
lemond步骤5:将现有客户端指向lemond
lemondMake three changes to the app's existing client construction — all three
are required, not optional:
- Set to
base_urlhttp://127.0.0.1:{port}/api/v1 - Set to the launcher key
api_key - Set the HTTP timeout to 120 seconds — this is mandatory, not optional
The 120-second timeout is not a tuning suggestion. The default on most HTTP
clients is 30s, which is shorter than lemond's first-run model load time on
real hardware. Without it the request silently times out and the UI shows
nothing, which is indistinguishable from a broken integration.
Python (openai) — the exact change to make:
python
import httpx
from openai import OpenAI
proc, key, port = start_lemond()
client = OpenAI(
base_url=f"http://127.0.0.1:{port}/api/v1",
api_key=key,
http_client=httpx.Client(timeout=120), # required: 120s for first-run model load
)For other clients:
| Existing client | New | New auth | Timeout |
|---|---|---|---|
| | | |
| | | |
| | | |
Raw | same | | set per-request |
| Ollama-compatible code | | pass key anyway | 120s |
The model identifier on requests stays a Lemonade model name (e.g.
), not the cloud name.
Qwen3-4B-GGUFLocal mode needs no cloud API key — at all. This is a defining property of
local mode, not an edge case: there is no cloud service to authenticate to, so
nothing should ever ask the user for a key. Any onboarding wall, validator, or
startup check that demands one must not block local-mode users. Concretely:
- Skip or auto-satisfy the key-entry screen in local mode.
- Treat local mode as already-authorized in every validation path — an empty-key check must short-circuit to "valid" when the active mode is local, never throw "API key not configured".
- Re-enable the gate only for cloud mode.
The key from Step 4 is generated internally by the launcher and used
only for the local loopback connection, so the user never sees or enters one;
any UI placeholder (e.g. ) is fine. Flipping into local mode should
never strand the user on a key-entry wall.
lemond"local"对应用现有客户端的构造进行三处修改——三者均为必填,不可省略:
- 将设置为
base_urlhttp://127.0.0.1:{port}/api/v1 - 将设置为启动器生成的密钥
api_key - 将HTTP超时设置为120秒——这是必填项,不可省略
120秒超时不是调优建议。大多数HTTP客户端的默认超时为30秒,短于lemond在真实硬件上首次运行时的模型加载时间。如果不设置,请求会无声超时,UI无任何显示,无法与集成失败区分。
Python(openai)——具体修改:
python
import httpx
from openai import OpenAI
proc, key, port = start_lemond()
client = OpenAI(
base_url=f"http://127.0.0.1:{port}/api/v1",
api_key=key,
http_client=httpx.Client(timeout=120), # 必填:首次运行模型加载需120秒
)其他客户端的修改:
| 现有客户端 | 新 | 新认证方式 | 超时设置 |
|---|---|---|---|
| | | |
| | | |
| | | |
原生 | 同上 | | 按请求设置 |
| 兼容Ollama的代码 | | 仍需传入密钥 | 120秒 |
请求中的模型标识符需使用Lemonade模型名称(例如),而非云服务中的模型名称。
Qwen3-4B-GGUF本地模式完全不需要云API密钥。 这是本地模式的核心特性,而非边缘情况:无需对接云服务,因此绝不应要求用户提供密钥。任何引导页拦截、验证器或启动检查都不得阻止本地模式用户。具体实现:
- 本地模式下跳过或自动通过密钥输入界面。
- 在所有验证路径中将本地模式视为已授权——当启用本地模式时,空密钥检查必须直接判定为“有效”,绝不能抛出“API密钥未配置”错误。
- 仅在云模式下重新启用密钥校验。
步骤4中生成的密钥由启动器内部生成,仅用于本地回环连接,用户永远不会看到或输入该密钥;UI中可显示占位符(例如)。切换到本地模式绝不应让用户卡在密钥输入界面。
lemond"local"Step 6: Health, backend, then pull the model — before first inference
步骤6:健康检查、后端安装、拉取模型——在首次推理前完成
GET /api/v1/healthDo not call at startup. Lemond lazy-loads the model
into memory on the first inference request and handles that step on its own.
Pre-loading is unreliable across lemond versions (the request body
shape has changed between releases) and a malformed call can crash or
destabilise the server before the user takes any action. Loading is the one
step you let lemond do lazily — pulling is not.
POST /api/v1/load/loadGET /api/v1/health启动时请勿调用。 Lemond会在首次推理请求时自动将模型懒加载到内存,无需手动处理。预加载在不同lemond版本中不可靠(请求体格式在版本间已变更),格式错误的调用可能在用户操作前导致服务器崩溃或不稳定。加载是唯一可交给lemond自动处理的步骤——拉取模型则必须主动执行。
POST /api/v1/load/loadPull the model so it exists on disk
拉取模型到磁盘
Lazy-load only loads weights that are already downloaded. If the model was
never pulled, the first inference does not error — lemond returns an empty /
blank result with HTTP 200. So after health passes and the backend is
installed, proactively pull the model:
http
POST /api/v1/pull
{"model": "Whisper-Large-v3-Turbo"}This is idempotent — a no-op if the weights are already present, a download
if they are not. Run it once during setup (after backend install, before the
first user-triggered inference) and log the result.
- Default model (the one you chose in Step 2): pull it by name as above.
- Custom / user-overridden model: do not assume it exists. Confirm it is a
real Lemonade model first via (the only trusted catalog — see reference.md), then pull it the same way. A model appearing in the catalog is not proof its weights are downloaded; a successful pull is.
GET /api/v1/models
Silent-empty is almost always an unpulled model. If inference returns an empty string / blank output with no HTTP error, the model was not downloaded. Check your pull step before debugging anything else — this is the failure mode that wastes the most time. Log the pull result and the first inference result (see Step 4) so this is diagnosable from the console, not by guesswork.
懒加载仅加载已下载的权重。如果模型从未拉取,首次推理不会报错——lemond会返回空/空白结果并伴随HTTP 200。因此,在健康检查通过且后端安装完成后,需主动拉取模型:
http
POST /api/v1/pull
{"model": "Whisper-Large-v3-Turbo"}该操作是幂等的——如果权重已存在则无操作,否则进行下载。在设置阶段运行一次(后端安装后,首次用户触发的推理前)并记录结果。
- 默认模型(步骤2中选择的模型):按上述方式通过名称拉取。
- 自定义/用户覆盖的模型:不要假设模型已存在。首先通过确认其为有效的Lemonade模型(这是唯一可信的模型目录——参见reference.md),然后按相同方式拉取。模型出现在目录中不代表其权重已下载;拉取成功才是权重存在的证明。
GET /api/v1/models
无提示空白结果几乎总是因为模型未拉取。 如果推理返回空字符串/空白输出且无HTTP错误,说明模型未下载。在调试其他问题前先检查拉取步骤——这是最浪费时间的故障模式。记录拉取结果和首次推理结果(参见步骤4),以便通过控制台排查问题,而非猜测。
Surface the whole setup, not just model load
展示完整设置流程,而非仅模型加载
First-run cold start is more than a model load. The full sequence is:
server spawn → health 200 → backend install → model download → model load → first resultOn a fresh machine, backend install and model download can each take from tens
of seconds to several minutes (multi-GB weights over the network). Model
load alone is 10–30s. An app that shows nothing during this will look frozen.
Minimum: show a loading indicator or status message ("Setting up local AI…")
from the moment setup begins until the first response arrives — covering the
entire sequence above, not just the final load. The simplest implementation
is a flag set when setup/first-request starts and cleared when the first
response arrives. Once the model is pulled and loaded once, subsequent runs are
fast; the long wait is first-run only.
首次运行冷启动不仅是模型加载。完整流程为:
服务器启动 → 健康检查200 → 后端安装 → 模型下载 → 模型加载 → 首次结果返回在全新机器上,后端安装和模型下载可能需要数十秒到数分钟(多GB权重通过网络下载)。仅模型加载就需要10–30秒。如果应用在此期间无任何显示,会看似冻结。
最低要求:从设置开始到首次结果返回期间,显示加载指示器或状态消息(“正在设置本地AI…”)——覆盖上述整个流程,而非仅最终的加载步骤。最简单的实现方式是设置一个标志,在设置/首次请求开始时置位,首次结果返回时复位。模型拉取并加载一次后,后续运行会很快;长时间等待仅出现在首次运行时。
Step 7: Lifecycle and recovery
步骤7:生命周期与恢复
These are the only failure modes worth handling. Do not over-engineer.
| Symptom | Cause | Recovery |
|---|---|---|
| Inference returns empty / blank with HTTP 200, no error | Model never pulled: backend is installed but weights are absent, so lazy-load has nothing to load | |
| Model not pulled yet (same root cause as the empty-result row above) | |
| Backend not installed for this hardware | |
| Subprocess exits immediately | Port race: another process grabbed the port between | The reference launcher retries with a fresh port automatically (3 attempts) |
| First-run backend extraction is slow on cold disk | Extend timeout to 90s on first launch, 30s after |
| HTTP 401 on every request | Forgot the | Audit the client config because Lemonade rejects unauth'd calls when |
Shutdown: On app exit, (Unix) or
(Windows). flushes config and exits cleanly within a
couple of seconds. Always wait on the process; never orphan it.
proc.terminate()proc.kill()lemondDo not parse stdout to detect readiness; use the HTTP
probe. Stdout format is not a stable contract.
lemond/api/v1/health只需处理以下故障模式,无需过度设计。
| 症状 | 原因 | 恢复方式 |
|---|---|---|
| 推理返回空/空白结果,HTTP 200,无错误 | 模型从未拉取:后端已安装但权重缺失,因此懒加载无内容可加载 | 调用 |
| 模型未拉取(与上述空白结果的根本原因相同) | 调用 |
| 当前硬件未安装对应后端 | 调用 |
| 子进程立即退出 | 端口冲突:在 | 参考启动器会自动使用新端口重试(最多3次) |
| 首次运行时后端在冷磁盘上提取速度慢 | 首次启动时将超时延长至90秒,后续启动为30秒 |
| 所有请求返回HTTP 401 | 忘记添加 | 检查客户端配置,因为当设置 |
关闭流程: 应用退出时,调用(Unix)或(Windows)。会刷新配置并在几秒内干净退出。始终等待进程结束;切勿让其成为孤儿进程。
proc.terminate()proc.kill()lemond请勿通过解析的stdout来检测就绪状态;请使用HTTP 探测。stdout格式并非稳定的契约。
lemond/api/v1/healthVerification checklist
验证清单
The integration is done when all of these are true:
- contains the full package:
vendor/lemonade/,lemond[.exe],lemonade[.exe], andLICENSE— not just the binary.resources/ - starts as a subprocess with a fresh API key per launch.
lemond - returns 200 within the timeout.
GET /api/v1/health - The default model is pulled (or bundled) before the first inference; a
custom/overridden model is confirmed via and then pulled. A blank result with no error means this step was skipped.
GET /api/v1/models - Each lifecycle stage logs a clear line (spawn, health, backend install, model pull, first result) so a failure is diagnosable from the console.
- The existing client's chat / image / speech call returns a valid response with the base URL and key swapped, with no other code changed.
- First-run latency is surfaced: the interface shows a loading state from the moment the first inference request is sent until the response arrives.
- The HTTP client timeout is set to 120 seconds.
- In local mode the app requires no cloud API key: no onboarding wall, validator, or startup check blocks the user, and no code path throws "API key not configured" when the active mode is local.
- If the app uses a dev-mode file watcher, is excluded from the watched paths so runtime writes by lemond do not trigger restarts.
vendor/lemonade/ - Killing the parent process leaves no subprocess behind.
lemond - On a fresh machine without the optimal backend, the app still works
via the Vulkan fallback bundled in .
bin/
If any box is unchecked, do not declare the task complete.
当所有以下条件满足时,集成完成:
- 包含完整包:
vendor/lemonade/、lemond[.exe]、lemonade[.exe]和LICENSE——而非仅二进制文件。resources/ - 作为子进程启动,每次启动生成新的API密钥。
lemond - 在超时时间内返回200。
GET /api/v1/health - 默认模型在首次推理前已拉取(或已打包);自定义/覆盖的模型先通过确认,然后拉取。无错误提示的空白结果表示此步骤被跳过。
GET /api/v1/models - 每个生命周期阶段都有清晰的日志记录(启动、健康检查、后端安装、模型拉取、首次结果),以便通过控制台排查故障。
- 现有客户端的聊天/图像/语音调用在仅替换base URL和密钥的情况下返回有效响应,无需修改其他代码。
- 首次运行延迟已展示:界面从首次推理请求发送到结果返回期间显示加载状态。
- HTTP客户端超时已设置为120秒。
- 本地模式下应用无需云API密钥:无引导页拦截、验证器或启动检查阻止用户,且本地模式下无代码路径抛出“API密钥未配置”错误。
- 如果应用使用开发模式文件监视器,已被排除在监视路径之外,避免lemond运行时写入触发应用重启。
vendor/lemonade/ - 杀死父进程后无子进程残留。
lemond - 在无最优后端的全新机器上,应用仍可通过中打包的Vulkan fallback正常运行。
bin/
如果有任何一项未勾选,请勿宣布任务完成。
Reference
参考资料
For detailed model catalog, backend selection matrix, full endpoint reference,
config keys, and per-model tuning, see
reference.md.
recipe_options.json如需详细的模型目录、后端选择矩阵、完整端点参考、配置项和各模型的调优,请查看reference.md。
recipe_options.json