Jack 53b2123ae9 chore: move generated images and database outside the git worktree
The output/ and data/ directories lived inside this git repo. They are
gitignored, so any rebuild of the directory (git clone into a recreated
path, rm + clone) silently took the generated images and the SQLite
database with it. On 2026-11-04 that cost a full round of regenerated
images.

The real data now lives in /root/apps/ai-image-data/:
  output/  generated images, one subdirectory per profile_name
  data/    ai_image.db + secret.key

Bind mounts use absolute paths so the container never depends on symlink
resolution. ./output and ./data remain as symlinks for backwards
compatibility with existing scripts and skills.

Also ignore the symlinks themselves: the directory-form rules do not
match them, so git would have picked them up.
2026-11-04 23:48:01 +08:00

ai-image

验收记录与当前限制见 docs/acceptance.md。

AI 绘图聚合服务。对外提供统一的多供应商生图能力(文生图 / 图生图),内置 Web 控制台,Agent 可通过 REST API 或 MCP 调用。


一、快速开始

pip install -r requirements.txt

# 启动(Web + REST + MCP 同端口)
python main.py

# 仅 MCP stdio(给 MCP 客户端当子进程用)
python main.py --stdio

打开 http://127.0.0.1:8765/ 即可进入控制台。

首次使用:进入「供应商」页填写上游的 Base URL 与 API Key → 「拉取模型」→ 勾选含 image 的模型保存 → 到「出图」页输入提示词出图。


二、当前状态(v2 · 已落地)

技术栈

层 选型
Web 框架 FastAPI
前端 原生 HTML/CSS/JS 静态页(web/),与后端同端口同源
持久化 SQLite(标准库 sqlite3)
HTTP 客户端 httpx
密钥加密 cryptography / Fernet
MCP fastmcp(stdio + streamable HTTP)
部署 Docker / docker-compose

目录结构

main.py                     入口:REST + Web + MCP
app/
  config.py                 路径与运行配置
  db.py                     SQLite 连接与建表
  security.py               密钥加解密与掩码
  schemas.py                Pydantic 请求模型
  providers/
    base.py                 适配器基类与能力声明
    openai_compat.py        OpenAI 兼容网关
    agnes.py                Agnes 原生协议
    __init__.py             适配器注册表
  services/
    providers.py            供应商 / 模型 CRUD 与模型发现
    generation.py           出图、落盘、记录
    gallery.py              output/ 扫描
    skill_doc.py            SKILL.md 读取
  routers/
    api_providers.py        供应商与模型 API
    api_models.py
    api_generate.py
    api_gallery.py
    api_skills.py
  mcp_server.py             MCP 工具
web/                        前端静态页
data/                       SQLite + 密钥文件(不入库)
output/                     图片输出(画廊数据源)
SKILL.md                    Agent 使用说明

Web 控制台

页面 文件 功能
出图 web/create.html 输入提示词、选供应商与模型、选尺寸与数量,直接出图
画廊 web/index.html 按画廊(profile)浏览、搜索、进入查看、删除
生成记录 web/records.html 查看历史生成任务与失败原因
供应商 web/providers.html 增删改供应商、拉取上游模型、勾选启用
模型 web/models.html 跨供应商查看模型、启用 / 停用
SKILL.md web/skills.html 在线查看 Agent 使用说明

前端只有一层数据出入口 web/api.js,全部走真实 REST;无 mock 数据。


三、核心机制

统一参数体系

所有供应商收敛到同一套入参,内部再翻译成各上游格式:

入参 类型 说明
provider_id str 供应商 ID,界面创建时自定义
model str 模型 ID,来自上游模型列表
prompt str 提示词
size str 统一 WxH 像素,如 1024x1024;也接受 1K/2K/3K/4K
ratio str 仅 Agnes 支持,如 16:9;省略时从 size 推导
n int 生成数量(1~4)
images list[str] 图生图参考图(URL 或 Data URI),仅部分供应商支持
profile_name str 调用方标识,用于输出目录隔离

适配器声明自身能力(supports_image_input、size_hint、kind),服务端据此校验,避免把不支持的参数透传给上游。

数据模型(SQLite)

providers

字段 类型 说明
id TEXT PK 用户自定义唯一标识
name TEXT 显示名
adapter TEXT openai_compat / agnes
base_url TEXT 上游地址
api_key TEXT 上游密钥(Fernet 加密存储,接口返回掩码)
default_model TEXT 默认模型
enabled INTEGER 是否启用
note TEXT 备注
created_at / updated_at TEXT 时间戳

models

字段 类型 说明
id TEXT PK provider_id:model_id
provider_id TEXT 外键(ON DELETE CASCADE)
model_id TEXT 上游模型标识
display_name TEXT 显示名
enabled INTEGER 是否对外暴露
kind TEXT 类型(image)
size_hint TEXT 尺寸提示
supports_image_input INTEGER 是否支持参考图
note TEXT 备注
discovered_at TEXT 最近一次发现时间

generations

字段 类型 说明
id TEXT PK 任务 ID
provider_id / provider_name TEXT 实际使用的供应商
model TEXT 模型
profile_name TEXT 调用方
prompt TEXT 提示词
size / ratio / n — 入参快照
status TEXT success / failed
error TEXT 失败原因
files TEXT(JSON) 落盘文件名列表
duration_ms INTEGER 耗时
created_at TEXT 时间戳

模型发现

用户在界面填完 API Key 与 Base URL 后点击「拉取模型」,服务端请求上游 GET {base_url}/models,筛选规则:模型 ID 转小写后包含 image。命中的模型写入 models 表并标记 discovered_at,用户可勾选哪些对外启用(未勾选的自动停用)。拉取失败不阻塞保存,仅提示错误。

两种适配器的发现与出图路径:

适配器 模型发现 出图
openai_compat GET /models POST /images/generations(data[0].b64_json 或 data[0].url)
agnes GET /models POST /images/generations(size 档位 + ratio,response_format 放在 extra_body 内)

画廊

output/ 目录即画廊数据源:

output/
├── slime-fellowship/   ← 一个画廊(profile_name)
│   ├── gpt-image-2-1K-0014ef2b.png
│   └── ...
├── smoke-gpt-1/
└── user/               ← 网页端手动出图(固定 profile_name = user)

目录名语义 = 谁生成的图片:Agent 调用时由调用方指定 profile_name(默认 agent),网页端手动出图固定 user;目录不存在时后端自动创建。

接口 说明
GET /api/galleries 画廊列表:名称、图片数、占用空间、更新时间、封面、涉及的模型
GET /api/galleries/{profile} 某个画廊内的图片(含模型、供应商、时间)
DELETE /api/galleries/{profile}/{filename} 删除单张(物理删除 + 同步 generations 记录)

MCP 对接

Agent 侧可用工具:

工具 说明 顺序
list_providers 列出可用供应商及其启用模型数 1
discover_models 拉取指定供应商的上游模型,返回 { provider_id, total, matched } 2(必做)
generate_image 出图(文生图 / 图生图) 3
list_generations 查询生成记录 可选
get_skill_doc 获取最新版 SKILL.md 内容 任意时刻

MCP 只暴露用户在界面中启用的供应商,不泄露未启用配置与密钥。

Agent 强制流程:list_providers() → discover_models(provider_id) → 用返回的 matched 出图。

matched 为空时,Agent 必须直接结束并回复「没找到有效的模型」,不得猜测模型名、不得调用出图接口。

SKILL.md

GET /api/skills 返回 SKILL.md 内容(JSON)或原文(/api/skills/raw,text/markdown)。页面提供在线查看,Agent 通过 MCP 工具 get_skill_doc 拉取。

SKILL.md 中不写任何固定模型名称:只描述「先拉模型、再出图」的流程与参数说明,模型清单一律来自 discover_models 的实时返回。


四、接口清单

方法 路径 说明
GET /health 健康检查
GET /api/providers 供应商列表(密钥掩码)
POST /api/providers 新增 / 更新供应商(api_key 留空表示不修改)
DELETE /api/providers/{id} 删除供应商及其模型
POST /api/providers/discover 手填 Base URL + Key 临时拉模型
POST /api/providers/{id}/discover 按已保存供应商拉模型并写库
POST /api/providers/{id}/models 保存勾选的模型,未勾选的自动停用
GET /api/models 模型列表(可 ?provider_id=)
PATCH /api/models/{id} 启用 / 停用单个模型
POST /api/models/bulk 批量启用 / 停用
POST /api/generate 出图
GET /api/generations 生成记录
GET /api/galleries 画廊列表
GET /api/galleries/{profile} 画廊内图片
DELETE /api/galleries/{profile}/{filename} 删除图片
GET /api/skills SKILL.md(JSON)
GET /api/skills/raw SKILL.md 原文(markdown)
GET /output/{profile}/{filename} 图片访问
POST /mcp MCP streamable HTTP

五、配置

.env(参考 .env.example):

变量 默认值 说明
AI_IMAGE_HOST 127.0.0.1 监听地址
AI_IMAGE_PORT 8765 监听端口
AI_IMAGE_DATA_DIR data SQLite 与密钥文件目录
AI_IMAGE_OUTPUT_DIR output 图片输出根目录
AI_IMAGE_SECRET_KEY 自动生成 密钥加密主密钥,不填则落 data/secret.key

供应商的 API Key 与 Base URL 不再走环境变量,在控制台填写,服务端加密后存 SQLite。


六、部署

docker compose up -d

宿主机 output/ 与 data/ 挂载进容器,端口映射 8765:8000。


七、MCP 客户端接入

config.json 示例:

{
  "mcpServers": {
    "ai-image": {
      "command": "python",
      "args": ["d:\\source\\MyProject\\ai-image\\main.py", "--stdio"],
      "cwd": "d:\\source\\MyProject\\ai-image",
      "env": {}
    }
  }
}

也可用 streamable HTTP 直连:http://127.0.0.1:8765/mcp。

八、测试

# 后端回归:标准库 unittest,无需额外测试框架
python -m unittest discover -s tests -p test_app.py -v

# 完整验收:包含 Chromium 浏览器交互、桌面/移动布局、MCP HTTP/stdio
pip install -r requirements-dev.txt
python -m playwright install chromium
python -m unittest discover -s tests -v

测试使用临时目录、独立 SQLite、虚拟密钥和本地模拟上游,不读取现有数据库或调用付费生图服务。 浏览器测试会启动临时本地服务,结束后关闭服务并清除临时数据。

S
Description
No description provided
Readme
269 KiB
Languages
HTML 42.9%
Python 36.9%
CSS 12.6%
JavaScript 7.4%
Dockerfile 0.2%