一个服务 · 七种能力 · N 个项目
把 AI 生成能力
收成一个接口
调用方传提示词和常用参数,不传模型。模型选择、供应商回退、重试退避、 并发闸、产出校验都在服务里 —— 那些是花过钱才知道的事, 不该让每个项目重踩一遍。
01 — 快速开始
三十秒
图片默认同步等,一次请求直接拿到产物地址。
curl -X POST https://ai.nofuns.xyz/v1/text2image \ -H "Authorization: Bearer $AICAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"prompt":"一只戴宇航头盔的橘猫,扁平卡通风格","quality":"fast"}'
# 回来的东西 { "job_id": "j_70f06bacbc54", "status": "done", "artifacts": [{ "id": "a_4f6a59d9ab40", "kind": "image", "mime": "image/png", "bytes": 319426, "url": "/v1/files/a_4f6a59d9ab40" }], "usage": {"calls": 1, "provider": "artsapi", "model": "gpt-image-2-low", "seconds": 74.2} }
# clients/aicap.py 拷进项目就能用,零依赖 from aicap import AiCap ai = AiCap("https://ai.nofuns.xyz", "tok_xxx") png = ai.text2image("一只戴宇航头盔的橘猫,扁平卡通风格")[0] open("cat.png", "wb").write(png)
// clients/aicap.ts 拷进项目就能用,零依赖 import { AiCap } from "./aicap"; const ai = new AiCap("https://ai.nofuns.xyz", process.env.AICAP_TOKEN!); const [png] = await ai.text2image("一只戴宇航头盔的橘猫,扁平卡通风格");
三个参数所有能力都吃
| 参数 | 说明 |
|---|---|
| prompt 必填 | 提示词。你想怎么写就怎么写,服务不改它。 |
| quality | fast / standard(默认)/ hq —— 映射到不同的模型链 |
| model | 可选,强制指定模型。不推荐:会绕过能力过滤,见「踩过的坑」 |
GET /v1/capabilities 会告诉你每个能力有哪些模型、
哪些是真验证过的、当前并发占用、哪些端点上限还没实测。
服务说得出来的事,不该让调用方去猜。
02 — 鉴权
一个项目一个令牌
所有 /v1/* 都要 Authorization: Bearer <令牌>。
/health 不要(给监控用)。
分开发令牌不是为了好看:N 个项目共用一份供应商额度,
不按项目记账就没法管谁把钱花光了 —— 这是第一天就会遇到的问题。
每个令牌能单独吊销、单独记账(GET /v1/usage)。
# 看自己项目最近 24 小时花了多少 curl -H "Authorization: Bearer $AICAP_TOKEN" \ https://ai.nofuns.xyz/v1/usage?hours=24
返回里有一列 wasted_but_charged —— 图出了、钱收了、我们用不上
的次数。那是最该被看见的数字,藏起来的话这类浪费永远查不出来。
03 — 图片
文生图 · 图生图
| 参数 | 说明 |
|---|---|
| image 图生图必填 | 参考图,可传多张(见下面那条旁注) |
| size | 默认 1024x1024 |
| n | 要几张,1~8。每张是独立一次调用,一张不合格不会拖累其他 |
| transparent | 要真 alpha 通道。服务会替你验 |
| wait | 默认 true,同步等到出图(上限 180 秒) |
# 图生图:多传几张参考图,用 multipart curl -X POST https://ai.nofuns.xyz/v1/image2image \ -H "Authorization: Bearer $AICAP_TOKEN" \ -F "prompt=把这个角色改成绿色,其他全部保持不变" \ -F "image=@front.png" \ -F "image=@side.png" \ -F "image=@back.png" \ -F "transparent=true"
transparent 返回不透明图,而且照样收钱。
服务会开图查 alpha 通道,不合格就自动换模型重试,并把这次记成「白花钱」。
你拿到的图一定有 alpha。
04 — 视频
文生视频 · 图生视频
视频一律异步 —— 拿 job_id 去轮询。跑 2~10 分钟,
同步等必然被网关掐断,那样你连轮询都没得轮。
| 参数 | 说明 |
|---|---|
| image 图生视频必填 | 首帧图 |
| last_image | 尾帧。传和 image 同一张 = 无缝循环 |
| duration | 秒,默认 5 |
| aspect_ratio | 默认 16:9 |
| resolution | 默认 720p |
| compress | 默认 true,见下面那条旁注 |
curl -X POST https://ai.nofuns.xyz/v1/image2video \ -H "Authorization: Bearer $AICAP_TOKEN" \ -F "prompt=角色缓慢转头,眨眼,镜头锁死不动" \ -F "image=@cat.png" \ -F "last_image=@cat.png" \ -F "duration=5" # → {"job_id":"j_xxx","status":"pending"} 然后轮询 /v1/jobs/j_xxx
compress=false。
05 — 视频理解
视频生文
喂一段视频,问你想问的。默认问「详细描述这段视频」。
| 参数 | 说明 |
|---|---|
| video 必填 | mp4 / webm,最大 256 MB |
| prompt | 你想问什么。不填就是让它详细描述 |
| frames | 抽几帧,1~32,默认 8。越多越细也越贵 |
curl -X POST https://ai.nofuns.xyz/v1/video2text \ -H "Authorization: Bearer $AICAP_TOKEN" \ -F "video=@clip.mp4" \ -F "prompt=这段视频里镜头是怎么动的?主体做了什么动作?" \ -F "frames=12" # 结果在 job 的 text 字段里,同时也存成一个 .txt 产物
06 — 三维
文生 3D · 图生 3D
两家供应商:Meshy(默认)和 Tripo。异步。
| 参数 | 说明 |
|---|---|
| image 图生3D必填 | 可多张。首图必须是正面,后面依次左 / 后 / 右 |
| provider | meshy(默认)/ tripo。二选一,不跨家 |
| texture | 要不要贴图,默认 true |
| polycount | 目标面数 100~300000 |
| formats | glb(默认)/ fbx,逗号分隔 |
| quad | 四边面。必须一起要 fbx,见旁注 |
| hi_poly | 顺带要减面前的高模(300 万面) |
| parts | 分件输出。和贴图互斥 |
curl -X POST https://ai.nofuns.xyz/v1/image2model \ -H "Authorization: Bearer $AICAP_TOKEN" \ -F "image=@front.png" -F "image=@left.png" \ -F "provider=meshy" -F "quality=standard" \ -F "formats=glb,fbx" -F "quad=true" -F "hi_poly=true"
quad 却只下 GLB 等于白给 ——
实测拿到 12836 个三角面、0 个四边面。服务会在花钱之前
把这个组合拦下来并告诉你原因,而不是让你等十分钟拿到一个假的四边面模型。
07 — 机制
作业与产物
视频和 3D 一律异步,图片可以同步等。两种回来的形状是一样的。
| status | 意思 |
|---|---|
| pending | 排着,还没有 worker 认领 |
| running | 在跑。step 里是当前打给谁(如 meshy/meshy-t2)或进度 |
| done | 成了。artifacts 里是产物,usage 里是这一趟发了几次、谁出的活 |
| failed | 败了。error 里是每个候选分别为什么失败 |
error 里
每一行都是「哪个账号 / 哪个模型 / 回了什么」,例如
tripo/v3.1 HTTP 403 code 2010 余额不足。
密钥会被两层脱敏掉,不会跟着报错泄露出去。
产物默认保留 14 天,过期后取件会回 410 并说明原因 ——
不会给你一个下载到一半断掉的文件。
08 — 客户端
拷走就能用
clients/aicap.py 和 clients/aicap.ts,零依赖,
单文件。轮询、下载、错误处理都封好了。
from aicap import AiCap, AiCapError ai = AiCap("https://ai.nofuns.xyz", "tok_xxx") # 图片:一步到位 png = ai.text2image("扁平卡通的橘猫", quality="fast")[0] png2 = ai.image2image([("ref.png", png)], "改成绿色")[0] # 视频 / 3D:会一直等(几分钟),别放在请求处理里同步调 mp4 = ai.image2video(png, "缓慢转头", onProgress=print) desc = ai.video2text(mp4, "镜头怎么动的?") files = ai.image2model([("front.png", png)], provider="meshy") open("out.glb", "wb").write(files["glb.glb"]) # 要非阻塞就用 submit_* 拿 job_id,自己轮询 job = ai.submit_image2video(png, "缓慢转头") # … 过一会儿 … done = ai.wait(job)
import { AiCap } from "./aicap"; const ai = new AiCap("https://ai.nofuns.xyz", process.env.AICAP_TOKEN!); const [png] = await ai.text2image("扁平卡通的橘猫", { quality: "fast" }); const mp4 = await ai.image2video(png, "缓慢转头"); const desc = await ai.video2text(mp4, "镜头怎么动的?"); const files = await ai.image2model( [{ name: "front.png", data: png }], { provider: "meshy" });
09 — 并发
两层闸
都在 SQLite 里,跨进程共享。要么都拿到、要么都不拿。
账号层
限流是按账号算的,不是按 key —— 加 key 不加容量,加账号才加。
端点层
端点必须分开算 —— edits 6 并发就开始劣化,
而 generations 24 并发都没找到顶。
⚠ 标记的三个是猜的或照官方文档配的,我们自己没压测过。
/v1/capabilities 里它们的 measured 是 false ——
没测过的东西不写成已知。
10 — 勘误
踩过的坑
这些都写成了数据,由路由层自动过滤,不靠人记。 列在这里是让你知道服务替你挡掉了什么 —— 以及为什么别绕过它。
- 一个总是快速失败的候选,比没有候选更糟。它不但不顶用,还要吃掉一次重试和一次退避。修掉一个这样的候选之后,一趟作业从 11.0 分钟降到 3.4 分钟。
- 链必须按端点分。
gpt-image-2-low在/images/edits上 8/8 全 400(它不支持这个端点),但在/images/generations上是最快最便宜的那个。用一个端点的观测推另一个端点的结论,栽过四次。 - 能力不能靠猜。要透明底时
low5/5 返回不透明图,每次 1.7 秒就返回 —— 那速度根本不是在出图,但上游照样收钱。另一个模型更狠:直接把棋盘格画进像素里当假透明。 - 尺寸约束不能一刀切。某个模型要 ≥2048,拿 1024 发它 44 次调用 44 次 400,每张白花 7.4 秒 —— 而且当年被误判成「它不支持透明底」,一个尺寸问题被记成了能力问题。
- 「同步端点」也可能返回任务号。
gpt-image-2-low走/images/generations回的不是图,是{"id":"…","status":"pending"}。不轮询的话它每次都被判成失败、每次都掉回贵的那个 —— 配了等于没配。 - 信封不是内容。Tripo 的响应外层
status:"success"是接口调用成功,内层data.status才是任务状态。读错的话任务跑到 5% 就被当成完成,然后报「成功了但没有产物」。 - 两步的事别只做一步。Meshy 文生 3D 是
preview出几何 →refine上贴图,只发第一步交付的是个白模,而调用方要点开才会发现。 - 限流按账号算,不按 key 算。12 并发全压 1 个 key 和均摊到 3 个 key 都是 12/12、零 429。⇒ 加 key 不加容量,加账号才加。
- 并发提高反而更慢。池 8→20 的实测:11.0 分钟 → 13.1 分钟。负反馈:错误率↑ → 退避重试 → 有效吞吐↓。顶着天花板跑,抖动一下就全挂。
- 「能连上」不等于「能用」。栽过的每一次,
curl都是 200。所以冒烟检查的不是状态码,是产物本身 —— 图能不能解码、GLB 的魔数对不对、描述是不是空的。
11 — 自检
不用问人
GET /health活着没、配了几个账号、有没有 ffmpeg(不要令牌)GET /v1/capabilities每条链现在实际会打给谁、哪些验证过、并发占用GET /v1/usage?hours=24你这个项目花了多少,含白花的钱GET /v1/jobs?limit=50你这个项目最近的作业GET /docsFastAPI 自动生成的接口文档,能直接试/health
会回 degraded 并列出哪条链降到了第几位、缺谁。
降级路径本来就是设计好的,它会安静地生效 —— 不喊出来的话能安静地跑一年。