Qwen Image Edit
qwen-image-edit、qwen-image-edit-plus、qwen-image-edit-max 图像编辑接口
Qwen Image Edit 系列通过参考图和编辑指令完成局部修改、文字替换、风格迁移、多图融合与构图调整。三个公开模型均使用同步 JSON 接口;能力与参数不可互换。
能力矩阵
| 能力 | qwen-image-edit | qwen-image-edit-plus | qwen-image-edit-max |
|---|---|---|---|
| 文本生成图片 | 不支持纯文本请求 | 不支持纯文本请求 | 不支持纯文本请求 |
| 图生图 / 局部编辑 | 支持 | 支持 | 支持,工业设计、几何与角色一致性更强 |
| 多图融合 | 支持 | 支持 | 支持 |
| 最大参考图 | 3 | 3 | 3 |
| 最大输出 | 固定 1 | 6,默认 1 | 6,默认 1 |
自定义 size | 不支持 | 支持 | 支持 |
| 默认尺寸 / 比例 | 由模型按输入推断 | 总像素接近 1024*1024,比例接近最后一张输入图 | 总像素接近 1024*1024,比例接近最后一张输入图 |
| 输出格式 | PNG | PNG | PNG |
| URL 输入 | 支持 | 支持 | 支持 |
| Base64 Data URL | 支持 | 支持 | 支持 |
| 流式响应 | 不在稳定范围 | 不在稳定范围 | 不在稳定范围 |
| Prompt 智能改写 | 不支持 | 支持,默认开启 | 支持,默认开启 |
seed / 水印 | 支持 / 支持 | 支持 / 支持 | 支持 / 支持 |
当前环境没有可安全使用的 API 凭据。上述模型服务能力来自后端、仓库接口 PDF 和官方资料的静态交叉核对,未通过 LoopToken 在线调用验证;超出本页明确范围的格式、尺寸或组合应视为不支持或未知。
接口
- Endpoint:
POST /v1/images/edits - Base URL:
https://api.vibelab.me Authorization: Bearer <your-api-key>Content-Type: application/json
请求体使用 input.messages 放置图片和文字,使用 parameters 放置生成选项。不要发送 multipart 表单。
参数
| 参数 | 类型 | 必填 | 默认值 | 范围 / 格式 | 适用性与依赖 |
|---|---|---|---|---|---|
model | string | 是 | - | 三个精确模型名之一 | 所有请求 |
input | object | 是 | - | 包含 messages | 所有请求 |
input.messages | array | 是 | - | 恰好 1 个元素 | 仅单轮 |
input.messages[0].role | string | 是 | - | 固定 user | 所有请求 |
input.messages[0].content | array | 是 | - | 1-3 个 image 和恰好 1 个 text | 图片顺序有意义 |
content[].image | string | 是 | - | 公网 URL 或 data:image/<mime>;base64,... | 每张图片一个内容项 |
content[].text | string | 是 | - | 中英文;基础版/Plus 最多 800 Token,Max 最多 1300 Token | 仅 1 个文字项;可用“图一”“图二”指代顺序 |
parameters | object | 否 | {} | 下列字段 | 省略使用模型默认值 |
parameters.size | string | 否 | 按输入推断 | 宽*高 | 仅 Plus/Max;宽、高各 512-2048,实际结果对齐到最接近的 16 倍数 |
parameters.n | integer | 否 | 1 | Plus/Max 为 1-6 | 基础版的模型约束是固定 1;不要提交其他值。LoopToken 不在本地强制该限制 |
parameters.negative_prompt | string | 否 | 空 | 最多 500 字符 | 三个模型 |
parameters.prompt_extend | boolean | 否 | true | true / false | 仅 Plus/Max;开启可能增加耗时 |
parameters.watermark | boolean | 否 | false | true / false | 三个模型;开启后右下角添加模型水印 |
parameters.seed | integer | 否 | 随机 | 0-2147483647 | 三个模型;相同值不保证完全一致 |
未列出的顶层字段和参数没有稳定支持承诺。LoopToken 当前主要读取 model、parameters.n 与 parameters.size 做路由和计费,其他字段由模型服务校验;越界通常会归一为 502 upstream_error。
图片输入
URL 引用
{
"model": "qwen-image-edit-plus",
"input": {
"messages": [{
"role": "user",
"content": [
{"image": "https://example.com/photo.jpg"},
{"text": "将背景改为星空,保持人物不变"}
]
}]
}
}URL 必须能被模型服务直接访问,不能依赖 Cookie、登录态、内网 DNS 或临时请求头。建议使用 HTTPS,并保证处理期间持续可访问;重定向、签名 URL 到期和防盗链均可能导致 502。
Base64 Data URL
{
"model": "qwen-image-edit",
"input": {
"messages": [{
"role": "user",
"content": [
{"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."},
{"text": "将图片改成水彩风格"}
]
}]
}
}必须包含 data: 前缀、准确 MIME 和 ;base64,。已记录格式为 JPEG/JPG、PNG、BMP、WEBP,单张不超过 10 MB。不要发送裸 Base64。GIF、TIFF、HEIC、SVG、带 alpha 的透明像素保留方式、最小输入尺寸、输入总像素和极端宽高比均未建立稳定范围。
多图按 content 数组顺序编号,提示词可明确写“图一”“图二”。未显式设置 size 时,Plus/Max 的输出比例接近最后一张参考图。不要依赖未描述的主体自动配对或输出顺序语义。
尺寸
qwen-image-edit 不接受自定义尺寸。Plus 和 Max 使用同一范围:宽、高各 512-2048;服务可能将结果调整为最接近的 16 倍数。
| 比例 | 推荐尺寸 |
|---|---|
| 1:1 | 1024*1024、1536*1536 |
| 2:3 | 768*1152、1024*1536 |
| 3:2 | 1152*768、1536*1024 |
| 3:4 | 960*1280、1080*1440 |
| 4:3 | 1280*960、1440*1080 |
| 9:16 | 720*1280、1080*1920 |
| 16:9 | 1280*720、1920*1080 |
| 21:9 | 1344*576、2048*872 |
请求示例
最小合法请求见上方 URL 示例。完整参数:
{
"model": "qwen-image-edit-max",
"input": {"messages": [{"role": "user", "content": [
{"image": "https://example.com/product.png"},
{"text": "保留产品结构,将材质改为磨砂金属,白色棚拍背景"}
]}]},
"parameters": {
"size": "1536*1024",
"n": 2,
"negative_prompt": "模糊,结构变形,文字乱码",
"prompt_extend": false,
"watermark": false,
"seed": 20260712
}
}多参考图融合:
{
"model": "qwen-image-edit-max",
"input": {"messages": [{"role": "user", "content": [
{"image": "https://example.com/city.jpg"},
{"image": "https://example.com/character.png"},
{"text": "以图一为背景,将图二角色自然放在街道中央,保持角色服装和面部特征"}
]}]}
}文字局部编辑:
{
"model": "qwen-image-edit-plus",
"input": {"messages": [{"role": "user", "content": [
{"image": "https://example.com/sign.png"},
{"text": "只把招牌文字改为“LoopToken”,其余画面不变"}
]}]}
}中文海报:
{
"model": "qwen-image-edit-max",
"input": {"messages": [{"role": "user", "content": [
{"image": "https://example.com/poster.png"},
{"text": "保留版式,将主标题改为“向海而歌”,日期改为“7月20日 18:00”"}
]}]},
"parameters": {"size": "1080*1440", "n": 1, "prompt_extend": false}
}群体合成:
{
"model": "qwen-image-edit-max",
"input": {"messages": [{"role": "user", "content": [
{"image": "https://example.com/person-a.png"},
{"image": "https://example.com/person-b.png"},
{"image": "https://example.com/studio.jpg"},
{"text": "将图一和图二的人物并排放入图三摄影棚,保持各自面部和服装特征"}
]}]}
}本接口没有已确认的流式请求示例;不要发送 stream。
cURL
curl --fail-with-body https://api.vibelab.me/v1/images/edits \
-H "Authorization: Bearer $LOOPTOKEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"qwen-image-edit-plus","input":{"messages":[{"role":"user","content":[{"image":"https://example.com/photo.jpg"},{"text":"将背景改为雪山"}]}]},"parameters":{"n":1}}'Python
import mimetypes, os
from pathlib import Path
from urllib.parse import urlparse
import requests
url = "https://api.vibelab.me/v1/images/edits"
payload = {"model": "qwen-image-edit-plus", "input": {"messages": [{"role": "user", "content": [
{"image": "https://example.com/photo.jpg"}, {"text": "将背景改为雪山"}
]}]}, "parameters": {"n": 2}}
r = requests.post(url, headers={"Authorization": f"Bearer {os.environ['LOOPTOKEN_API_KEY']}"}, json=payload, timeout=300)
try:
body = r.json()
except ValueError:
body = {"error": {"message": r.text}}
if not r.ok:
raise RuntimeError(f"HTTP {r.status_code}: {body.get('error', body)}")
images = [item["image"] for choice in body.get("output", {}).get("choices", [])
for item in choice.get("message", {}).get("content", []) if item.get("image")]
if not images:
raise RuntimeError("response contained no image")
for i, image_url in enumerate(images, 1):
download = requests.get(image_url, timeout=120); download.raise_for_status()
mime = download.headers.get("content-type", "").split(";", 1)[0]
ext = mimetypes.guess_extension(mime) or Path(urlparse(image_url).path).suffix or ".png"
Path(f"qwen-edit-{i}{ext}").write_bytes(download.content)Node.js
import { writeFile } from 'node:fs/promises';
import path from 'node:path';
const response = await fetch('https://api.vibelab.me/v1/images/edits', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LOOPTOKEN_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'qwen-image-edit-plus', input: { messages: [{ role: 'user', content: [
{ image: 'https://example.com/photo.jpg' }, { text: '将背景改为雪山' },
] }] }, parameters: { n: 2 } }),
});
const text = await response.text();
let body; try { body = JSON.parse(text); } catch { body = { error: { message: text } }; }
if (!response.ok) throw new Error(`HTTP ${response.status}: ${JSON.stringify(body.error ?? body)}`);
const images = (body.output?.choices ?? []).flatMap((c) => (c.message?.content ?? []).filter((x) => x.image));
if (!images.length) throw new Error('response contained no image');
const extensions = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp', 'image/bmp': '.bmp' };
for (const [i, item] of images.entries()) {
const download = await fetch(item.image);
if (!download.ok) throw new Error(`download failed: HTTP ${download.status}`);
const mime = (download.headers.get('content-type') ?? '').split(';', 1)[0];
const ext = extensions[mime] ?? (path.extname(new URL(item.image).pathname) || '.png');
await writeFile(`qwen-edit-${i + 1}${ext}`, Buffer.from(await download.arrayBuffer()));
}响应
{
"output": {"choices": [{"finish_reason": "stop", "message": {"role": "assistant", "content": [
{"image": "https://example.invalid/generated/edit-1.png?Expires=0"},
{"image": "https://example.invalid/generated/edit-2.png?Expires=0"}
]}}]},
"usage": {"image_count": 2, "width": 1536, "height": 1024}
}| 字段 | 类型 | 说明 |
|---|---|---|
output.choices[] | array | choice 顺序按响应原样保留 |
output.choices[].finish_reason | string | 正常完成通常为 stop |
output.choices[].message.role | string | 通常为 assistant |
output.choices[].message.content[] | array | 图片按数组顺序处理,不假设与输入图一一对应 |
output.choices[].message.content[].image | string | PNG 临时下载 URL |
usage.image_count | integer | 返回图片数 |
usage.width / usage.height | integer | 实际输出像素 |
Qwen Image 编辑的成功响应体由模型服务透传。usage 及其他附加字段只有在模型服务实际返回时才存在,LoopToken 不向响应体注入 size 或 request_id。LoopToken 请求 ID 位于 HTTP 响应头 X-Request-Id;排查时应同时保存该响应头。
下载链接是临时资源,应立即下载并转存。具体有效期未经在线验证,不属于稳定合同;不要依赖固定下载域名、长期可恢复性或 URL 排序语义。
错误处理
{"error":{"message":"invalid image generation request","type":"invalid_request","code":"invalid_request","request_id":"req_example"}}| HTTP | error.type / error.code | 含义 |
|---|---|---|
| 400 | invalid_request | LoopToken 无法读取请求体、JSON 无法解析,或 model 缺失、为空、不是可读取的字符串 |
| 401 | 鉴权错误 | API Key 缺失或无效 |
| 402 | insufficient_credits | 余额不足 |
| 403 | model_not_allowed | Key 的模型白名单不允许 |
| 404 | model_not_found | 未知模型名 |
| 502 | upstream_error | 模型服务返回非 2xx,例如拒绝参数、素材或内容 |
| 503 | no_available_channel | 当前没有可处理该模型的服务资源;也可能在可重试失败后返回 |
模型服务错误会统一转换为 HTTP 502 upstream_error,不透出其内部错误码。客户端应先检查 HTTP 状态,再读取 error.message、error.code 和 error.request_id。上述错误包络来自实现,因缺少凭据尚未在线验证。
缺少 input、input.messages、图片或文字指令不会被 LoopToken 本地归为 400;普通 Qwen 请求会先转发。模型服务若以非 2xx 拒绝,会被归一为 HTTP 502 upstream_error。
普通 Qwen 路径若收到模型服务 2xx,即使 LoopToken 无法识别其中的图片,也会原样返回 HTTP 200;该情况不会转换为 upstream_error,计费按下节所述回退到提交的 n。
计费
三个模型均按图片计费。请求开始按提交的正数 parameters.n 预扣,省略、零或负数时按 1 张;此逻辑对三个模型相同。基础版固定输出 1 张是模型服务约束,不是 LoopToken 的本地预扣约束:误提交 n>1 可能先按该数预扣,模型服务拒绝并返回非 2xx 时会全额退款。成功响应按 output.choices[].message.content[].image 中识别到的图片数结算;模型服务返回 2xx 但得到 Count=0 时,结算回退到上述本地提交数量。usage 只记录模型服务用量,不代表扣费金额。尺寸价格键未命中时使用当前模型的 default 单价。最终价格以模型列表为准。