# Pixel 开发指南 本指南提供 Pixel 项目的开发最佳实践、API 使用说明和常见问题解决方案。 ## 目录 - [模型配置](#模型配置) - [API 调用](#api-调用) - [复合 ID 格式](#复合-id-格式) - [错误处理](#错误处理) - [最佳实践](#最佳实践) - [常见问题](#常见问题) --- ## 模型配置 ### 获取可用模型 使用 `/api/v1/models` 端点获取所有可用的模型配置: ```typescript // 前端示例 const response = await fetch('/api/v1/models'); const data = await response.json(); // 响应格式 { "code": "200", "data": { "image": { "dashscope/qwen-image": { "id": "dashscope/qwen-image", "name": "Qwen Image", "type": "image", "provider": "dashscope", "model_key": "qwen-image", "is_default": true, "enabled": true, "capabilities": { "supportsLora": true, "supportsRefImage": true }, "resolutions": { "1K": { "16:9": "1280*720", "1:1": "1024*1024" }, "2K": { "16:9": "2560*1440", "1:1": "2048*2048" } } } }, "video": { ... }, "audio": { ... }, "llm": { ... } } } ``` ### 模型配置结构 每个模型配置包含以下字段: | 字段 | 类型 | 说明 | |------|------|------| | `id` | string | 复合 ID,格式:`provider/model_key` | | `name` | string | 显示名称 | | `type` | string | 模型类型:`image`、`video`、`audio`、`llm` | | `provider` | string | 提供商名称 | | `model_key` | string | 模型键 | | `is_default` | boolean | 是否为默认模型 | | `enabled` | boolean | 是否启用 | | `capabilities` | object | 模型能力(可选) | | `resolutions` | object | 支持的分辨率(图片/视频) | | `durations` | object | 支持的时长(视频) | | `voices` | array | 支持的声音(音频) | --- ## API 调用 ### 复合 ID 格式 **重要**:所有 API 调用必须使用复合 ID 格式:`provider/model_key` #### ✅ 正确示例 ```typescript // 图片生成 await fetch('/api/v1/generations/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: "dashscope/qwen-image", // ✅ 复合 ID 格式 prompt: "a beautiful sunset" }) }); // 视频生成 await fetch('/api/v1/generations/video', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: "dashscope/wan2.6-video", // ✅ 复合 ID 格式 prompt: "a cat playing" }) }); ``` #### ❌ 错误示例 ```typescript // ❌ 错误:缺少 provider { model: "qwen-image", prompt: "a cat" } // ❌ 错误:使用了已废弃的 provider 参数 { model: "dashscope/qwen-image", provider: "dashscope", // 不再需要 prompt: "a cat" } ``` ### 图片生成 API **端点**:`POST /api/v1/generations/image` **请求参数**: ```typescript { // 必需参数 model: string; // 复合 ID: "provider/model_key" prompt: string; // 生成提示词 // 可选参数 negativePrompt?: string; // 负面提示词 imageInputs?: string[]; // 参考图片 URL 列表 resolution?: string; // 分辨率级别: "1K", "2K", "4K" aspectRatio?: string; // 宽高比: "16:9", "1:1", "9:16" n?: number; // 生成数量,默认 1 extraParams?: object; // 额外参数 // 元数据 projectId?: string; // 项目 ID source?: string; // 来源 sourceId?: string; // 来源 ID } ``` **响应**: ```typescript { "code": "200", "data": { "task_id": "task_123456" } } ``` **完整示例**: ```typescript const response = await fetch('/api/v1/generations/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: "dashscope/qwen-image", prompt: "a beautiful sunset over mountains", negativePrompt: "blurry, low quality", resolution: "2K", aspectRatio: "16:9", n: 1 }) }); const result = await response.json(); console.log('Task ID:', result.data.task_id); ``` ### 视频生成 API **端点**:`POST /api/v1/generations/video` **请求参数**: ```typescript { // 必需参数 model: string; // 复合 ID: "provider/model_key" prompt: string; // 生成提示词 // 可选参数 negativePrompt?: string; // 负面提示词 imageInputs?: string[]; // 参考图片 URL 列表 resolution?: string; // 分辨率级别 aspectRatio?: string; // 宽高比 duration?: string; // 时长: "5s", "10s" extraParams?: object; // 额外参数 // 元数据 projectId?: string; source?: string; sourceId?: string; } ``` ### 音频生成 API **端点**:`POST /api/v1/generations/audio` **请求参数**: ```typescript { // 必需参数 model: string; // 复合 ID: "provider/model_key" prompt: string; // 生成提示词或文本 // 可选参数 voice?: string; // 声音 ID speed?: number; // 语速 extraParams?: object; // 额外参数 // 元数据 projectId?: string; source?: string; sourceId?: string; } ``` --- ## 复合 ID 格式 ### 格式规范 复合 ID 使用 `/` 分隔符连接 provider 和 model_key: ``` 格式:provider/model_key 示例:dashscope/qwen-image ``` ### 格式验证 **有效格式**: - ✅ `dashscope/qwen-image` - ✅ `modelscope/qwen-image` - ✅ `volcengine/doubao-tts` - ✅ `google/gemini-pro` **无效格式**: - ❌ `qwen-image` (缺少 provider) - ❌ `dashscope-qwen-image` (使用了 `-` 而不是 `/`) - ❌ `dashscope/qwen/image` (多个分隔符) - ❌ `/qwen-image` (provider 为空) - ❌ `dashscope/` (model_key 为空) ### 前端验证 ```typescript function validateModel(model: string): void { if (!model || !model.trim()) { throw new Error('Model cannot be empty'); } if (!model.includes('/')) { throw new Error( `Model must be in format 'provider/model_key', got: '${model}'` ); } const parts = model.split('/'); if (parts.length !== 2) { throw new Error( `Model format invalid: '${model}'. Must have exactly one '/' separator.` ); } const [provider, modelKey] = parts; if (!provider || !modelKey) { throw new Error( `Model format invalid: '${model}'. Both provider and model_key must be non-empty.` ); } } // 使用示例 try { validateModel("dashscope/qwen-image"); // ✅ 通过 validateModel("qwen-image"); // ❌ 抛出错误 } catch (error) { console.error(error.message); } ``` ### 后端验证 后端会自动验证 model 格式,无效格式会返回 400 错误: ```python # backend/src/models/schemas/generation.py class ImageGenerationRequest(BaseModel): model: str @field_validator('model') @classmethod def validate_model_format(cls, v: str) -> str: if '/' not in v: raise ValueError( f"Model must be in format 'provider/model_key', got: '{v}'" ) parts = v.split('/') if len(parts) != 2: raise ValueError( f"Model format invalid: '{v}'. Must have exactly one '/' separator." ) provider, model_key = parts if not provider or not model_key: raise ValueError( f"Model format invalid: '{v}'. Both provider and model_key must be non-empty." ) return v ``` --- ## 错误处理 ### 常见错误码 | 错误码 | 说明 | 解决方案 | |--------|------|----------| | 400 | 请求参数错误 | 检查参数格式,特别是 model 格式 | | 404 | 模型不存在 | 使用 `/api/v1/models` 获取可用模型列表 | | 500 | 服务器内部错误 | 联系技术支持 | ### 错误响应格式 ```typescript { "code": "400", "message": "Model must be in format 'provider/model_key', got: 'qwen-image'. Example: 'dashscope/qwen-image'" } ``` ### 错误处理示例 ```typescript async function generateImage(params: ImageGenerationParams) { try { // 前端验证 validateModel(params.model); const response = await fetch('/api/v1/generations/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }); if (!response.ok) { const error = await response.json(); throw new Error(error.message); } return await response.json(); } catch (error) { if (error.message.includes('format')) { console.error('Model format error:', error.message); // 提示用户使用正确格式 } else if (error.message.includes('not found')) { console.error('Model not found:', error.message); // 提示用户选择其他模型 } else { console.error('Unknown error:', error); } throw error; } } ``` --- ## 最佳实践 ### 1. 使用 ModelStore 管理模型配置 ```typescript // frontend/src/lib/store/modelStore.ts import { useModelStore } from '@/lib/store/modelStore'; // 获取图片模型 const imageModels = useModelStore.getState().imageModels; // 获取特定模型 const model = imageModels["dashscope/qwen-image"]; // 获取默认模型 const defaultModel = Object.values(imageModels).find(m => m.is_default); ``` ### 2. 直接使用复合 ID ```typescript // ✅ 推荐:直接使用 model.id const modelId = model.id; // "dashscope/qwen-image" await ImageGenerationService.generate({ model: modelId, prompt: "a cat" }); // ❌ 不推荐:手动拼接 const modelId = `${model.provider}/${model.model_key}`; ``` ### 3. 缓存模型配置 ```typescript // 只在首次加载时请求 let modelsLoaded = false; async function loadModels() { if (modelsLoaded) return; const response = await fetch('/api/v1/models'); const data = await response.json(); useModelStore.getState().setModels(data); modelsLoaded = true; } ``` ### 4. 验证用户输入 ```typescript // 在发送请求前验证 function validateGenerationParams(params: ImageGenerationParams) { validateModel(params.model); if (!params.prompt || params.prompt.trim().length === 0) { throw new Error('Prompt cannot be empty'); } if (params.n && (params.n < 1 || params.n > 4)) { throw new Error('n must be between 1 and 4'); } } ``` ### 5. 处理异步任务 ```typescript async function generateAndWait(params: ImageGenerationParams) { // 1. 创建任务 const { data } = await ImageGenerationService.generate(params); const taskId = data.task_id; // 2. 轮询任务状态 while (true) { const task = await fetch(`/api/v1/tasks/${taskId}`).then(r => r.json()); if (task.data.status === 'completed') { return task.data.result; } if (task.data.status === 'failed') { throw new Error(task.data.error); } // 等待 1 秒后重试 await new Promise(resolve => setTimeout(resolve, 1000)); } } ``` --- ## 常见问题 ### Q1: 为什么不再需要 `provider` 参数? **A**: 复合 ID 已经包含了 provider 信息。例如 `dashscope/qwen-image` 中,`dashscope` 就是 provider。传递单独的 `provider` 参数是冗余的。 **迁移示例**: ```typescript // 旧代码(已废弃) await ImageGenerationService.generate({ model: "dashscope/qwen-image", provider: "dashscope", // ❌ 冗余 prompt: "a cat" }); // 新代码 await ImageGenerationService.generate({ model: "dashscope/qwen-image", // ✅ 只需要 model prompt: "a cat" }); ``` ### Q2: 如何从复合 ID 中提取 provider? **A**: 使用字符串分割: ```typescript const modelId = "dashscope/qwen-image"; const [provider, modelKey] = modelId.split('/'); console.log(provider); // "dashscope" console.log(modelKey); // "qwen-image" ``` 但通常不需要手动提取,因为 `ModelConfig` 对象已经包含了 `provider` 字段: ```typescript const model = imageModels["dashscope/qwen-image"]; console.log(model.provider); // "dashscope" ``` ### Q3: 如何处理模型不存在的情况? **A**: 后端会返回 404 错误,前端应该提示用户选择其他模型: ```typescript try { await ImageGenerationService.generate({ model: "invalid/model", prompt: "a cat" }); } catch (error) { if (error.message.includes('not found')) { // 提示用户 alert('模型不存在,请选择其他模型'); // 获取可用模型列表 const response = await fetch('/api/v1/models'); const { data } = await response.json(); console.log('Available models:', Object.keys(data.image)); } } ``` ### Q4: 如何选择默认模型? **A**: 使用 `is_default` 字段: ```typescript const imageModels = useModelStore.getState().imageModels; const defaultModel = Object.values(imageModels).find(m => m.is_default); if (defaultModel) { console.log('Default model:', defaultModel.id); } ``` ### Q5: 如何检查模型是否支持某个功能? **A**: 使用 `capabilities` 字段: ```typescript const model = imageModels["dashscope/qwen-image"]; if (model.capabilities?.supportsLora) { console.log('Model supports LoRA'); } if (model.capabilities?.supportsRefImage) { console.log('Model supports reference images'); } ``` ### Q6: 如何获取模型支持的分辨率? **A**: 使用 `resolutions` 字段: ```typescript const model = imageModels["dashscope/qwen-image"]; // 获取所有分辨率级别 const levels = Object.keys(model.resolutions); // ["1K", "2K", "4K"] // 获取特定级别的宽高比 const aspectRatios = Object.keys(model.resolutions["2K"]); // ["16:9", "1:1", "9:16"] // 获取具体尺寸 const size = model.resolutions["2K"]["16:9"]; // "2560*1440" ``` ### Q7: 旧代码中的 `getProviderForModel` 函数怎么办? **A**: 该函数已被移除,不再需要。直接使用 `ModelConfig.provider` 字段: ```typescript // 旧代码(已废弃) const provider = getProviderForModel(modelId, 'image'); // ❌ // 新代码 const model = imageModels[modelId]; const provider = model.provider; // ✅ ``` ### Q8: 如何处理多个 provider 提供相同的 model_key? **A**: 使用完整的复合 ID 来区分: ```typescript // 两个不同的模型,但 model_key 相同 const dashscopeModel = imageModels["dashscope/qwen-image"]; const modelscopeModel = imageModels["modelscope/qwen-image"]; // 使用时指定完整的复合 ID await ImageGenerationService.generate({ model: "dashscope/qwen-image", // 使用 DashScope 的版本 prompt: "a cat" }); ``` ### Q9: 如何测试 API 调用? **A**: 使用 curl 或 Postman: ```bash # 测试图片生成 curl -X POST http://localhost:8000/api/v1/generations/image \ -H "Content-Type: application/json" \ -d '{ "model": "dashscope/qwen-image", "prompt": "a beautiful sunset", "resolution": "2K", "aspectRatio": "16:9" }' # 测试模型配置 curl http://localhost:8000/api/v1/models ``` ### Q10: 如何调试模型查找问题? **A**: 检查后端日志: ```bash # 后端会记录详细的查找过程 tail -f logs/app.log | grep "Resolving service" ``` 日志示例: ``` INFO: Resolving service: model='dashscope/qwen-image', type=image INFO: Found service by composite ID: 'dashscope/qwen-image' ``` --- ## 相关文档 - [API 文档](./API.md) - 完整的 API 参考 - [项目 README](../README.md) - 项目概述和快速开始 - [前端优化文档](./FRONTEND_OPTIMIZATION.md) - 前端性能优化策略 --- **最后更新**:2026-02-11 **版本**:1.0.0