GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-05-02-image-generation-tools-design.md
2026-05-20 21:39:12 +08:00

483 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 文生图与图生图工具设计
> 日期2026-05-02
> 状态:待实施
> 范围:后端工具层、图片 Provider 层、前端工具渲染、Prompt Builder
## 1. 背景与目标
系统已在模型渠道中配置了火山引擎和 MiniMax 的图片生成模型。需要在 Agent 对话中提供文生图和图生图能力,主要面向电商场景(产品主图、详情图、文章配图),并支持配置专用电商套图 Agent 一次性生成成套图片。
### 目标
- 新增 `text_to_image``image_to_image` 两个独立工具
- 建立可扩展的图片生成 Provider 层,当前支持火山引擎和 MiniMax未来可扩展其他渠道
- 扩展 `ToolResultContent` 支持多图返回
- 前端 tool-card 支持单图/多图渲染
- 生成图片转存本地,解决 Provider 临时 URL 过期问题
- Prompt Builder 根据可用工具动态生成附件提示语
### 不做
- 不改造 LLM Provider 层(图片生成走独立工具,不走 chat completions
- 不新增附件上传能力(现有机制已满足图生图的参考图传递)
- 不做图片编辑器 UI生成结果在 tool-card 内渲染和预览)
## 2. 架构总览
```
packages/backend/src/modules/netaclaw/
├── image_providers/ # 新增:图片生成 Provider 层
│ ├── types.ts # 统一接口、凭证、错误类型、工厂函数
│ ├── ark.ts # 火山引擎OpenAI SDK images.generate
│ └── minimax.ts # MiniMax自有 REST API
├── tools/
│ ├── common.ts # 修改:新增 images 类型
│ ├── catalog.ts # 修改:注册两个新工具
│ └── builtin/
│ ├── text_to_image.ts # 新增:文生图工具
│ └── image_to_image.ts # 新增:图生图工具
├── service/
│ ├── tool_resolver.ts # 修改:注入凭证和 provider
│ └── image_storage.ts # 新增:图片转存本地服务
└── runtime/
└── prompt_builder.ts # 修改:附件提示语扩展
packages/frontend/src/modules/agent/
├── tools/renderer-registry.ts # 修改rawResult 新增 images 类型
└── components/tool-card.vue # 修改:单图/多图渲染
```
## 3. Provider 层
### 3.1 统一接口
```typescript
// image_providers/types.ts
export interface ImageProviderCredentials {
baseUrl: string;
apiKey: string;
supplier: string;
modelId: string;
promptHint: string | null;
extra?: ToolGovernanceExtra | null;
}
export interface TextToImageParams {
prompt: string;
width?: number;
height?: number;
aspectRatio?: string;
n?: number;
responseFormat?: 'url' | 'base64';
watermark?: boolean;
seed?: number;
extra?: Record<string, unknown>;
}
export interface ImageToImageParams extends TextToImageParams {
referenceImage: string;
strength?: number;
}
export interface ImageGenerationResult {
images: { url?: string; base64?: string; width?: number; height?: number }[];
model: string;
provider: string;
}
export interface ImageGenerationProvider {
readonly id: string;
textToImage(params: TextToImageParams, creds: ImageProviderCredentials): Promise<ImageGenerationResult>;
imageToImage(params: ImageToImageParams, creds: ImageProviderCredentials): Promise<ImageGenerationResult>;
}
export class ImageGenerationError extends Error {
constructor(
message: string,
public readonly code: 'content_safety' | 'rate_limit' | 'insufficient_balance' |
'invalid_params' | 'timeout' | 'network' | 'unknown',
public readonly retryable: boolean,
) {
super(message);
}
}
```
### 3.2 Provider 路由
supplier + baseUrl 联合路由,解决火山引擎 supplier 可能配为 "OpenAI" 的问题:
```typescript
export function getImageProvider(supplier: string, baseUrl: string): ImageGenerationProvider | null {
const s = supplier.toLowerCase();
if (s === 'minimax') return providers.get('minimax')!;
if (s === 'ark' || s === 'volcengine') return providers.get('ark')!;
if (s === 'openai') {
if (baseUrl.includes('volces.com') || baseUrl.includes('volcengine')) return providers.get('ark')!;
if (baseUrl.includes('minimax')) return providers.get('minimax')!;
}
return null;
}
```
### 3.3 火山引擎实现 (ark.ts)
- 使用 OpenAI SDK `client.images.generate()`
- 尺寸通过 `size` 字段传递(如 `"1024x1024"``"2K"`
- 图生图通过 `extra_body.image` 传参考图
- 特有参数watermark 等)通过 `extra_body` 透传
- 60 秒超时保护
### 3.4 MiniMax 实现 (minimax.ts)
- 使用自有 REST API `POST {baseUrl}/v1/image_generation`
- 尺寸通过 `aspect_ratio``width + height` 传递
- 图生图通过 `subject_reference` 字段传参考图
- 特有参数:`style`(画风)、`prompt_optimizer`prompt 优化)通过 `extra` 透传
- 响应从 `data.image_urls[]` 归一化到统一结构
- Bearer token 认证
- 60 秒超时保护
### 3.5 扩展新渠道
新增 Provider 只需:
1.`image_providers/` 下新建文件,实现 `ImageGenerationProvider` 接口
2. 在工厂函数 `getImageProvider` 中注册路由规则
## 4. 工具层
### 4.1 text_to_image
```typescript
// tools/builtin/text_to_image.ts
const Params = Type.Object({
prompt: Type.String({ description: '图片描述,尽量详细具体' }),
aspectRatio: Type.Optional(Type.String({
description: '宽高比。可选: 1:1, 16:9, 4:3, 3:2, 2:3, 3:4, 9:16',
})),
width: Type.Optional(Type.Integer({ description: '精确宽度(像素),优先级低于 aspectRatio' })),
height: Type.Optional(Type.Integer({ description: '精确高度(像素)' })),
n: Type.Optional(Type.Integer({ description: '生成数量,默认 1最大 9', minimum: 1, maximum: 9 })),
watermark: Type.Optional(Type.Boolean({ description: '是否添加水印' })),
seed: Type.Optional(Type.Integer({ description: '随机种子,相同 seed 可复现相近结果' })),
extra: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {
description: 'Provider 特有参数,如 MiniMax 的 style、prompt_optimizer',
})),
});
registerSchema({
name: 'text_to_image',
toolset: 'vision',
description: '根据文字描述生成图片,支持指定尺寸、数量、风格等参数。',
capability: 'multimodal',
visibility: 'tool',
isCore: false,
canDisable: true,
supportsPromptHint: true,
requiresModel: true,
});
```
工具描述会拼接 `promptHint`,管理员可在后台配置行业特定的默认提示词。
### 4.2 image_to_image
```typescript
// tools/builtin/image_to_image.ts
const Params = Type.Object({
prompt: Type.String({ description: '对参考图的修改描述' }),
referenceImage: Type.String({ description: '参考图片 URL从用户上传附件获取' }),
strength: Type.Optional(Type.Number({
description: '参考图影响强度 0-1越大越接近原图', minimum: 0, maximum: 1,
})),
aspectRatio: Type.Optional(Type.String({ description: '宽高比' })),
width: Type.Optional(Type.Integer({ description: '精确宽度(像素)' })),
height: Type.Optional(Type.Integer({ description: '精确高度(像素)' })),
n: Type.Optional(Type.Integer({ description: '生成数量,默认 1最大 9', minimum: 1, maximum: 9 })),
watermark: Type.Optional(Type.Boolean({ description: '是否添加水印' })),
seed: Type.Optional(Type.Integer({ description: '随机种子' })),
extra: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {
description: 'Provider 特有参数',
})),
});
registerSchema({
name: 'image_to_image',
toolset: 'vision',
description: '基于参考图片生成新图片支持风格迁移、内容编辑等。传入参考图URL和修改描述。',
capability: 'multimodal',
visibility: 'tool',
isCore: false,
canDisable: true,
supportsPromptHint: true,
requiresModel: true,
});
```
### 4.3 tool_resolver 集成
两个工具在 tool_resolver 中的注入逻辑与 `image_recognize` 对称:
1. 检查 `filteredNames` 是否包含工具名
2.`toolRegistry.getToolModelConfig()` 获取模型渠道配置
3.`modelChannelService.resolveForAgent()` 解析凭证
4. 通过 `getImageProvider(supplier, baseUrl)` 获取 provider 实例
5. 创建工具实例,注入 creds 和 provider
6. 未配置时写入 `disabledReasons`
两个工具可以绑定不同的模型渠道(如文生图用火山引擎,图生图用 MiniMax
## 5. 多图返回
### 5.1 后端类型扩展
```typescript
// tools/common.ts
export interface ImageItem {
url: string;
mimeType?: string;
width?: number;
height?: number;
seed?: number;
}
export type ToolResultContent =
| { type: 'text'; text: string }
| { type: 'json'; data: unknown }
| { type: 'image'; url: string; mimeType?: string; text?: string;
width?: number; height?: number; bytes?: number;
originalWidth?: number; originalHeight?: number;
originalBytes?: number; resized?: boolean; }
| { type: 'images'; images: ImageItem[]; text?: string };
export function imagesResult(images: ImageItem[], text?: string): ToolResultContent {
return { type: 'images', images, text };
}
```
### 5.2 toolResultToText 扩展
```typescript
if (value.type === 'images') {
const lines = value.images.map((img, i) =>
`[图${i + 1}] ${img.url}${img.width && img.height ? ` (${img.width}x${img.height})` : ''}`
);
const header = value.text || `已生成 ${value.images.length} 张图片`;
return `${header}\n${lines.join('\n')}`;
}
```
### 5.3 返回策略
- 单图n=1返回 `type: 'image'`,向后兼容
- 多图n>1返回 `type: 'images'`,前端网格渲染
## 6. 图片转存
### 6.1 问题
MiniMax URL 有效期 24 小时,火山引擎类似。历史会话中图片会失效。
### 6.2 方案
新增 `service/image_storage.ts`,工具 execute 完成后立即将临时 URL 下载并转存到本地存储:
```typescript
export class ImageStorageService {
async persist(tempUrl: string, metadata?: { toolName: string }): Promise<string> {
const response = await fetch(tempUrl);
const buffer = Buffer.from(await response.arrayBuffer());
const ext = this.detectExtension(response.headers.get('content-type'));
const filename = `${Date.now()}-${randomUUID().slice(0, 8)}${ext}`;
// 复用现有文件存储基础设施写入本地
return await this.fileService.saveBuffer(buffer, filename);
}
}
```
工具内部在 provider 返回后、构造 ToolResultContent 前执行转存session tree 中存储的是永久本地 URL。
## 7. 前端渲染
### 7.1 renderer-registry 扩展
`ToolRenderSource.rawResult` 新增 `images` 类型:
```typescript
rawResult?: {
type: 'text' | 'json' | 'image' | 'images';
// 现有字段...
images?: { url: string; mimeType?: string; width?: number; height?: number; seed?: number }[];
};
```
### 7.2 message-item.vue 渲染
当前 `message-item.vue` 已有 `rawResult.type === 'image'` 的单图渲染(`<img>` 标签)。需要:
- 将单图渲染升级为 `el-image`,支持点击预览大图
- 新增 `rawResult.type === 'images'` 分支CSS Grid 网格布局,`el-image` 预览列表联动
- 其他类型保持原有 `<pre>` 文本渲染
多图网格样式:`grid-template-columns: repeat(auto-fill, minmax(140px, 1fr))`,自适应列数。
## 8. Prompt Builder 扩展
### 8.1 改动
`buildLLMMessages` 新增 `toolNames` 参数,附件提示语根据 Agent 实际可用工具动态生成:
-`image_recognize` → 提示"如需分析图片内容,请使用 image_recognize 工具"
-`image_to_image` → 提示"如需基于图片生成新图片,请使用 image_to_image 工具,将图片 URL 作为 referenceImage 参数"
- 两者都有 → 两条提示都输出
避免提到 Agent 没有的工具。
## 9. 错误处理
Provider 层统一抛出 `ImageGenerationError`,包含错误码和是否可重试标记:
| 错误码 | 含义 | 可重试 | Agent 可见信息 |
|--------|------|--------|--------------|
| content_safety | 内容安全拦截 | 否 | "提示词触发内容安全策略,请调整描述" |
| rate_limit | 限流 | 是 | "当前请求过多,请稍后重试" |
| insufficient_balance | 余额不足 | 否 | "模型渠道余额不足" |
| invalid_params | 参数错误 | 否 | 具体参数错误信息 |
| timeout | 超时60s | 是 | "生成超时,可尝试降低图片尺寸" |
| network | 网络错误 | 是 | "网络连接失败" |
工具层捕获后返回结构化错误文本Agent 可据此决策(换 prompt 重试或告知用户)。
## 10. 工具参数控制权
### 10.1 问题
图片生成工具的参数n、aspectRatio、watermark、width/height 等)存在三个来源:
| 来源 | 设置者 | 例子 |
|------|--------|------|
| 工具管理页 `extra` | 管理员 | 默认 n=1、watermark=true |
| Agent 系统 prompt | 管理员 | "电商主图用 1:1" |
| Agent 运行时 tool_use | Agent响应用户 | `{ n: 4, aspectRatio: "16:9" }` |
如果管理员在工具管理页设置了 n=1但用户说"生成 4 张"Agent 传了 n=4应该听谁的
### 10.2 设计原则Agent 决策优先,管理员设默认值
参数分为两类:
**默认值参数Agent 可覆盖)**`n``aspectRatio``width``height``watermark``seed``responseFormat`
管理员在工具管理页的 `extra` 字段中配置默认值。Agent 不传时用默认值Agent 传了则以 Agent 为准。这些参数在工具编辑页可查看可编辑,标注"默认值Agent 可覆盖"。
**约束参数Agent 不可突破)**`maxN``maxWidth``maxHeight`
管理员设置硬上限,工具 execute 内部做 clamp。防止 Agent 被用户诱导生成过多或过大图片导致成本失控。这些参数在工具编辑页可查看可编辑,标注"硬上限"。
### 10.3 extra 字段结构
扩展 `netaclaw_tool.extra`,图片工具新增:
```typescript
// ToolGovernanceExtra 扩展
export type ToolGovernanceExtra = {
allowInSubagent?: boolean;
workerRoutingStrategy?: ToolWorkerRoutingStrategy;
// 图片工具新增
imageDefaults?: {
n?: number;
aspectRatio?: string;
width?: number;
height?: number;
watermark?: boolean;
responseFormat?: 'url' | 'base64';
};
imageConstraints?: {
maxN?: number; // 默认 9
maxWidth?: number; // 默认 2048
maxHeight?: number; // 默认 2048
};
};
```
### 10.4 工具 execute 内部合并逻辑
```typescript
async execute(_id, params) {
const defaults = creds.extra?.imageDefaults ?? {};
const constraints = creds.extra?.imageConstraints ?? {};
const merged = {
prompt: params.prompt,
n: Math.min(params.n ?? defaults.n ?? 1, constraints.maxN ?? 9),
aspectRatio: params.aspectRatio ?? defaults.aspectRatio,
width: clamp(params.width ?? defaults.width, constraints.maxWidth ?? 2048),
height: clamp(params.height ?? defaults.height, constraints.maxHeight ?? 2048),
watermark: params.watermark ?? defaults.watermark ?? false,
// ...
};
const result = await provider.textToImage(merged, creds);
// ...
}
```
### 10.5 前端工具编辑页
工具编辑抽屉中,当工具名为 `text_to_image``image_to_image` 时,在模型配置区域下方新增"图片生成配置"区块:
```
┌─ 图片生成配置 ──────────────────────────────┐
│ │
│ 默认值Agent 可覆盖) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ 数量: 1 │ │ 比例: 1:1│ │ 水印: ☑ │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ │
│ 硬上限 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │最大数量:9│ │最大宽:2048│ │最大高:2048 │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ │
│ ⓘ 默认值在 Agent 未指定时生效Agent 可根据 │
│ 用户指令覆盖。硬上限不可突破。 │
└──────────────────────────────────────────────┘
```
## 11. 专用电商套图 Agent
通过管理后台 Agent 编辑页配置:
- **系统 Prompt**:电商视觉设计领域知识(平台尺寸规范、构图原则、套图工作流)
- **工具集**`text_to_image` + `image_to_image` + `image_recognize` + `todo`
- **maxToolRounds**30套图需要多轮工具调用
- **promptHint**:各工具在后台配置行业特定的默认提示词
- **imageDefaults**:工具管理页配置电商常用默认值(如 aspectRatio: "1:1"、watermark: false
- **imageConstraints**:设置 maxN 防止成本失控
典型流程:用户描述产品 → Agent 规划套图方案 → 逐张生成 → 如有参考图则用图生图 → 汇总输出。
## 12. 完整改动清单
| 层 | 文件 | 改动类型 |
|----|------|---------|
| Provider | `image_providers/types.ts` | 新增 |
| Provider | `image_providers/ark.ts` | 新增 |
| Provider | `image_providers/minimax.ts` | 新增 |
| 工具 | `tools/common.ts` | 修改 |
| 工具 | `tools/builtin/text_to_image.ts` | 新增 |
| 工具 | `tools/builtin/image_to_image.ts` | 新增 |
| 注册 | `tools/catalog.ts` | 修改 |
| 治理 | `tools/manifest.ts` | 修改ToolGovernanceExtra 扩展) |
| 解析 | `service/tool_resolver.ts` | 修改 |
| 存储 | `service/image_storage.ts` | 新增 |
| Prompt | `runtime/prompt_builder.ts` | 修改 |
| 前端 | `tools/renderer-registry.ts` | 修改 |
| 前端 | `components/message-item.vue` | 修改(图片渲染升级 + 多图支持) |
| 前端 | `views/tools.vue` | 修改(图片生成配置区块) |