玄枢API
图片生成与编辑示例
用 gpt-image-2 完成文生图、图生图与图片编辑,包含可运行的 Python 与 Node.js 流式示例,以及常见问题排查。
1. 准备 API Key
- 还没有账户的先到 /register 注册。
- 在 /keys 创建 Key,把示例里的 <YOUR_API_KEY> 换成完整值。
- 在 /available-channels 确认 gpt-image-2 对当前 Key 可见。
2. 文生图
只给提示词,不传参考图。响应用 SSE 推送,partial_image 事件表示进度,最终事件里的 b64_json 就是图片。
import base64
import json
import urllib.request
from pathlib import Path
API_URL = "https://www.xuanshuapi.com/v1/images/generations"
API_KEY = "<YOUR_API_KEY>"
def iter_sse(response):
buffer = ""
while chunk := response.read(4096):
buffer += chunk.decode("utf-8", errors="replace")
frames = buffer.split("\n\n")
buffer = frames.pop()
for frame in frames:
payload = [
line[5:].strip()
for line in frame.splitlines()
if line.startswith("data:")
]
data = "\n".join(payload).strip()
if data and data != "[DONE]":
yield data
body = {
"model": "gpt-image-2",
"prompt": "一只在太空里漂浮的猫,科技感插画风格",
"n": 1,
"size": "1024x1024",
"stream": True,
"response_format": "b64_json",
}
request = urllib.request.Request(
API_URL,
data=json.dumps(body).encode("utf-8"),
method="POST",
headers={
"Authorization": "Bearer " + API_KEY,
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
)
with urllib.request.urlopen(request, timeout=900) as response:
for data in iter_sse(response):
event = json.loads(data)
if event.get("type") == "image_generation.partial_image":
print(".", end="", flush=True)
image = (
event.get("b64_json")
or ((event.get("data") or [{}])[0]).get("b64_json")
or (event.get("item") or {}).get("result")
)
if image:
Path("generated-image.png").write_bytes(base64.b64decode(image))
print("\n已保存:generated-image.png")
breakimport { writeFile } from "node:fs/promises";
const API_URL = "https://www.xuanshuapi.com/v1/images/generations";
const API_KEY = "<YOUR_API_KEY>";
async function* readSse(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split(/\r?\n\r?\n/);
buffer = frames.pop() || "";
for (const frame of frames) {
const data = frame
.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).trim())
.join("\n");
if (data && data !== "[DONE]") yield data;
}
}
}
const response = await fetch(API_URL, {
method: "POST",
headers: {
Authorization: "Bearer " + API_KEY,
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({
model: "gpt-image-2",
prompt: "一只在太空里漂浮的猫,科技感插画风格",
n: 1,
size: "1024x1024",
stream: true,
response_format: "b64_json",
}),
});
if (!response.ok) throw new Error(await response.text());
for await (const data of readSse(response)) {
const event = JSON.parse(data);
if (event.type === "image_generation.partial_image") process.stdout.write(".");
const image = event.b64_json ?? event.data?.[0]?.b64_json ?? event.item?.result;
if (image) {
await writeFile("generated-image.png", Buffer.from(image, "base64"));
console.log("\n已保存:generated-image.png");
break;
}
}3. 图生图(JSON 传参考图)
参考图用 images[].image_url 传入,支持多张,也接受 data URL。images[].file_id 不受支持,传了会直接报错。
import base64
import json
import urllib.request
from pathlib import Path
API_URL = "https://www.xuanshuapi.com/v1/images/edits"
API_KEY = "<YOUR_API_KEY>"
def iter_sse(response):
buffer = ""
while chunk := response.read(4096):
buffer += chunk.decode("utf-8", errors="replace")
frames = buffer.split("\n\n")
buffer = frames.pop()
for frame in frames:
payload = [
line[5:].strip()
for line in frame.splitlines()
if line.startswith("data:")
]
data = "\n".join(payload).strip()
if data and data != "[DONE]":
yield data
# 参考图用 images[].image_url 传入,支持多张,也可传 data URL。
# 注意:images[].file_id 不受支持。
body = {
"model": "gpt-image-2",
"prompt": "参考这张图,生成一张更精致的科技风品牌图。",
"images": [{"image_url": "https://www.xuanshuapi.com/brand/og-cover.png"}],
"n": 1,
"size": "1024x1024",
"quality": "auto",
"stream": True,
"response_format": "b64_json",
}
request = urllib.request.Request(
API_URL,
data=json.dumps(body).encode("utf-8"),
method="POST",
headers={
"Authorization": "Bearer " + API_KEY,
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
)
with urllib.request.urlopen(request, timeout=900) as response:
for data in iter_sse(response):
event = json.loads(data)
if event.get("type") == "image_generation.partial_image":
print(".", end="", flush=True)
image = (
event.get("b64_json")
or ((event.get("data") or [{}])[0]).get("b64_json")
or (event.get("item") or {}).get("result")
)
if image:
Path("generated-image.png").write_bytes(base64.b64decode(image))
print("\n已保存:generated-image.png")
break4. 图片编辑(multipart 上传)
直接上传图片文件并按提示词修改。这条路径用 multipart/form-data,不要手写 Content-Type,交给 FormData 生成 boundary。
import { writeFile } from "node:fs/promises";
const API_URL = "https://www.xuanshuapi.com/v1/images/edits";
const API_KEY = "<YOUR_API_KEY>";
const SOURCE_URL = "https://www.xuanshuapi.com/brand/og-cover.png";
async function* readSse(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split(/\r?\n\r?\n/);
buffer = frames.pop() || "";
for (const frame of frames) {
const data = frame
.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).trim())
.join("\n");
if (data && data !== "[DONE]") yield data;
}
}
}
const source = await fetch(SOURCE_URL);
const sourceType = source.headers.get("content-type") || "image/png";
const sourceFile = new File(
[await source.arrayBuffer()], "source.png", { type: sourceType });
const form = new FormData();
form.append("image", sourceFile);
form.append("prompt", "把图片整体色调改为蓝色。");
form.append("model", "gpt-image-2");
form.append("n", "1");
form.append("quality", "auto");
form.append("size", "1024x1024");
form.append("stream", "true");
form.append("response_format", "b64_json");
const response = await fetch(API_URL, {
method: "POST",
headers: { Authorization: "Bearer " + API_KEY, Accept: "text/event-stream" },
body: form,
});
if (!response.ok) throw new Error(await response.text());
for await (const data of readSse(response)) {
const event = JSON.parse(data);
if (event.type === "image_generation.partial_image") process.stdout.write(".");
const image = event.b64_json ?? event.data?.[0]?.b64_json ?? event.item?.result;
if (image) {
await writeFile("edited-image.png", Buffer.from(image, "base64"));
console.log("\n已保存:edited-image.png");
break;
}
}5. 成功标准
命令正常退出并在当前目录生成 generated-image.png 或 edited-image.png,图片能正常打开;在 /usage 能看到 gpt-image-2 的成功调用记录,且没有 404、401/403 或 429。
6. 常见问题排查
| 现象 | 检查 | 处理 |
|---|---|---|
| 404 | 文生图端点是 /v1/images/generations,图生图与编辑是 /v1/images/edits,都带 /v1。 | 修正 URL 后重试。 |
| 400 缺少参考图 | JSON 方式调用 edits 时 images[].image_url 是必填项。 | 补上 images 数组,或改用 multipart 上传图片文件。 |
| file_id 报错 | images[].file_id 与 mask.file_id 都不受支持。 | 改用 image_url 或 data URL 传图。 |
| 模型不可用 | 在 /available-channels 确认 gpt-image-2 对当前 Key 可见。 | 换用控制台中可见的图片模型。 |
| 429 | 在 /usage 检查余额、Key 限额与并发。 | 降低并发并等待限流窗口恢复。 |
| 拿不到图片 | 流式响应的最终图片可能出现在 b64_json、data[0].b64_json 或 item.result 之一。 | 按示例里三个位置都取一遍,取到即写文件并跳出循环。 |