使用 Python 生成圖片
使用 Python 標準函式庫提交一個 MaxAPI 圖片任務、輪詢狀態,避免盲目重送造成重複生成。
技術核對日期:
1. 準備伺服器端金鑰
使用 Python 3.10 以上版本。下載檔案只使用 json、time 與 urllib,不需要安裝 pip 套件。在 Console 建立金鑰,確認模型、路由權限及可用餘額。將 MAXAPI_KEY 放在伺服器環境或密鑰管理服務,不要寫入公開筆記本、瀏覽器程式或原始碼倉庫。
2. 從小規模請求開始
下載下方 maxapi_image.py,安全設定 MAXAPI_KEY 後執行。程式只提交一次 POST /v1/images/generations,使用 google/gemini-3.1-flash-lite-image、resolution 1K、aspect_ratio 1:1、n 1 與 async true。這個 Lite 圖片模型只支援 1K,不要直接改成 4K;需要先換成支援該解析度的模型。
python3 maxapi_image.py3. 區分受理與完成
程式不會自動保存圖片。它將任務 ID 與輪詢網址輸出到 stderr,請存入自己的工作紀錄,成功後才輸出結果網址。HTTP 202 代表受理,不是完成;之後的 HTTP 200 仍可能是執行中或失敗,必須檢查 status。私人圖片網址不要公開,也不要把 MaxAPI 金鑰附在其他主機的圖片下載請求中。
4. 恢復查詢,而非重新提交
每次網路操作有 30 秒 socket 逾時,預設每隔五秒查詢,最多 120 次 GET。由於請求本身也耗時,這不是嚴格的十分鐘期限。輪詢到達上限或按 Ctrl+C 並不會取消任務。將已保存的網址設為 MAXAPI_POLL_URL,用同帳號金鑰恢復查詢,此模式不會再次 POST。若初次 POST 在取得 ID 前逾時,先查使用紀錄,再決定是否重新生成。
MAXAPI_POLL_URL="/v1/images/generations/YOUR_TASK_ID" python3 maxapi_image.py下載完整範例
maxapi_image.py
"""Python 3.10+. Standard library only. Server-side; local-fixture tested, not a live model test."""
import json
import os
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
EXAMPLE_PAYLOAD = {
"model": "google/gemini-3.1-flash-lite-image",
"prompt": "A single blue ceramic cup on a plain cream background, soft studio light.",
"resolution": "1K", "aspect_ratio": "1:1", "n": 1, "async": True,
}
ACTIVE = {"submitted", "queued", "pending", "running", "processing"}
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
def safe_url(base, path):
import re
origin = urllib.parse.urlsplit(base)
url = urllib.parse.urlsplit(urllib.parse.urljoin(base + "/", path))
valid_scheme = origin.scheme == "https" or (origin.scheme == "http" and origin.hostname in {"127.0.0.1", "::1"})
def address(value):
return (value.scheme, value.hostname, value.port or (443 if value.scheme == "https" else 80))
if (not valid_scheme or origin.path not in {"", "/"} or origin.query or origin.fragment
or origin.username or origin.password or url.username or url.password or url.query or url.fragment
or address(url) != address(origin) or not re.fullmatch(r"/v1/images/(generations|edits)(/[^/?]+)?", url.path)):
raise RuntimeError("Unsafe API or polling URL; no credentials sent.")
return urllib.parse.urlunsplit(url)
def run_image(key, payload=None, base="https://api.maxapi.dev", poll_url=None,
interval=5, max_polls=120, timeout=30, on_task=lambda task: None):
if not key:
raise RuntimeError("Set MAXAPI_KEY in your server environment.")
if not isinstance(max_polls, int) or not 1 <= max_polls <= 720 or interval < 0 or timeout <= 0:
raise RuntimeError("Invalid polling limits.")
opener = urllib.request.build_opener(NoRedirect())
def request(path, body=None):
import re
headers = {"Authorization": "Bearer " + key}
if body is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(safe_url(base, path), headers=headers,
data=json.dumps(body).encode() if body is not None else None,
method="POST" if body is not None else "GET")
try:
response = opener.open(req, timeout=timeout)
except urllib.error.HTTPError as error:
response = error
except (urllib.error.URLError, TimeoutError, OSError) as error:
raise RuntimeError("Network or timeout error; outcome may be unknown. No automatic resubmission.") from error
with response:
status = response.code
try:
result = json.load(response)
except (ValueError, UnicodeError) as error:
raise RuntimeError(f"HTTP {status}: non-JSON response; inspect history before resubmitting.") from error
if not isinstance(result, dict):
raise RuntimeError("Invalid JSON response object.")
if not 200 <= status < 300 or result.get("error"):
detail = result.get("error")
code = detail.get("code", "api_error") if isinstance(detail, dict) else "api_error"
code = code if isinstance(code, str) and re.fullmatch(r"[a-zA-Z0-9_-]{1,80}", code) else "api_error"
raise RuntimeError(f"HTTP {status}: {code}; no automatic retry.")
return result
current = request(poll_url) if poll_url else request("/v1/images/generations", EXAMPLE_PAYLOAD if payload is None else payload)
path = poll_url or current.get("poll_url")
if path:
path = safe_url(base, path)
on_task({"id": current.get("id"), "poll_url": path})
count = 0
while True:
if current.get("status") == "succeeded" or (not current.get("status") and isinstance(current.get("data"), list) and current["data"]):
return current
if current.get("status") not in ACTIVE:
raise RuntimeError("Task failed, cancelled or returned an unknown state. Check history; no resubmission.")
if not path:
raise RuntimeError("Pending task has no polling URL. Check history; do not resubmit automatically.")
if count >= max_polls:
raise RuntimeError("Polling limit reached; task was NOT cancelled. Resume using the saved polling URL.")
time.sleep(interval)
current = request(path)
count += 1
if __name__ == "__main__":
try:
result = run_image(os.environ.get("MAXAPI_KEY"), poll_url=os.environ.get("MAXAPI_POLL_URL"),
on_task=lambda task: print("Save to resume:", json.dumps(task), file=sys.stderr))
print(json.dumps({"id": result.get("id"), "status": result.get("status", "succeeded"),
"urls": [item["url"] for item in result.get("data", []) if item.get("url")]}, indent=2))
except (RuntimeError, ValueError) as error:
print(str(error), file=sys.stderr)
sys.exit(1)
各模型參數
這是公開設定快照,不代表即時可用性。參數、支援路由與參考圖限制請查看各模型頁面。
- Gemini 3.1 Flash Lite Image
google/gemini-3.1-flash-lite-image - Gemini 3.1 Flash Image Preview
google/gemini-3.1-flash-image-preview