From cf8b6c067d6ed88f83ce806892d9e1c1eca4a678 Mon Sep 17 00:00:00 2001 From: toor Date: Tue, 18 Aug 2026 23:23:46 +0800 Subject: [PATCH] Add FastAPI image generation service Provide containerized GPT and Grok image generation with environment-based configuration. --- .env.example | 3 + .gitignore | 40 ++++++++++ Dockerfile | 13 +++ SKILL.md | 115 +++++++++++++++++++++++++++ agent-image-t2i.py | 114 +++++++++++++++++++++++++++ docker-compose.yml | 12 +++ docs.md | 191 +++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 + 8 files changed, 492 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 SKILL.md create mode 100644 agent-image-t2i.py create mode 100644 docker-compose.yml create mode 100644 docs.md create mode 100644 requirements.txt diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ffb87ea --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +GROK_API_KEY=your_grok_api_key +GPT_API_KEY=your_gpt_api_key +IMAGE_API_BASE_URL=https://api.slomerex.xyz/v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05d5936 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Environment and secrets +.env +.env.* +!.env.example + +# Generated output +output/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.coverage +htmlcov/ + +# Virtual environments +.venv/ +venv/ +env/ + +# Packaging +build/ +dist/ +*.egg-info/ + +# IDE and OS +.vscode/ +.idea/ +*.iml +.DS_Store +Thumbs.db + +# Logs and local databases +*.log +*.sqlite3 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..45f7fdd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY agent-image-t2i.py . +RUN mkdir -p /app/output + +EXPOSE 8000 + +CMD ["uvicorn", "agent-image-t2i:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..52827e9 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,115 @@ +--- +name: ai-image +description: Generate images via AI text-to-image API (GPT Image / Grok). Use when the user asks to draw, paint, generate, or create an image from a text description. +--- + +# AI 绘图工具 + +通过 FastAPI 文生图服务生成图片,供 Agent 通过 HTTP/cURL 直接调用,无需自己实现模型请求逻辑。 + +服务端使用 OpenAI Python SDK 请求 GPT Image / Grok Image 上游接口,生成的图片保存在服务端 `output/` 目录,并通过 HTTP 返回访问地址。 + +- 服务端 API 不要求调用方提供上游 API Key;上游鉴权通过项目根目录 `.env` 配置 +- `.env` 不应提交到 Git,复制 `.env.example` 为 `.env` 后填写真实 Key +- 使用 Docker Compose 时会自动读取 `.env` 并注入容器 + +## 可用脚本 + +| 能力 | 服务端文件 | HTTP 端点 | 模型 | +|------|------------|------------|------| +| GPT / Grok 绘图 | `agent-image-t2i.py` | `POST /images/generations` | `gpt-image-2-1K` / `gpt-image-2-2K` / `gpt-image-2-4K` / `grok-imagine-image-lite` | +| 健康检查 | `agent-image-t2i.py` | `GET /health` | — | +| 图片访问 | `agent-image-t2i.py` | `GET /output/{filename}` | — | + +## GPT 模型说明 + +| 模型 | 输出尺寸 | 备注 | +|------|----------|------| +| `gpt-image-2-1K` | ~1254×1254(1:1)或 1536×1024(横版) | **推荐**,线路稳定 | +| `gpt-image-2-2K` | 不固定 | 线路不稳定,可能 503 | +| `gpt-image-2-4K` | 不固定 | 线路不稳定,可能 503 | + +> Grok 模型的 size 参数不可靠,API 返回尺寸不受控。 + +## 使用方式 + +先启动 FastAPI 服务: + +```bash +uvicorn agent-image-t2i:app --host 0.0.0.0 --port 8000 +``` + +默认服务地址为 `http://localhost:8000`。 + +健康检查: + +```bash +curl http://localhost:8000/health +``` + +使用 Grok 绘图: + +```bash +curl -X POST http://localhost:8000/images/generations \ + -H "Content-Type: application/json" \ + -d '{ + "prompt": "a cute cat", + "provider": "grok", + "size": "1024x1024", + "n": 1 + }' +``` + +使用 GPT 绘图: + +```bash +curl -X POST http://localhost:8000/images/generations \ + -H "Content-Type: application/json" \ + -d '{ + "prompt": "a cute cat", + "provider": "gpt", + "model": "gpt-image-2-1K", + "size": "1536x1024", + "n": 1 + }' +``` + +生成结果中的 `url` 为图片访问地址,例如 `http://localhost:8000/output/{filename}`。 + +## 调用建议 + +- **优先用 Grok**:稳定出图,适合日常使用 +- **追求画质用 GPT 1K**:请求中设置 `"provider": "gpt"` +- **GPT 2K/4K 线路可能不稳定**:不推荐作为默认模型 +- **Prompt 用英文**:效果通常比中文好 +- 使用 Docker Compose 时,项目的 `output/` 会映射到容器的 `/app/output` + +## 请求参数 + +请求体为 JSON,发送到 `POST /images/generations`: + +- `prompt`:必填,图片提示词 +- `provider`:可选,`grok` 或 `gpt`,默认 `grok` +- `model`:可选,覆盖 provider 的默认模型 +- `size`:可选,默认 `1024x1024` +- `n`:可选,生成数量,范围 `1 ~ 10`,默认 `1` + +## 输出规范 + +- 图片默认保存在服务端 `output/` 目录 +- 响应返回 `filename`、`url` 和文件大小 `bytes` +- 图片通过 `GET /output/{filename}` 访问 +- 服务启动后,响应中的相对路径 `url` 需要拼接服务地址,例如 `http://localhost:8000/output/{filename}` + +## Prompt 技巧 + +- 使用英文 prompt 效果更好 +- 描述要具体:主体 + 环境 + 光线 + 风格 + 情绪 +- 横版图用 `--size 1536x1024`,正方形用 `--size 1024x1024` +- 加上 `cinematic lighting, highly detailed, 4K` 等后缀提升质感 + +## 配置与注意事项 + +- 单次上游请求超时为 120 秒 +- 上游请求遇到网络错误或 408/429/500/502/503/504 时,最多自动重试 3 次,并使用指数退避 +- FastAPI 自动文档地址:`http://localhost:8000/docs` diff --git a/agent-image-t2i.py b/agent-image-t2i.py new file mode 100644 index 0000000..53cbce6 --- /dev/null +++ b/agent-image-t2i.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""通过 FastAPI 提供 GPT Image 和 Grok Image 文生图服务。""" + +import base64 +import os +import time +from pathlib import Path +from uuid import uuid4 + +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException +from fastapi.staticfiles import StaticFiles +from openai import APIConnectionError, APIStatusError, APITimeoutError, OpenAI +from pydantic import BaseModel, Field + +load_dotenv() + +API_BASE_URL = os.getenv("IMAGE_API_BASE_URL", "https://api.slomerex.xyz/v1") +OUTPUT_DIR = Path("output") +MAX_RETRIES = 3 +RETRYABLE_STATUS_CODES = {408, 429, 500, 502, 503, 504} +PROVIDERS = { + "grok": { + "api_key_env": "GROK_API_KEY", + "model": "grok-imagine-image-lite", + }, + "gpt": { + "api_key_env": "GPT_API_KEY", + "model": "gpt-image-2-1K", + }, +} + +app = FastAPI(title="AI Image T2I API", version="1.0.0") +OUTPUT_DIR.mkdir(parents=True, exist_ok=True) +app.mount("/output", StaticFiles(directory=OUTPUT_DIR), name="output") + + +class ImageGenerationRequest(BaseModel): + prompt: str = Field(min_length=1, description="图片提示词") + provider: str = Field(default="grok", pattern="^(grok|gpt)$", description="模型提供方") + model: str | None = Field(default=None, description="覆盖提供方默认模型") + size: str = Field(default="1024x1024", description="图片尺寸") + n: int = Field(default=1, ge=1, le=10, description="生成数量") + + +class GeneratedImage(BaseModel): + filename: str + url: str + bytes: int + + +class ImageGenerationResponse(BaseModel): + provider: str + model: str + images: list[GeneratedImage] + + +def generate_with_retry(client: OpenAI, *, model: str, prompt: str, size: str, n: int): + for attempt in range(1, MAX_RETRIES + 1): + try: + return client.images.generate(model=model, prompt=prompt, size=size, n=n) + except APIStatusError as e: + if e.status_code not in RETRYABLE_STATUS_CODES or attempt == MAX_RETRIES: + raise HTTPException(status_code=502, detail=f"上游图片服务错误: HTTP {e.status_code}") from e + time.sleep(2 ** (attempt - 1)) + except (APIConnectionError, APITimeoutError) as e: + if attempt == MAX_RETRIES: + raise HTTPException(status_code=504, detail="上游图片服务连接或请求超时") from e + time.sleep(2 ** (attempt - 1)) + + +@app.get("/health") +def health_check(): + return {"status": "ok"} + + +@app.post("/images/generations", response_model=ImageGenerationResponse) +def generate_images(request: ImageGenerationRequest): + config = PROVIDERS[request.provider] + api_key = os.getenv(config["api_key_env"]) + if not api_key: + raise HTTPException(status_code=500, detail=f"未配置环境变量 {config['api_key_env']}") + + model = request.model or config["model"] + client = OpenAI(base_url=API_BASE_URL, api_key=api_key, timeout=120.0, max_retries=0) + response = generate_with_retry( + client, + model=model, + prompt=request.prompt, + size=request.size, + n=request.n, + ) + + images = [] + for item in response.data: + if not item.b64_json: + raise HTTPException(status_code=502, detail="上游图片服务未返回 Base64 图片数据") + + try: + raw = base64.b64decode(item.b64_json) + except ValueError as e: + raise HTTPException(status_code=502, detail="上游图片数据格式无效") from e + + filename = f"{model}-{uuid4().hex}.png" + (OUTPUT_DIR / filename).write_bytes(raw) + images.append(GeneratedImage(filename=filename, url=f"/output/{filename}", bytes=len(raw))) + + return ImageGenerationResponse(provider=request.provider, model=model, images=images) + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1168e52 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + ai-image-t2i: + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + env_file: + - .env + volumes: + - ./output:/app/output + restart: unless-stopped diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..9df27ec --- /dev/null +++ b/docs.md @@ -0,0 +1,191 @@ +# 图像模型 API 文档 + +本文档整理 GPT Image 和 Grok Image 模型的 API 信息、调用示例、支持参数及速率限制。 + +## 通用信息 + +- API 基础地址:`https://api.slomerex.xyz/v1` +- 认证方式:`Authorization: Bearer ` +- Anthropic 格式的端点也接受 `x-api-key` 请求头。 +- 可在「令牌」页面生成 API Key,并按模型、分组、IP、速率等维度进行授权。 +- 示例中的 `` 请替换为令牌设置中的 API Key。 + +## 模型列表 + +| 模型 | 提供方 | 计费方式 | +|---|---|---| +| `gpt-image-2-1K` | OpenAI | 按次计费 | +| `gpt-image-2-2K` | OpenAI | 按次计费 | +| `gpt-image-2-4K` | OpenAI | 按次计费 | +| `grok-imagine-image-lite` | xAI | 按次计费 | + +--- + +## gpt-image-2-1K + +### 调用示例 + +```python +from openai import OpenAI + +client = OpenAI( + base_url="https://api.slomerex.xyz/v1", + api_key="", +) + +completion = client.chat.completions.create( + model="gpt-image-2-1K", + messages=[ + {"role": "user", "content": "Explain quantum entanglement in one paragraph."} + ], +) + +print(completion.choices[0].message.content) +``` + +### 支持的参数 + +| 参数 | 类型 | 默认值 / 范围 | 说明 | +|---|---|---|---| +| `prompt` | `string` | 必填 | 想要生成图像的文字描述 | +| `size` | `enum` | `1024x1024` | 输出图像尺寸 | +| `quality` | `enum` | `standard` | 生成质量预设 | +| `style` | `enum` | `vivid` | 画风 | +| `n` | `integer` | `1`,范围 `1 ~ 10` | 生成的图像数量 | +| `response_format` | `enum` | `url` | 图像结果的返回方式 | + +### 速率限制 + +| 分组 | RPM | TPM | RPD | +|---|---:|---:|---:| +| Gpt Image | 50 | — | 800 | + +--- + +## gpt-image-2-2K + +### 调用示例 + +```python +from openai import OpenAI + +client = OpenAI( + base_url="https://api.slomerex.xyz/v1", + api_key="", +) + +completion = client.chat.completions.create( + model="gpt-image-2-2K", + messages=[ + {"role": "user", "content": "Explain quantum entanglement in one paragraph."} + ], +) + +print(completion.choices[0].message.content) +``` + +### 支持的参数 + +| 参数 | 类型 | 默认值 / 范围 | 说明 | +|---|---|---|---| +| `prompt` | `string` | 必填 | 想要生成图像的文字描述 | +| `size` | `enum` | `1024x1024` | 输出图像尺寸 | +| `quality` | `enum` | `standard` | 生成质量预设 | +| `style` | `enum` | `vivid` | 画风 | +| `n` | `integer` | `1`,范围 `1 ~ 10` | 生成的图像数量 | +| `response_format` | `enum` | `url` | 图像结果的返回方式 | + +### 速率限制 + +| 分组 | RPM | TPM | RPD | +|---|---:|---:|---:| +| Gpt Image | 70 | — | 1.2K | + +--- + +## gpt-image-2-4K + +### 调用示例 + +```bash +curl https://api.slomerex.xyz/v1/chat/completions \ + -H "Authorization: Bearer $NEW_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "gpt-image-2-4K", + "messages": [ + { + "role": "user", + "content": "Explain quantum entanglement in one paragraph." + } + ], + "temperature": 0.7 + }' +``` + +### 支持的参数 + +| 参数 | 类型 | 默认值 / 范围 | 说明 | +|---|---|---|---| +| `prompt` | `string` | 必填 | 想要生成图像的文字描述 | +| `size` | `enum` | `1024x1024` | 输出图像尺寸 | +| `quality` | `enum` | `standard` | 生成质量预设 | +| `style` | `enum` | `vivid` | 画风 | +| `n` | `integer` | `1`,范围 `1 ~ 10` | 生成的图像数量 | +| `response_format` | `enum` | `url` | 图像结果的返回方式 | + +### 速率限制 + +| 分组 | RPM | TPM | RPD | +|---|---:|---:|---:| +| Gpt Image | 60 | — | 900 | + +--- + +## grok-imagine-image-lite + +### 调用示例 + +```python +from openai import OpenAI + +client = OpenAI( + base_url="https://api.slomerex.xyz/v1", + api_key="", +) + +completion = client.chat.completions.create( + model="grok-imagine-image-lite", + messages=[ + {"role": "user", "content": "Explain quantum entanglement in one paragraph."} + ], +) + +print(completion.choices[0].message.content) +``` + +### 支持的参数 + +| 参数 | 类型 | 默认值 / 范围 | 说明 | +|---|---|---|---| +| `prompt` | `string` | 必填 | 想要生成图像的文字描述 | +| `size` | `enum` | `1024x1024` | 输出图像尺寸 | +| `quality` | `enum` | `standard` | 生成质量预设 | +| `style` | `enum` | `vivid` | 画风 | +| `n` | `integer` | `1`,范围 `1 ~ 10` | 生成的图像数量 | +| `response_format` | `enum` | `url` | 图像结果的返回方式 | + +### 速率限制 + +| 分组 | RPM | TPM | RPD | +|---|---:|---:|---:| +| Grok | 40 | — | 700 | + +--- + +## 速率限制说明 + +- **RPM**:每分钟请求数(Requests Per Minute) +- **TPM**:每分钟 token 数(Tokens Per Minute) +- **RPD**:每日请求数(Requests Per Day) +- 限制按令牌分组生效。 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a03e0d0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi +openai +python-dotenv +uvicorn[standard]