在 Claude Code 中使用 Nano Banana Pro:一个能生成和编辑图片的 skill
给 Claude Code 做一个 Nano Banana Pro skill:一段提交请求、轮询任务并保存图片的小脚本,加一个可以按名称调用的 SKILL.md。
以 Markdown 阅读Claude Code 可以通过一个 skill 用 Nano Banana Pro 生成和编辑图片:一个包含 SKILL.md 文件和一段调用 SeedRouter API 的简短脚本的文件夹。你输入 /nano-banana-pro 并描述想要的图片,Claude 写好提示词、运行脚本,脚本负责提交请求、轮询任务,并把图片保存到你的项目里。同一个文件夹在 OpenAI Codex 中也能用。SeedRouter 不提供官方的 skill 或 MCP 服务器,所以本指南给你一个可以直接照抄的。skill 的格式依据 Anthropic 和 OpenAI 的文档,核对于 2026 年 9 月 29 日。
Claude Code 怎么用 Nano Banana Pro 生成图片?
Claude Code 自己不会画图。它能运行命令,所以可以运行一个调用图像 API 的脚本。装好 skill 之后,一次请求的流程是这样的:
- 你调用 skill 并描述图片,例如
/nano-banana-pro a hero image for the pricing page。 - Claude 把你的描述改写成 Nano Banana Pro 提示词,并选好尺寸和宽高比。
- 它运行脚本。脚本把请求发送到
POST https://api.seedrouter.ai/v1/images/generations,拿回一个任务 ID。 - 脚本每隔几秒轮询一次
GET /v1/tasks/{id},直到图片生成好,然后把它下载到images/文件夹。 - Claude 告诉你文件路径,之后还可以直接在你的代码里使用这张图。
用脚本而不是只靠指令,能让 API 调用保持精确:无论 Claude 写出什么样的提示词,请求体、轮询循环和保存文件的方式每次都一样。
开始之前需要准备什么?
- 一个 SeedRouter API Key,在你启动 Claude Code 的终端里导出:
export SEEDROUTER_API_KEY="your-key"- 余额。 每运行一次就是一次付费请求。新账户自带少量免费余额,失败的请求不收费。
- Python 3。 脚本只用标准库,不需要安装任何东西。
脚本:提交、轮询和保存
把下面的代码保存为 skill 文件夹里的 scripts/nano_banana_pro.py:
#!/usr/bin/env python3
import argparse
import json
import os
import pathlib
import sys
import time
import urllib.error
import urllib.request
API = "https://api.seedrouter.ai/v1"
AGENT = "nano-banana-pro-skill/1.0"
MIME = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".webp": "image/webp",
".heic": "image/heic",
".heif": "image/heif",
}
RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]
def call(method, path, body=None):
request = urllib.request.Request(
API + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={
"Authorization": "Bearer " + os.environ["SEEDROUTER_API_KEY"],
"Content-Type": "application/json",
"User-Agent": AGENT,
},
)
try:
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
except urllib.error.HTTPError as error:
sys.exit(f"HTTP {error.code}: {error.read().decode()}")
def submit(args):
parts = [{"text": args.prompt}]
for url in args.ref:
suffix = pathlib.PurePosixPath(url.split("?")[0]).suffix.lower()
if suffix not in MIME:
sys.exit(f"Cannot tell the image type of {url}; use a .jpg, .png, .webp, .heic or .heif URL.")
parts.append({"fileData": {"mimeType": MIME[suffix], "fileUri": url}})
image_config = {}
if args.ratio:
image_config["aspectRatio"] = args.ratio
if args.size:
image_config["imageSize"] = args.size
generation_config = {"responseModalities": ["IMAGE"]}
if image_config:
generation_config["imageConfig"] = image_config
body = {
"model": args.model,
"contents": [{"role": "user", "parts": parts}],
"generationConfig": generation_config,
}
return call("POST", "/images/generations", body)["id"]
def wait(task_id, out_dir, timeout):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
task = call("GET", "/tasks/" + task_id)
if task["status"] == "completed":
out_dir.mkdir(parents=True, exist_ok=True)
extension = "jpg" if task["output"]["output_format"] == "jpeg" else task["output"]["output_format"]
for index, image in enumerate(task["output"]["data"]):
path = out_dir / f"{task_id}-{index}.{extension}"
download = urllib.request.Request(image["url"], headers={"User-Agent": AGENT})
with urllib.request.urlopen(download, timeout=120) as response:
path.write_bytes(response.read())
print(path)
return
if task["status"] == "failed":
sys.exit(f"Task {task_id} failed: {task['error']['message']}")
time.sleep(3)
sys.exit(f"Task {task_id} is still processing. Resume with --resume {task_id}")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--prompt")
parser.add_argument("--ref", action="append", default=[])
parser.add_argument("--ratio", choices=RATIOS)
parser.add_argument("--size", choices=["1K", "2K", "4K"])
parser.add_argument("--model", default="gemini-3-pro-image",
choices=["gemini-3-pro-image", "gemini-3-pro-image-official"])
parser.add_argument("--out", default="images")
parser.add_argument("--timeout", type=int, default=600)
parser.add_argument("--resume")
args = parser.parse_args()
if args.resume:
task_id = args.resume
elif args.prompt:
task_id = submit(args)
print(f"task {task_id}", file=sys.stderr)
else:
parser.error("--prompt or --resume is required")
wait(task_id, pathlib.Path(args.out), args.timeout)
if __name__ == "__main__":
main()它做了这些事:
- 只提交一次。
--prompt是提示词文本,--ref添加一张参考图 URL(要多张就重复使用),--ratio和--size设置imageConfig。请求体是 Google 的generateContent格式加上一个model字段,与 Nano Banana Pro API 参考的定义一致。 - 任务一创建就打印任务 ID,然后才开始等待。
- 先轮询,再保存。 任务完成后,它把每张图片下载到
images/并打印路径;任务失败时,它会打印失败原因。 - 续查而不是重新提交。 如果等待超时,用
--resume <task id>再次查询同一个任务。再提交一次就是第二次付费请求。 - 发送
User-Agent请求头。 请保留这一行:有些服务器会拒绝 Python 默认的 User-Agent,没有它下载可能会以 403 失败。
在交给 Claude 使用之前,可以先单独测试一下:
python3 scripts/nano_banana_pro.py --prompt "A ceramic teapot on a linen tablecloth, soft window light" --ratio 16:9 --size 2Kskill 本身:SKILL.md 里写什么?
Claude Code 的文档把 skill 描述为一个包含 SKILL.md 文件的目录:“--- 标记之间的 YAML frontmatter 告诉 Claude 什么时候使用这个 skill,markdown 内容则是 skill 运行时 Claude 要遵循的指令。”scripts/ 这样的辅助文件夹就放在它旁边。
把下面的内容保存为同一文件夹里的 SKILL.md:
---
name: nano-banana-pro
description: Generate or edit an image with Nano Banana Pro (gemini-3-pro-image) through the SeedRouter API and save it into the project. Use when the user asks for an image, illustration, icon, poster or edit of an existing image.
argument-hint: "[what the image should show]"
disable-model-invocation: true
---
Create an image for this request: $ARGUMENTS
1. Write one prompt in full sentences: subject, framing, action, setting and style. Put any text that must appear in the image in double quotes and say where it goes. For an edit, say what changes and what must stay the same.
2. Choose --ratio from 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 and --size from 1K, 2K, 4K (4K only for print).
3. Reference images must be public HTTPS URLs ending in .jpg, .png, .webp, .heic or .heif, passed with --ref. If the user points to a local file, ask for a public URL instead. Never send base64.
4. Show the user the prompt and options, and wait for approval. Each run is a paid request.
5. Run: python3 ${CLAUDE_SKILL_DIR}/scripts/nano_banana_pro.py --prompt "..." [--ratio R] [--size S] [--ref URL]
6. If it stops with "still processing", run it again with --resume and the same task ID. Never submit the same request twice.
7. Report the saved file paths. If the task failed, explain the reason and suggest a change to the prompt.这里有两个关键字段:
disable-model-invocation: true表示只有你能启动这个 skill。Claude Code 的文档建议把它用于“有副作用、或你想自己控制时机的工作流”。每次运行都要花钱,所以不应该让 Claude 自己决定去生成图片。${CLAUDE_SKILL_DIR}会被替换成 skill 自己所在的文件夹,所以无论你把 skill 装在哪里,脚本都能正常运行。
skill 放在哪里,怎么运行?
Claude Code 主要从两个地方加载 skill:
| 位置 | 路径 | 可用范围 |
|---|---|---|
| 个人 | ~/.claude/skills/nano-banana-pro/ | 这台机器上你的所有项目 |
| 项目 | .claude/skills/nano-banana-pro/ | 当前仓库;提交到仓库即可与团队共享 |
完成后的文件夹结构如下:
nano-banana-pro/
├── SKILL.md
└── scripts/
└── nano_banana_pro.py在已设置 SEEDROUTER_API_KEY 的终端里启动 Claude Code,然后输入 skill 名称,后面跟上你想要的内容:
/nano-banana-pro a 16:9 hero image for the pricing page: a calm desk with a laptop, a notebook and a cup of tea, soft morning light, muted green palette, no textClaude 展示提示词和参数,你确认后,图片就会出现在 images/ 里。除非你已经预先批准了这条命令,Claude Code 在运行脚本前还会请求权限,这是在产生费用之前的第二道确认。
关于提示词本身,Nano Banana Pro 提示词指南更深入地介绍了结构、图中文字和参考图。
Codex 能用同一个 Nano Banana Pro skill 吗?
能。两个工具都遵循开放的 Agent Skills 标准。OpenAI 的 Codex 文档说,skill “是一个包含 SKILL.md 文件以及可选脚本和参考资料的目录”,并且该文件“必须包含 name 和 description”,上面的 skill 两者都有。
Codex 在另外的文件夹里查找 skill:
| 位置 | 路径 |
|---|---|
| 个人 | ~/.agents/skills/nano-banana-pro/ |
| 仓库 | .agents/skills/nano-banana-pro/ |
把文件夹复制过去,或者建一个指向它的符号链接;两个工具都支持符号链接形式的 skill 文件夹。在 Codex 中,运行 /skills 或输入 $nano-banana-pro 来调用它。
有两点不同需要注意:
- 脚本路径。
${CLAUDE_SKILL_DIR}是 Claude Code 的替换变量。Codex 会把每个 skill 的文件路径列给模型,所以它能找到SKILL.md旁边的scripts/nano_banana_pro.py;如果它运行的路径不对,告诉它脚本在哪里即可。 - 自动调用。
disable-model-invocation是 Claude Code 的字段。Codex 中对应的做法是在 skill 文件夹里放一个agents/openai.yaml文件,在policy下设置allow_implicit_invocation: false,这样 Codex 只会在你调用时运行这个 skill。
脚本需要网络访问才能连到 api.seedrouter.ai。如果你的 Agent 运行在不能联网的沙箱里,请为这次会话开启网络访问。
怎么编辑项目里已有的图片?
Nano Banana Pro 通过参考图进行编辑,而 API 只接受公开 URL 形式的参考图。你硬盘上的文件需要先托管到某个地方,例如你自己的存储桶,然后再用 --ref 传入。
生成出来的图片本身已经是托管好的:每个完成的任务都会以 URL 的形式返回图片。要在某个结果上继续修改,就把它的 URL 作为参考图传回去,再写上你想要的改动:
/nano-banana-pro edit the last image: keep the desk and the laptop, change the tea cup to a glass of water, and make the light warmerClaude 会用 --ref 复用上一张图片的 URL,并写一个说明哪些部分保持不变的编辑提示词。如果要进行在轮次之间保留模型自身上下文的多轮编辑,API 参考说明了如何把之前的轮次发回去;上面的脚本一次只发送一轮。
如果你已经在编程 Agent 里使用 GPT Image 2,在 Codex 和 Claude Code 中使用 GPT Image 2 介绍了那套配置,它的核心是一段粘贴给 Agent 的提示词。
常见问题
SeedRouter 有官方的 Nano Banana Pro skill 或 MCP 服务器吗?
没有,SeedRouter 不提供。本指南中的 skill 就是一个普通文件夹,归你所有、可以随意修改,它用你的 Key 调用公开 API。
为什么脚本要轮询,而不是一直等着图片返回?
在 SeedRouter 上,Nano Banana Pro 请求是任务:提交后返回一个任务 ID,任务完成时图片才会出来。轮询让每次连接都很短,即使等待被中断,任务 ID 依然有效。
同一个 skill 能用于 Nano Banana 2 吗?
能,改几处就行。把脚本里 --model 的默认值和可选值改成 gemini-3.1-flash-image,再在 --size 的可选值里加上 512,这个尺寸 Nano Banana 2 支持而 Pro 不支持。Nano Banana 2 vs Pro vs 2 Lite 对比了这几个模型。
加载 skill 会产生费用吗?
不会。Claude 读取 SKILL.md 在 SeedRouter 上不产生任何费用。脚本每运行一次就是一次请求,按模型页面上的费率计费,失败的任务不收费。
从 Claude Code 开始生成图片
建好文件夹,导出你的 Key,先用 /nano-banana-pro 试一张小图。当前价格见 Nano Banana Pro 页面,所有请求字段见 API 参考。



