Janvia

接口

OpenAI 兼容接口

用 OpenAI 的 SDK 发图片、语音请求;每个请求创建一个任务,并立即返回。

按 OpenAI 图片、语音接口写的请求可以直接发到这里:把接口地址换成 https://janvia.io/v1,密钥换成这里的 API 密钥。model 要填这里的模型 ID,见 模型。

和 OpenAI 不同,这些接口不会一直等到结果出来。每个请求都会创建一个普通的 任务,并立即返回任务对象。之后用 GET /v1/tasks/TASK_ID 查询结果;也可以传 callback_url,任务结束时我们把任务 POST 给你(见 回调通知)。路由、计费、失败退款都和任务接口一样。

支持三个接口:

  • POST /v1/images/generations:文生图,JSON。
  • POST /v1/images/edits:改图、参考图生图,multipart,图片放在 image 或 image[]。
  • POST /v1/audio/speech:语音合成,JSON。

视频请用 任务接口。OpenAI 自己的视频接口已经停用,这里不提供。

提交,再查结果

curl https://janvia.io/v1/images/generations \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "a red fox in the snow",
    "size": "1536x1024",
    "callback_url": "https://example.com/hooks/relay"
  }'

返回 HTTP 200 和任务对象,状态一般是 queued。X-Relay-Task-Id 和 Location 两个 Header 也指向这个任务:

{
  "id": "task_9f2c...",
  "object": "task",
  "model": "gpt-image-2",
  "status": "queued",
  "output": null,
  "cost_usd": 0.06,
  ...
}

反复查询,直到 status 变成 completed 或 failed,生成的文件在 output.files[].url:

curl https://janvia.io/v1/tasks/task_9f2c... \
  -H "Authorization: Bearer $API_KEY"

Python

SDK 调用返回的是任务,不是 OpenAI 的 data 列表。这里自己的字段(如 callback_url、route)放在 extra_body 里。

import time

import httpx
from openai import OpenAI

client = OpenAI(base_url="https://janvia.io/v1", api_key="sk_xxx")

task = client.images.generate(
    model="gpt-image-2",
    prompt="a red fox in the snow",
    size="1536x1024",
    # extra_body={"callback_url": "https://example.com/hooks/relay"},
).to_dict()

while task["status"] not in ("completed", "failed"):
    time.sleep(3)
    task = client.get(f"/tasks/{task['id']}", cast_to=httpx.Response).json()

if task["status"] == "completed":
    print(task["output"]["files"][0]["url"])
else:
    print(task["error"])

改图也一样:client.images.edit(model=..., image=[open("product.png", "rb")], prompt=...) 返回任务。client.audio.speech.create(...) 也返回任务,用 .json() 读取;任务完成后,音频在 output.files[0].url。

Node.js

import OpenAI from 'openai';

const client = new OpenAI({ baseURL: 'https://janvia.io/v1', apiKey: 'sk_xxx' });

let task = await client.images.generate({
  model: 'gpt-image-2',
  prompt: 'a red fox in the snow',
  size: '1536x1024',
  // callback_url: 'https://example.com/hooks/relay',
});

while (!['completed', 'failed'].includes(task.status)) {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  task = await client.get(`/tasks/${task.id}`);
}

console.log(task.output?.files[0].url ?? task.error);

在 TypeScript 里,SDK 会把返回值标成 OpenAI 的响应类型,请转成你自己的任务类型。client.audio.speech.create(...) 返回 Response,用 await response.json() 读取任务。

参数对应

图片:

  • size:1024x1536 这类宽高,换算成最接近的 aspect_ratio,分辨率按长边算:1536 以内为 1K,2560 以内为 2K,更大为 4K。auto 或不传,用模型默认值。
  • n:生成几张,1–4。
  • quality:low、medium、high、auto 原样使用;standard 当作 medium;hd、xhigh、max 当作 high。
  • output_format:png、jpeg 或 webp。
  • response_format:只支持 url。结果是任务上的文件,传 b64_json 会返回 400。
  • 不支持、会返回 400:background: "transparent"、mask、stream。
  • 其他 OpenAI 参数会被忽略。

也可以直接带上这里自己的字段,它们优先于换算结果:resolution、aspect_ratio、negative_prompt、seed、image_urls、route(见 线路与路由)、callback_url。

语音:

  • input 是要朗读的文本,voice 是音色 ID(字符串,或 { "id": ... })。
  • response_format:音频文件的格式,mp3(默认)或 wav。
  • speed:0.5–2。
  • 不支持 stream_format: "sse";instructions 会被忽略。

报错和重试

提交时出错(参数不对、余额不足等)返回 4xx,不创建任务,也不扣费。任务之后失败的,费用全额退回,任务上写明错误码。

SDK 遇到超时、网络错误和 5xx 会自动重试。带上 Idempotency-Key,重试就会拿回同一个任务,不会再付一次钱:

client.images.generate(
    model="gpt-image-2",
    prompt="a red fox in the snow",
    extra_headers={"Idempotency-Key": "order-123"},
)
await client.images.generate(
  { model: 'gpt-image-2', prompt: 'a red fox in the snow' },
  { headers: { 'Idempotency-Key': 'order-123' } }
);