297 lines
11 KiB
Markdown
297 lines
11 KiB
Markdown
# ai-image
|
||
|
||
验收记录与当前限制见 [docs/acceptance.md](docs/acceptance.md)。
|
||
|
||
AI 绘图聚合服务。对外提供统一的多供应商生图能力(文生图 / 图生图),内置 Web 控制台,Agent 可通过 REST API 或 MCP 调用。
|
||
|
||
---
|
||
|
||
## 一、快速开始
|
||
|
||
```bash
|
||
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。
|
||
|
||
---
|
||
|
||
## 六、部署
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
宿主机 `output/` 与 `data/` 挂载进容器,端口映射 `8765:8000`。
|
||
|
||
---
|
||
|
||
## 七、MCP 客户端接入
|
||
|
||
`config.json` 示例:
|
||
|
||
```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`。
|
||
|
||
## 八、测试
|
||
|
||
```bash
|
||
# 后端回归:标准库 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、虚拟密钥和本地模拟上游,不读取现有数据库或调用付费生图服务。
|
||
浏览器测试会启动临时本地服务,结束后关闭服务并清除临时数据。
|