
先还原一下场景日常工作中经常要“看一眼图片/网页/截图然后让大模型写总结、提取信息、生成文案”。如果每次都把图片传到网页端再复制结果效率很低。如果把豆包的多模态能力通过 API 接到 Quicker 动作里截图之后一键触发体验会顺畅很多。这篇文章会围绕“Quicker 豆包 API DeepSeek 工具链 多模态”这条主线给出一个最小可跑通的方案本地起一个 API 网关统一处理图片编码和模型调用再让 Quicker 通过命令行或 HTTP 请求触发它。整个过程不会依赖某个收费中转站也不会绑定某个只能手动操作的客户端。你只需要拿到大模型厂商的 API Key然后用 FastAPI 写一个小服务最后在 Quicker 里挂一个动作即可。文章会包含完整代码、调用示例、Quicker 配置思路和常见报错排查适合刚接触 API 接入的开发者也适合想给 Quicker 增加 AI 能力的效率工具玩家。1. 为什么要把多模态能力接到 Quicker 里1.1 桌面工具的重复劳动太多了使用电脑时很多操作其实是重复的打开截图工具、截取屏幕区域、保存图片、上传到某个模型网页、等待回复、复制结果、再粘贴到文档里。这一套流程看起来只要十几秒但一天重复几十次浪费的时间就很可观。Quicker 这类效率工具解决的正是“高频重复动作”的问题把固定流程封装成一个动作用鼠标或快捷键触发让电脑自动完成中间步骤。大模型 API 恰好能补上效率工具最缺的一环理解能力。Quicker 可以帮你截图、取词、读剪贴板但没有 AI 时它无法理解图片内容也无法生成一段完整文本。把豆包这种多模态模型接到 Quicker 里等于给桌面工具装上了“会看图的 AI 大脑”。1.2 本方案能覆盖哪些使用场景接入之后可以完成这些事对屏幕截图直接提问让模型总结当前页面内容。把本地图片路径作为参数传给动作自动生成图片描述或提取文字信息。选中一段文本后让模型润色、翻译或生成摘要。结合 OCR 需求用视觉模型完成简单票据、流程图、表格截图的信息提取。这些场景有一个共同点输入是图片/文本输出是文本中间逻辑相对固定非常适合封装成 API 服务。Quicker 只需要负责“触发动作”和“传参数”真正的 AI 处理逻辑全部交给本地 API 网关完成。1.3 为什么选择 API 而不是网页端豆包网页版本身很好用但如果把它嵌入到自动化流程里会遇到两个问题一是网页端没有稳定、公开的自动化接口依赖页面元素选择器容易被改版破坏二是网页端更适合人工交互不适合批量、参数化调用。API 的优势是稳定和可控一次编码永久复用可以调整模型参数比如温度、超时时间、图片分辨率可以把 Key 集中管理而不是暴露在每个脚本里。虽然需要一点开发量但这套服务写完一次后续所有 Quicker 动作都可以共用长期收益非常明显。2. 核心概念多模态、API 与工具链2.1 多模态模型到底“多”在哪先看一个容易混淆的点传统文本模型输入的是纯文字输出也是纯文字多模态模型除了文本还能处理图片、音频等输入。本文重点关注“视觉理解能力”也就是把图片传给模型让模型回答图片里有什么、文字是什么、图表表达了什么。豆包的多模态模型就属于这一类。你可以传一张截图、一个表情包、一张表格照片模型会结合你的文字提示词给出回答。这种能力在自动化工具里价值很大因为很多桌面信息本身就是以图像形式存在的截图比提取底层文本数据要容易得多。2.2 OpenAI 兼容 API 格式现在国内大模型厂商提供的 API 很多都兼容 OpenAI 的接口风格也就是 POST 一个 JSON 到/chat/completions消息体里包含model、messages等字段。兼容格式最大的好处是切换成本低换一家模型厂商往往只需要改base_url、api_key、model三个参数代码主体不用动。多模态消息在 OpenAI 兼容格式里通常写成这样{ model: your-model-id, messages: [ { role: user, content: [ {type: text, text: 请描述这张图片}, {type: image_url, image_url: {url: data:image/jpeg;base64,....}} ] } ] }也就是说文本和图片被放在同一个content数组里用type区分类型。这个格式不需要死记硬背理解结构后用 Python 字典构造即可。2.3 豆包、DeepSeek 与 deepseekharness 的定位差异很多读者看到“豆包 2 API”“DeepSeek Harness”这类词时容易混淆。先做个简单区分豆包字节跳动推出的大模型产品提供网页端、客户端和 API。多模态能力可以通过火山方舟平台开通API 风格为 OpenAI 兼容。DeepSeek深度求索推出的大模型以文本推理和代码能力见长API 也是 OpenAI 兼容风格。它的优势更多在长文本推理、代码生成视觉理解能力需要看你开通的具体模型。deepseekharness社区中出现的工具类项目名称通常指把 DeepSeek API 或相关模型能力包装成更易用工具链的封装项目。由于这类项目版本变化快、维护主体不固定使用前需要看源代码和文档不能盲目照搬命令行。本文的落地思路不绑定某个特定封装项目而是用标准的 OpenAI 兼容 API 方式实现“豆包做视觉理解、DeepSeek 做文本推理”的组合。这样即使社区工具更新迭代你的基础代码仍然可以继续使用。3. 整体架构与准备3.1 架构设计整体流程可以拆成三层Quicker 动作 ↓ 本地图片/文本参数 本地 API 网关FastAPI ↓ OpenAI 兼容请求 豆包/DeepSeek API云端多模态模型Quicker 只负责触发和传参本地 API 网关负责校验参数、处理图片 Base64、调用上游模型、返回统一格式云端模型负责真正的理解与生成。把 API 网关放在本地而不是干脆让 Quicker 直接请求云端原因有三点图片需要先转成 Base64这段逻辑放在服务端更利于复用不同 Quicker 动作都调用同一个接口。API Key 只需要配置在本地服务环境变量里Quicker 动作里不出现密钥。后续要加缓存、审计日志、限流都只需要在网关层处理。3.2 环境清单本机环境如下Windows 10/11安装了 Quicker。Python 3.10建议使用 3.10 以上版本类型语法更友好。一个豆包多模态模型 API Key能通过火山方舟或其他兼容接口调用。一个可选的 DeepSeek API Key用于纯文本推理场景。版本不是固定要求。如果你的环境是 Linux或者 Python 3.9代码稍作调整也能运行。核心是理解“请求-响应”结构而不是死磕某个版本。3.3 项目目录建议创建如下目录结构multimodal-gateway/ ├── service/ │ ├── requirements.txt │ ├── .env │ ├── app.py ├── client/ │ ├── ask.py └── README.mdservice目录放 API 网关client目录放 Quicker 可以直接调用的命令行脚本。两者分离的好处是网关可以单独部署到服务器客户端可以在任意电脑上运行。4. 搭建统一多模态 API 服务4.1 初始化项目与安装依赖打开终端进入项目根目录创建虚拟环境cd multimodal-gateway python -m venv .venv .\.venv\Scripts\activate然后安装依赖。requirements.txt内容如下fastapi uvicorn httpx pydantic python-dotenv安装命令pip install -r requirements.txt如果网络环境受限可以换国内 pip 源例如pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple4.2 环境变量配置在service/.env里保存密钥和模型配置DOUBAO_BASE_URLhttps://ark.cn-beijing.volces.com/api/v3 DOUBAO_API_KEY你的豆包API Key DOUBAO_MODEL你的豆包视觉模型ID DEEPSEEK_BASE_URLhttps://api.deepseek.com DEEPSEEK_API_KEY你的DeepSeek API Key DEEPSEEK_MODELdeepseek-chat注意DOUBAO_MODEL必须填你在模型控制台里实际可用的模型 ID 或推理接入点 ID。不同账号、不同时间的可用模型不一样不要照抄网上的模型名以控制台展示为准。4.3 核心代码实现在service/app.py中写入以下代码# 文件路径service/app.py import os import base64 from typing import Optional import httpx from dotenv import load_dotenv from fastapi import FastAPI, HTTPException from pydantic import BaseModel load_dotenv() app FastAPI(titleMultimodal API Gateway) DOUBAO_BASE_URL os.environ.get(DOUBAO_BASE_URL, https://ark.cn-beijing.volces.com/api/v3) DOUBAO_API_KEY os.environ.get(DOUBAO_API_KEY, ) DOUBAO_MODEL os.environ.get(DOUBAO_MODEL, ) DEEPSEEK_BASE_URL os.environ.get(DEEPSEEK_BASE_URL, https://api.deepseek.com) DEEPSEEK_API_KEY os.environ.get(DEEPSEEK_API_KEY, ) DEEPSEEK_MODEL os.environ.get(DEEPSEEK_MODEL, deepseek-chat) class AnalyzeRequest(BaseModel): prompt: str 请描述这张图片的内容。 image_url: Optional[str] None image_base64: Optional[str] None image_type: Optional[str] image/jpeg use_deepseek: bool False def build_image_content(image_url: Optional[str], image_base64: Optional[str], image_type: str) - Optional[dict]: 把图片参数转成 OpenAI 兼容格式的 image_url 内容块。 if image_url: return {type: image_url, image_url: {url: image_url}} if image_base64: if image_base64.startswith(data:): return {type: image_url, image_url: {url: image_base64}} return { type: image_url, image_url: {url: fdata:{image_type};base64,{image_base64}}, } return None async def chat_completion(base_url: str, api_key: str, model: str, messages: list, timeout: float 60.0) - dict: 调用 OpenAI 兼容的 chat/completions 接口。 url base_url.rstrip(/) /chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json, } payload { model: model, messages: messages, temperature: 0.3, } async with httpx.AsyncClient(timeouttimeout) as client: response await client.post(url, headersheaders, jsonpayload) if response.status_code ! 200: raise HTTPException( status_code502, detailf上游API返回 {response.status_code}: {response.text}, ) return response.json() app.post(/analyze) async def analyze(req: AnalyzeRequest): has_image bool(req.image_url or req.image_base64) # 纯文本场景或者调用方明确指定 use_deepseekTrue 时走 DeepSeek if req.use_deepseek or not has_image: messages [{role: user, content: req.prompt}] data await chat_completion( DEEPSEEK_BASE_URL, DEEPSEEK_API_KEY, DEEPSEEK_MODEL, messages, ) return {engine: deepseek, text: data[choices][0][message][content]} # 有图片默认走豆包视觉理解 image_content build_image_content(req.image_url, req.image_base64, req.image_type) if image_content is None: raise HTTPException(status_code400, detail请提供 image_url 或 image_base64) messages [ { role: user, content: [ {type: text, text: req.prompt}, image_content, ], } ] data await chat_completion( DOUBAO_BASE_URL, DOUBAO_API_KEY, DOUBAO_MODEL, messages, ) return {engine: doubao, text: data[choices][0][message][content]} app.get(/health) async def health(): return {status: ok}这段代码做了三件事定义AnalyzeRequest请求体结构字段包括提示词、图片 URL、图片 Base64、图片类型和是否强制走 DeepSeek。实现build_image_content把图片参数统一转换成 OpenAI 兼容格式。实现chat_completion用httpx发送异步请求并统一处理非 200 响应。需要说明的是多模态模型的视觉输入格式在很多兼容接口里采用上述风格但不同厂商可能存在差异。如果豆包控制台返回 400 或参数错误优先去查你所用的模型文档确认图片字段格式不要硬套。4.4 启动与验证在service目录下启动服务uvicorn app:app --host 0.0.0.0 --port 8000看到类似输出表示启动成功INFO: Uvicorn running on http://0.0.0.0:8000先验证健康检查接口curl http://127.0.0.1:8000/health预期返回{status:ok}再用一段纯文本测试 DeepSeek 路由curl -X POST http://127.0.0.1:8000/analyze \ -H Content-Type: application/json \ -d {\prompt\: \用一句话介绍 Python\}如果 DeepSeek API Key 配置正确会返回类似{engine:deepseek,text:Python 是一种简单易学的编程语言……}到这里API 网关的基本功能已经跑通。5. 让 Quicker 快速调用5.1 编写命令行客户端服务端跑通后需要让 Quicker 能方便地调用。最简单的方式是写一个命令行客户端通过参数传入图片路径或提示词。在client/ask.py中写入# 文件路径client/ask.py import argparse import base64 import sys import requests def encode_image(path: str) - str: with open(path, rb) as f: return base64.b64encode(f.read()).decode(utf-8) def infer_image_type(path: str) - str: lower path.lower() if lower.endswith(.png): return image/png if lower.endswith(.webp): return image/webp return image/jpeg def main(): parser argparse.ArgumentParser(description调用本地多模态 API 网关) parser.add_argument(--image, help本地图片路径) parser.add_argument(--prompt, default请描述这张图片的内容。) parser.add_argument(--server, defaulthttp://127.0.0.1:8000) parser.add_argument(--text-only, actionstore_true, help强制走文本模型) args parser.parse_args() payload {prompt: args.prompt} if args.image and not args.text_only: try: payload[image_base64] encode_image(args.image) payload[image_type] infer_image_type(args.image) except FileNotFoundError: print(f图片文件不存在: {args.image}, filesys.stderr) sys.exit(1) else: payload[use_deepseek] True target args.server.rstrip(/) /analyze try: response requests.post(target, jsonpayload, timeout90) response.raise_for_status() result response.json() print(result[text]) except requests.exceptions.Timeout: print(请求超时请检查网络或稍后重试, filesys.stderr) sys.exit(1) except requests.exceptions.ConnectionError: print(无法连接本地服务请确认 uvicorn 已启动, filesys.stderr) sys.exit(1) if __name__ __main__: main()脚本逻辑很直接有图片时读取本地文件并转为 Base64没有图片时请求 DeepSeek 文本模型最终把模型的返回文本打印到标准输出。这样 Quicker 的动作就可以捕获到返回结果。5.2 测试命令行客户端先测文本模式python client/ask.py --prompt 写一封请假邮件 --text-only再测图片模式python client/ask.py --image D:\test.png --prompt 图中是什么内容如果图片较大第一次运行可能需要几秒到十几秒原因是要 Base64 编码和上传完整图片。返回结果会直接打印在终端里。5.3 在 Quicker 中创建动作Quicker 的动作编辑界面不同版本菜单名称可能略有不同但思路一致。新建一个空白动作然后添加一个“运行命令行”或“执行脚本”步骤命令填python D:\multimodal-gateway\client\ask.py --image 图片路径 --prompt 总结图中文字如果要在 Quicker 里动态传入当前截图路径可以把图片保存到固定临时目录然后用可变参数替换路径。Quicker 里常见的做法是动作第一步调用系统截图工具把图片保存到C:\Temp\screenshot.png。第二步执行ask.py--image指向这个固定路径。第三步把输出结果写入剪贴板或弹出提示框。如果你的 Quicker 版本支持“HTTP 请求”步骤也可以直接发送 POST 请求到http://127.0.0.1:8000/analyze请求体和上一节curl命令一样。区别是命令行客户端更适合带本地图片文件HTTP 请求更适合传图片 URL 或纯文本。5.4 扩展局域网与 Web 化部署前面启动服务时使用了--host 0.0.0.0意味着同一局域网内的其他设备也能访问8000端口。如果你有两台电脑可以把 FastAPI 服务部署在其中一台另一台 Quicker 动作里的--server改成局域网 IPpython ask.py --server http://192.168.1.100:8000 --image D:\test.png如果想把能力开放给 Web 页面也可以在 FastAPI 里追加一个 HTML 页面前端上传图片后 POST 到/analyze。这个方向就变成了一个轻量多模态应用Quicker 只是其中一个入口。6. 常见问题与排查6.1 模型返回 529 过载有读者在调用时遇到这样的错误api error: 529 overloaded. this is a server-side issue, usually temporary529 Overloaded表示上游模型服务暂时过载这是服务端问题通常不是你的代码错误。解决思路是稍后重试并在代码里做指数退避。最简单的实现是客户端遇到 5xx 错误时等待 2 秒、4 秒、8 秒再重试连续失败三次后放弃。也可以把请求错峰需要批量处理图片时设置一个请求间隔避免在高峰期集中排队。6.2 Key、模型名与权限问题问题现象常见原因解决思路401 UnauthorizedAPI Key 错误或过期检查.env里的 Key确认账号状态404 model not found模型 ID 不对登录模型控制台复制真实可用的模型 ID403 无权限账号未开通对应模型服务在控制台申请或购买对应模型权限这里提醒一下模型 ID 是很容易踩坑的点。很多人直接复制网上的模型名但账号没有开通该模型或者地区、接入点不一致就会报错。保守做法是到你所用的模型控制台里找到“推理接入点”或“在线体验”页看实际返回的模型标识。6.3 连接异常与超时命令行客户端常见的报错有ConnectionError: 无法连接本地服务先确认 uvicorn 是否还在运行再确认端口是否被占用。如果服务跑在远程服务器127.0.0.1要改成服务器 IP并确保防火墙放行端口。另一个场景是The socket connection was closed unexpectedly这通常和本地网络代理、系统代理有关。如果你开启了系统级网络代理命令行请求可能被代理拦截。排查时先临时关闭系统代理或者让httpx通过环境变量走代理确认是否恢复。注意这里说的是常规系统代理不是任何特殊上网工具。6.4 图片格式与大小问题图片过大是最容易导致超时的原因。一张 10MB 的截图转成 Base64 后大约 13MB 多上传传输都会变慢。建议在客户端增加压缩逻辑或者限制图片尺寸。使用 Pillow 可以快速压缩from PIL import Image def compress_image(path: str, max_size: int 1280, quality: int 85) - str: img Image.open(path) img.thumbnail((max_size, max_size)) img.save(compressed.png, optimizeTrue, qualityquality) return compressed.png在主流程里先调用压缩函数再用压缩后的图片转 Base64能显著降低 Token 消耗和网络耗时。Base64 解析失败的问题多半是格式前缀不对。标准格式一定是data:image/jpeg;base64,xxxxxx注意逗号不能少image/jpeg要和实际图片类型一致。如果代码里直接传了不带前缀的原始 Base64服务端也会自动补上但前提是image_type正确。7. 工程化与成本控制建议7.1 Key 安全管理API Key 是敏感信息绝对不能硬编码到 Quicker 动作或提交到 Git。建议做法是Key 只保存在本地服务的.env文件里。.env加入.gitignore。后续如果多个同事都需要使用可以在网关层做统一鉴权给每个调用方发独立的本地 Token避免所有人共用同一个大模型 Key。如果 Key 疑似泄露去控制台立即重置。这样即使某个 Quicker 动作脚本被误发出去也不会直接泄露云端模型 Key。7.2 日志与异常处理API 网关上线后建议给每个请求加上关键日志比如调用方 IP、目标引擎、耗时、返回状态。FastAPI 里可以用中间件实现import time from fastapi import Request app.middleware(http) async def log_requests(request: Request, call_next): start time.time() response await call_next(request) duration time.time() - start print(f{request.client.host} {request.method} {request.url.path} {response.status_code} {duration:.2f}s) return response日志里不要记录完整图片 Base64也不要记录完整请求体避免敏感内容落盘。真要记录可以只记图片大小、是否成功、耗时这些元信息。7.3 成本与性能优化多模态图片请求的 Token 消耗通常比纯文本高很多优化手段包括限制图片尺寸用小图完成任务。同一张图片短时间内多次提问时在本地做简单缓存比如按图片 MD5 值缓存结果。使用更精简的提示词减少上下文 Token。批量场景设置请求间隔避免触发上游限流。性能和成本往往是反比关系先明确场景需求再决定图片压缩到什么程度。如果只是提取截图里的文字不需要请求超高分辨率。7.4 使用社区工具前的检查清单如果你看到“deepseekharness”这类社区工具并希望使用先回答这几个问题项目仓库是否公开最近一次更新是什么时候依赖的 Python/Node 版本和你的环境是否兼容是否需要额外下载模型权重或连接外部服务项目是否提供单元测试或示例代码它在代码里是否有硬编码的远程地址或可疑请求社区工具能节省开发时间但也可能带来供应链风险。生产环境使用前至少要做一次代码审查不要直接在生产机器上运行来源不明的安装命令。8. 总结与后续扩展到这里你已经拥有一个最小可用的“Quicker 豆包 API DeepSeek 文本模型”多模态调用链路。这套方案的核心不是某个单独的模型而是把模型能力抽象成了统一 API 接口让效率工具可以随时调用。顺着这个思路可以继续扩展增加更多模型入口比如把通义、智谱等兼容接口都接进来根据任务自动路由。增加剪贴板监听截图后自动触发识别不用手动保存图片。加入 OCR 专用提示词模板把常见任务参数化方便 Quicker 里选择不同场景。把网关部署到内网服务器让整个办公室的设备都能共用同一个多模态入口。实际项目中优先关注的还是稳定性和成本先定好图片压缩策略再管好 API Key最后再想着加功能。工具越顺手使用频率越高越需要在早期设计好边界。如果你也经常被困在“截图-上传-复制-粘贴”的循环里可以直接把本文这段代码跑起来然后把一个最简单的“截图提问”动作挂到鼠标手势上试试。