Joplin Transcribe 部署与 API 实战:基于 llama.cpp 的图片文字转录服务
Joplin Transcribe 部署与 API 实战基于 llama.cpp 的图片文字转录服务【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplinJoplin 仓库中的packages/transcribe是一个独立的自托管图片转录OCR/HTR服务它内置 llama.cpp 二进制通过 MiniCPM-o 视觉语言模型把图片中的文字转录为 Markdown 文本并以异步任务队列的方式对外提供POST /transcribe与GET /transcribe/{jobId}两个 HTTP 接口。本文以 packages/transcribe/README.md 为主线结合仓库源码讲解 Docker 部署、环境变量、GPU 加速、安全配置、API 调用与任务队列原理帮助你在一台服务器上完整跑通这套服务并能与 Joplin Server 集成使用。服务定位与整体架构从代码结构看transcribe 服务是一个典型的「HTTP 入口 任务队列 工作进程」三层架构HTTP API 层api/app.ts 使用 Koa 启动服务并监听端口api/router.ts 完成路由分发与 API Key 鉴权任务队列层提交的图片会先落盘然后以任务Job形式进入队列。队列支持 SQLiteSqliteQueue.ts与 PostgreSQLpg-boss驱动见 PgBossQueue.ts两种实现转录工作进程workers/JobProcessor.ts 周期性从队列取任务调用 core/HtrCli.ts 封装好的llama-mtmd-cli命令行工具完成转录把结果写回队列并清理临时图片。服务内部通过环境变量TRANSCRIBE_ENABLED、TRANSCRIBE_BASE_URL、TRANSCRIBE_API_KEY与 Joplin Server 联动见仓库根目录 docker-compose.server.yml 中的app服务配置因此它可以作为 Joplin Server 的附件转录后端为笔记中的图片自动生成文字内容。一、Docker 部署下载模型并准备数据目录transcribe 的 Docker 镜像直接内置了 llama.cpp 二进制但AI 模型需要单独下载并通过卷挂载提供给容器。1. 创建数据目录并下载模型mkdir -p ./data/models chmod 755 ./data wget -O ./data/models/Model-7.6B-Q4_K_M.gguf https://huggingface.co/openbmb/MiniCPM-o-2_6-gguf/resolve/main/Model-7.6B-Q4_K_M.gguf wget -O ./data/models/mmproj-model-f16.gguf https://huggingface.co/openbmb/MiniCPM-o-2_6-gguf/resolve/main/mmproj-model-f16.gguf两个模型文件的作用可以从 core/HtrCli.ts 的命令构造逻辑看出Model-7.6B-Q4_K_M.gguf主语言模型通过-m参数传入mmproj-model-f16.gguf多模态投影vision projector模型通过--mmproj参数传入负责把图像特征映射到语言模型。2. 配置环境变量复制.env-transcribe-sample到你的 Docker 配置目录重命名为.env-transcribe将API_KEY设置为一个安全值服务启动时会强制校验见下文。3. 启动服务docker run --rm --env-file .env-transcribe -p 4567:4567 \ -v ./data:/data \ joplin/transcribe:amd64-latest容器启动后会在/data下自动创建以下内容images/—— 上传的图片models/—— AI 模型由你提供queue.sqlite3—— 任务队列数据库对应的路径派生逻辑在 src/env.ts 中HTR_CLI_IMAGES_FOLDER $DATA_DIR/images、HTR_CLI_MODELS_FOLDER $DATA_DIR/modelsSQLite 队列数据库为$DATA_DIR/queue.sqlite3。二、环境变量完整说明服务启动时会在 src/env.ts 读取并校验环境变量所有配置都有默认值。以下分为必需与可选两类说明。必需变量变量说明API_KEYAPI 请求的认证密钥。为空时服务直接拒绝启动DATA_DIR所有数据图片、模型、数据库的基础目录HTR_CLI_BINARY_PATHllama-mtmd-cli二进制的路径启动时的强校验逻辑见 api/app.ts三个变量任一为空都会抛出Error并退出进程。可选变量与默认值变量默认值说明SERVER_PORT4567HTTP 监听端口QUEUE_DRIVERpg源码默认值sqlite或pg。README 说明独立 Docker 部署默认使用 sqlitedocker-compose.server.yml中则显式设置 PG 相关变量走 PostgreSQLQUEUE_DATABASE_HOSTlocalhostPostgreSQL 队列主机仅pg驱动QUEUE_DATABASE_PORT5432PostgreSQL 端口仅pg驱动QUEUE_DATABASE_USER/QUEUE_DATABASE_PASSWORD空PostgreSQL 凭据仅pg驱动QUEUE_DATABASE_NAMEtranscribePostgreSQL 库名SQLite 驱动下固定为$DATA_DIR/queue.sqlite3QUEUE_TTL15 分钟活跃任务超时时间超时会被重新入队QUEUE_RETRY_COUNT2任务最大重试次数超过则标记为失败QUEUE_MAINTENANCE_INTERVAL60 秒队列维护超时任务回收间隔FILE_STORAGE_TTL7 天上传图片的保留时长超过后由维护任务清理FILE_STORAGE_MAINTENANCE_INTERVAL1 小时图片存储清理间隔IMAGE_MAX_DIMENSION400上传图片会被缩放到该最大边长像素控制模型输入尺寸HTR_CLI_GPU_LAYERS0卸载到 GPU 的模型层数0表示纯 CPU设为较大值如9999则全部卸载到 GPU源码 src/env.ts 还展示了两个细节数字型变量会在解析时做Number()转换并校验合法性派生路径images、models、queue.sqlite3统一由DATA_DIR计算无需单独配置。三、使用 Docker Compose 部署仓库根目录的 docker-compose.server.yml 提供了最小化编排配置配合.env-sample使用执行cp .env-sample .env按需修改.env中的配置项启动服务docker compose -f docker-compose.server.yml --profile full up --detached该 Compose 文件定义了dbJoplin Server 的 PostgreSQL、appJoplin Server、transcribe-dbtranscribe 专用 PostgreSQL与transcribe四个服务fullprofile 会一并拉起transcribe服务使用joplin/transcribe:latest镜像挂载${HTR_CLI_IMAGES_FOLDER}到容器内/app/packages/transcribe/images、${HTR_CLI_MODELS_FOLDER}只读到/opt/models通过TRANSCRIBE_ENABLED、TRANSCRIBE_BASE_URLhttp://transcribe:4567、TRANSCRIBE_API_KEY三个变量把 Joplin Server 与 transcribe 打通实现笔记附件转录能力。对于更高级的配置可参考.env-sample-transcribe中提供的更多选项。四、安全设计README 明确列出了容器的安全加固措施这些措施在 docker-compose.server.yml 的transcribe服务中均有对应配置非 root 用户运行应用以transcribe用户运行而非 root只读文件系统容器文件系统只读仅/app/packages/transcribe/images与/tmp可写Compose 中通过read_only: true与tmpfs: /tmp实现资源限制内存与 CPU 限制防止失控进程耗尽宿主机资源Compose 中为memory: 16G、cpus: 4无需 Docker socket与早期版本不同不再需要挂载 Docker socket。此外core/HtrCli.ts 在调用 CLI 前会对图片文件名做路径穿越防护使用basename()校验文件名若包含..或与原始名不一致则直接抛出异常。五、开发环境搭建与测试启动开发服务器在packages/transcribe目录下执行yarn start该命令实际运行node dist/src/api/app.js见 package.json因此开发时需要先通过yarn rebuildclean build tsc生成dist产物。运行测试yarn test-all需要注意的是需要完整模型的集成测试默认不运行包括 CI 上。被禁用的测试位于 workers/JobProcessor.test.ts只有设置TRANSCRIBE_RUN_ALL1即test-all脚本才会执行。因此在修改模型或提示词prompt时需格外谨慎并主动运行该测试验证。除了集成测试仓库还提供了大量针对单元逻辑的测试例如 core/HtrCli.test.ts命令构造与输出清理以及 api/utils/isFileAValidImage.test.ts上传文件类型校验。六、GPU 加速配置服务默认使用 CPU 推理。根据 README可以按运行环境选择以下三种 GPU 方案。1. Docker NVIDIA GPU使用 CUDA 镜像并传入--gpus all同时设置HTR_CLI_GPU_LAYERS9999把全部模型层卸载到 GPUdocker run --rm --gpus all --env-file .env-transcribe -p 4567:4567 \ -e HTR_CLI_GPU_LAYERS9999 \ -v ./data:/data \ joplin/transcribe:gpu-latest前提是宿主机已安装 NVIDIA Container Toolkit。设置为0或不设置则回退到 CPU。2. Windows 原生运行Windows 下可在宿主机原生运行使用 Windows x64 的 CUDA 版llama-mtmd-cli.exe然后设置HTR_CLI_GPU_LAYERS为要卸载到 GPU 的层数。3. macOSApple Silicon / MetalApple Silicon 上 GPU 加速只能在宿主机原生运行Docker 无法把 Metal 暴露给容器使用支持 Metal 的 macOS ARM64 版llama-mtmd-cli再设置HTR_CLI_GPU_LAYERS。原生 GPU 配置示例二进制可从 llama.cpp 官方发布页下载。原生运行的环境变量如下HTR_CLI_BINARY_PATH/path/to/llama-mtmd-cli HTR_CLI_GPU_LAYERS9999在该配置下服务会向llama-mtmd-cli传递-ngl 9999GPU 层数参数GPU 能力完全由所选二进制决定。对应代码见 core/HtrCli.tsif (gpuLayers 0) args.push(-ngl, String(gpuLayers))。七、API 接口详解所有请求都必须携带Authorization请求头值为你配置的API_KEY。鉴权中间件为 api/auth/authorizationGuard.ts。POST/transcribe—— 创建转录任务上传的图片会被缩放按IMAGE_MAX_DIMENSION、落盘并在数据库中创建一条任务记录。处理链路见 api/handler/createJob.tsresizeImageAndDeleteInput→storeImage→sendToQueue。请求体Content-Typemultipart/form-data字段file必填—— 要处理的图片文件响应{ jobId: bcd2e633-eb10-44cb-a280-bf723238c12e }cURL 示例curl --request POST \ --url http://localhost:4567/transcribe \ --header Authorization: api-key \ --header Content-Type: multipart/form-data \ --form file/home/js/Pictures/2025-07-24_17-42_1.pngGET/transcribe/{jobId}—— 查询任务结果用POST /transcribe返回的jobId轮询任务状态。任务状态机定义在 src/types.tscreated(0) →retry(1) /active(2) →completed(3) /cancelled(4) /failed(5)。各状态响应示例任务刚创建{ id: 57ebd2e2-b496-40ab-9008-5f861bcb7858, state: created }正在处理中{ id: 07f09553-f5e9-467e-b98d-406778e61969, state: active }处理完成output.result为 Markdown 格式的转录文本{ id: 57ebd2e2-b496-40ab-9008-5f861bcb7858, completedOn: 2025-06-11T18:20:22.000Z, output: { result: markdown\r\n# Main title\r\n\r\nSome text here. This should take more than one line.\r\n\r\n## Sub title\r\n\r\n- One kind\r\n - of list\r\n - sub-item\r\n\r\n## Conclusion\r\n\r\nLets finish here. }, state: completed }cURL 示例curl --request GET \ --url http://localhost:4567/transcribe/57ebd2e2-b496-40ab-9008-5f861bcb7858 \ --header Authorization: api-key路由层的实际分发逻辑见 api/router.tsPOST /transcribe走createJobGET路径包含/transcribe则按jobId查询其余路径统一返回 404 错误。八、任务处理流程与队列原理提交任务后后台的工作进程 workers/JobProcessor.ts 会以5 秒为间隔checkInterval 5000轮询队列queue.fetch()取出一个任务将其状态置为active调用workHandler.run(filePath)执行转录即调用 llama.cpp 二进制成功后queue.complete(jobId, { result })写入结果并删除落盘的临时图片失败则queue.fail(jobId, error)若重试次数超限QUEUE_RETRY_COUNT默认 2同样清理图片。在 SQLite 队列实现 SqliteQueue.ts 中可以看到更多细节写入任务遇到SQLITE_BUSY时最多自动重试 3 次间隔500ms * iteration定时维护任务会把「已超时QUEUE_TTL默认 15 分钟且未超过重试上限」的active任务重新置为retry并递增retry_count任务表与队列表通过 sqlite_queue_migrations 中的 knex 迁移创建任务状态以 tinyint 存储。九、底层实现OCR 提示词与采样参数转录质量的核心在 core/HtrCli.ts 中构造的 llama.cpp 命令。系统提示词system prompt明确约束模型行为SYSTEM: you are an agent of a OCR system. Your job is to be concise and correct. You should NEVER deviate from the content of the image. You should NEVER add any context or new information. Your only job should be to transcribe the text presented in the image as text without anything new information. The output for it should be inside triple backticks like:{{example}}. If you find no text, output an empty code block: . Your turn:其核心要求是只做忠实转录、不添加任何上下文或新信息、输出放在三重反引号内、无文本时输出空代码块。实际执行的命令参数HtrCli.ts如下参数值含义-m$MODELS/Model-7.6B-Q4_K_M.gguf主模型--mmproj$MODELS/mmproj-model-f16.gguf多模态投影模型-c4096上下文长度--temp0.05采样温度极低以保证确定性转录--top-p0.8核采样阈值--top-k100Top-K 采样--repeat-penalty1.05重复惩罚--image$IMAGES/$imageName待转录图片-psystem prompt提示词-nglHTR_CLI_GPU_LAYERSGPU 层数0 时追加命令输出的清理逻辑cleanUpResult也很有参考价值丢弃image decoded行之前的工具日志、截掉llama_perf_context_print:之后的性能日志最后剥掉模型按提示词要求输出的三重反引号得到纯 Markdown 结果。十、总结与进一步阅读通过本文可以完整掌握 Joplin Transcribe 的部署与使用下载模型、配置环境变量、以 Docker 或 Compose 启动、按需启用 GPU 加速、调用两个 REST 接口提交与查询转录任务并理解其背后的任务队列与 llama.cpp 调用原理。整个服务以「队列解耦 只读容器 无 Docker socket」的方式设计兼顾了异步扩展与安全性。如需深入推荐继续阅读以下仓库文件packages/transcribe/README.md —— 官方部署文档本文主体来源packages/transcribe/src/env.ts —— 全部环境变量及默认值packages/transcribe/src/core/HtrCli.ts —— llama.cpp 命令构造与输出清洗packages/transcribe/src/workers/JobProcessor.ts —— 后台工作进程packages/transcribe/src/services/queue/SqliteQueue.ts —— SQLite 队列实现packages/transcribe/src/api/router.ts —— 路由与鉴权docker-compose.server.yml —— 与 Joplin Server 联动的编排示例【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考