# YunQi AI 完整 API Reference(供 Agent 读取) > 文档版本:v1.0.11 > 更新日期:2026-09-14 > 内容:API 协议、模型参数、文件输入、客户端配置与可运行示例,包含 GPT Image 2.5 Flare / Sunburst 和 MJ / 悠船。 本文件提供 YunQi AI 的接口、参数、媒体格式、图片尺寸、返回结构、错误处理和客户端配置。它适合直接交给 Codex、Claude Code、WorkBuddy 或其他开发 Agent,用来完成接入、实现调用、排查错误和解析返回。 Agent 直读入口为文档站同域名的 `/llms-full.txt`,索引为 `/llms.txt`。这些地址不需要登录或 API Key;它们属于文档站,不是 API 服务端点。 ## 0. Agent 执行规则 1. 让用户在 YunQi AI 控制台创建 API Key,并将密钥保存在环境变量或客户端私有配置中。 2. 开始实现前调用 `GET /v1/models`,以这把 API Key 实际返回的完整模型 ID 为准。若返回 401/403,先停止后续生成调用,处理密钥或分组权限;不要用更换模型、端点或参数绕过。 3. 根据模型和任务选择协议,不要混用不同协议的字段结构。 4. 先发送只含必填字段的最小请求,再加入流式输出、工具、多模态或图片参数。 5. 遍历完整响应数组和内容块,不要只读取第一个图片或第一个内容块。 6. 不要把 API Key 写进仓库、日志、截图或回复正文。 7. 用户决定模型;不要自行替换型号。按对应模型章节构造请求,不跨型号套用参数或接口。 8. GPT Image 2.5 编辑使用 `/v1/images/edits?api-version=2025-04-01-preview`,不发送 `response_format`、`input_fidelity`、`output_format=webp`。完整字段见 12A。 9. MJ 使用 `POST /v1/tob/diffusion` 的 `text` 字段,再用 `GET /v1/tob/job/{jobId}` 查询。`subscribe` 是上游账户查询路径,不能用于生图。完整异步流程见 12B。 10. 多轮会话由客户端维护历史。Responses 回传原始 `input`、完整 `output[]`(含 reasoning 项)和工具结果,不使用 `previous_response_id` 或响应检索接口保存会话。 11. POST 超时或连接断开时结果可能未知;不要自动重放、切换型号或重复执行有副作用的工具。SDK 示例关闭内置自动重试。 ## 1. 连接清单 ```yaml provider: YunQi AI console: https://www.yunqiai.chat openai_base_url: https://www.yunqiai.chat/v1 anthropic_base_url: https://www.yunqiai.chat gemini_generate_content_url: https://www.yunqiai.chat/v1beta/models/{model}:generateContent models_endpoint: GET https://www.yunqiai.chat/v1/models image_25_edits_url: https://www.yunqiai.chat/v1/images/edits?api-version=2025-04-01-preview mj_generation_url: https://www.yunqiai.chat/v1/tob/diffusion mj_job_url: https://www.yunqiai.chat/v1/tob/job/{jobId} authentication: openai: "Authorization: Bearer YOUR_API_KEY" anthropic: "x-api-key: YOUR_API_KEY" gemini: "x-goog-api-key: YOUR_API_KEY" ``` ### Base URL 与完整接口地址 - OpenAI SDK、Codex、Cherry Studio、Chatbox 等需要 Base URL 的客户端填写 `https://www.yunqiai.chat/v1`。 - Anthropic SDK 与 Claude Code 填写 `https://www.yunqiai.chat`,客户端会继续请求 `/v1/messages`。 - Gemini 原生调用使用完整地址 `https://www.yunqiai.chat/v1beta/models/{model}:generateContent`。除非客户端明确说明其路径拼接规则,不要把站点根地址直接当作 Gemini SDK Base URL。 - WorkBuddy 的自定义协议字段要求完整地址,填写 `https://www.yunqiai.chat/v1/chat/completions`。 - 不要在已经包含 `/v1` 的 Base URL 后再次拼接 `/v1`。 ### 鉴权与 Content-Type | 协议 | 鉴权请求头 | 常用 Content-Type | |---|---|---| | OpenAI Compatible、Responses、Images | `Authorization: Bearer YOUR_API_KEY` | JSON 请求使用 `application/json`;图片编辑使用 `multipart/form-data` | | Anthropic Messages | `x-api-key: YOUR_API_KEY` | `application/json` | | Gemini Generate Content | `x-goog-api-key: YOUR_API_KEY` | `application/json` | Anthropic Messages 还需要: ```text anthropic-version: 2023-06-01 ``` ## 2. 当前发布接口 | 场景 | 方法与路径 | 关键输入 | 鉴权 | 适用模型 | |---|---|---|---|---| | 获取模型列表 | `GET /v1/models` | 无请求体 | Bearer | 当前 API Key 可调用的模型 | | OpenAI 对话 | `POST /v1/chat/completions` | `model`、`messages` | Bearer | GPT、Claude、Gemini 文本模型 | | OpenAI Responses | `POST /v1/responses` | `model`、`input` | Bearer | GPT 系列 | | Anthropic Messages | `POST /v1/messages` | `model`、`messages`、`max_tokens` | `x-api-key` | Claude 系列 | | Gemini 文本与多模态 | `POST /v1beta/models/{model}:generateContent` | `contents` | `x-goog-api-key` | Gemini 文本模型 | | Gemini 生图与参考图编辑 | `POST /v1beta/models/{model}:generateContent` | `contents`、`generationConfig.imageConfig` | `x-goog-api-key` | Gemini Image 模型 | | OpenAI 图片生成 | `POST /v1/images/generations` | `model`、`prompt` | Bearer | `gpt-image-2` | | OpenAI 图片编辑 | `POST /v1/images/edits` | `model`、`prompt`、`image` | Bearer | `gpt-image-2` | | GPT Image 2.5 生成 | `POST /v1/images/generations` | `model`、`prompt` | Bearer | `gpt-image-2.5-flare`、`gpt-image-2.5-sunburst` | | GPT Image 2.5 编辑 | `POST /v1/images/edits?api-version=2025-04-01-preview` | `model`、`prompt`、multipart `image` / `image[]` | Bearer | 两款 GPT Image 2.5 | | MJ 生成 | `POST /v1/tob/diffusion` | `model`、`text` | Bearer | model 可为 mj_imagine 或 youchuan-image | | MJ 查询 | `GET /v1/tob/job/{jobId}` | 路径中传返回的任务 `id` | Bearer | MJ / 悠船任务 | Gemini Image 的 `generateContent` 在当前 HTTP 响应中返回结果。GPT Images 的同步与流式格式见各型号章节;GPT Image 2.5 使用第 12A 节的同步 JSON 响应。MJ 为异步任务,必须查询到完成状态。MJ 的查询接口不能查询 GPT Images 或 Gemini Image 的生成请求。 ## 3. 模型目录与能力 模型目录用于核对模型 ID 和协议。实现时先调用 `GET /v1/models`,因为不同 API Key 可见的模型可能不同。用户自行决定调用型号。 | 模型 ID | 输入 | 输出 | 首选协议 | 主要用途或边界 | |---|---|---|---|---| | `gpt-6-astra` | 文本、图片、PDF | 文本 | Responses;文本对话也可用 Chat Completions | 推理、工具调用、结构化输出;见第 11 节 | | `gpt-5.6-sol` | 文本、图片 | 文本 | Chat Completions | 复杂推理、高难度代码、Agent 工作流与图片理解 | | `gpt-5.6-terra` | 文本 | 文本 | Chat Completions | 通用对话与 Agent 工作流,兼顾效果、速度与成本 | | `gpt-5.6-luna` | 文本、图片 | 文本 | Chat Completions | 低延迟、图片理解、分类、提取、改写与高频轻量任务 | | `gpt-5.5` | 文本、图片、文件 | 文本 | Responses | 图片理解、文件任务、工具调用、结构化输出 | | `claude-sonnet-4-6` | 文本、图片 | 文本 | Anthropic Messages | 代码、长文本、图片理解与工具调用 | | `claude-opus-4-6` | 文本 | 文本 | Anthropic Messages | 复杂推理与长上下文任务 | | `claude-opus-4-7` | 文本、图片、文件 | 文本 | Anthropic Messages | 推理、工具、图片与文件任务 | | `claude-opus-4-8` | 文本 | 文本 | Anthropic Messages | 高阶推理与大型代码任务 | | `claude-sonnet-5` | 文本 | 文本 | Anthropic Messages | 日常开发、审阅与通用推理 | | `claude-fable-5` | 文本 | 文本 | Anthropic Messages | 高难度代码、架构理解与长任务 | | `gemini-3.5-flash` | 文本、图片、视频、音频、PDF | 文本 | Gemini Generate Content | 低延迟多模态;上下文 1,048,576 tokens;输出上限 65,536 tokens | | `gemini-3.1-pro-preview` | 文本、图片、视频、音频、PDF | 文本 | Gemini Generate Content | 复杂多模态推理;上下文 1,048,576 tokens;输出上限 65,536 tokens | | `gemini-3.1-flash-image` | 文本、参考图 | 图片、文本 | Gemini Generate Content | 512、1K、2K、4K;最多 14 张参考图 | | `gemini-3-pro-image-preview` | 文本、参考图 | 图片、文本 | Gemini Generate Content | 1K、2K、4K;最多 14 张参考图 | | `gpt-image-2` | 文本、参考图、蒙版 | 图片 | Images Generations / Edits | 文本生图、参考图编辑、多图合成与局部重绘 | | `gpt-image-2.5-flare` | 文本、参考图、蒙版 | 图片 | Images Generations / 带 api-version 的 Edits | 五档质量及 auto、单图与多参考图;见 12A | | `gpt-image-2.5-sunburst` | 文本、参考图、蒙版 | 图片 | Images Generations / 带 api-version 的 Edits | 五档质量及 auto、单图与多参考图;见 12A | | MJ / 悠船 | 文本、图片 URL | 异步图片 URL | `/v1/tob/diffusion` 与 `/v1/tob/job/{jobId}` | 模型列表可见 mj_imagine、youchuan-image;生成请求使用 text;见 12B | | `glm-5.2` | — | — | 联系商务 | 已灰度下线,如有需要请联系商务 | `glm-5.2` 的接入范围与调用配置以商务确认信息为准。Agent 不应自动尝试调用该模型,也不要自行替换为其他型号。 ## 4. GET `/v1/models` 返回当前 API Key 可见的模型目录。可见不等于该分组始终有可用渠道,使用最小请求确认调用权限。客户端模型选择器使用 `data[].id`,不要把展示名称当作模型 ID。 ### 请求 ```bash curl https://www.yunqiai.chat/v1/models \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### 响应结构 ```json { "object": "list", "data": [ {"id": "gpt-6-astra", "object": "model"}, {"id": "gpt-5.6-sol", "object": "model"}, {"id": "claude-sonnet-4-6", "object": "model"}, {"id": "gemini-3.5-flash", "object": "model"} ] } ``` ### Agent 处理要求 - 使用 `data[].id` 填充模型列表。 - 切换 API Key 后重新读取模型列表。 - 收到 `model_not_found` 时重新读取列表,并检查是否使用了正确分组的 API Key。 ## 5. POST `/v1/chat/completions` OpenAI 兼容对话接口。GPT、Claude 与 Gemini 文本模型可以使用此接口;模型专属能力优先使用上方模型表中的首选协议。 ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `model` | `string` | 是 | 使用 `GET /v1/models` 返回的完整模型 ID | | `messages` | `array` | 是 | `system`、`user`、`assistant`、`tool` 消息数组 | | `max_completion_tokens` | `integer` | 否 | 最大生成 Token;上限随模型变化 | | `stream` | `boolean` | 否 | 是否使用 SSE 流式返回;默认 `false` | | `temperature` | `number` | 否 | `0–2`;推理模型建议省略,使用模型默认值 | | `top_p` | `number` | 否 | `0–1`;通常与 `temperature` 只设置一个 | | `n` | `integer` | 否 | 返回候选数量;通常使用 `1` 控制费用 | | `stop` | `string` 或 `string[]` | 否 | 停止序列;使用模型默认行为时省略 | | `tools` | `array` | 否 | OpenAI Function Calling 工具定义 | | `tool_choice` | `string` 或 `object` | 否 | `auto`、`none`、`required` 或指定工具 | | `response_format` | `object` | 否 | 普通文本或 JSON Schema 结构化输出 | ### 最小请求 ```bash curl --request POST \ --url https://www.yunqiai.chat/v1/chat/completions \ --header "Authorization: Bearer YOUR_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "gpt-5.6-sol", "messages": [ {"role": "user", "content": "你好,请介绍一下你自己"} ], "stream": false }' ``` ### 图片输入 OpenAI 兼容消息使用 `image_url` 内容块。Base64 必须写成完整 Data URL。 ```json { "model": "gpt-5.6-sol", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "描述这张图片"}, { "type": "image_url", "image_url": { "url": "data:image/png;base64,BASE64_IMAGE_DATA", "detail": "low" } } ] } ] } ``` ### 响应结构 ```json { "id": "chatcmpl_01JY7X", "object": "chat.completion", "model": "gpt-5.6-sol", "choices": [ { "index": 0, "message": {"role": "assistant", "content": "你好!我是一个 AI 助手。"}, "finish_reason": "stop" } ] } ``` 非流式文本通常读取 `choices[0].message.content`。如果 `n` 大于 `1`,遍历全部 `choices[]`。 ## 6. POST `/v1/responses` OpenAI Responses 原生请求格式,适用于 GPT 系列。 ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `model` | `string` | 是 | 要调用的 GPT 模型 ID | | `input` | `string` 或 `array` | 是 | 文本、消息、图片或 `input_file` 文件输入项 | | `max_output_tokens` | `integer` | 否 | 从 `16` 起;最大值随模型变化 | | `stream` | `boolean` | 否 | 是否使用 SSE 流式返回;默认 `false` | | `temperature` | `number` | 否 | `0–2`;推理模型建议省略,使用模型默认值 | | `top_p` | `number` | 否 | `0–1`;通常与 `temperature` 只设置一个 | | `tools` | `array` | 否 | 客户端执行的 `function` 工具定义;图片生成使用 Images 端点 | | `metadata` | `object` | 否 | 业务元数据 | ### 最小请求 ```bash curl --request POST \ --url https://www.yunqiai.chat/v1/responses \ --header "Authorization: Bearer YOUR_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "gpt-5.6-sol", "input": "用三句话解释量子计算", "max_output_tokens": 1024, "stream": false }' ``` ### Base64 图片输入 Responses 的 `input_image.image_url` 使用完整 Data URL,不要只传裸 Base64。 ```json { "model": "gpt-5.6-luna", "input": [ { "role": "user", "content": [ {"type": "input_text", "text": "描述这张图片"}, { "type": "input_image", "image_url": "data:image/png;base64,BASE64_IMAGE_DATA", "detail": "low" } ] } ], "max_output_tokens": 256 } ``` ### 文件输入(`gpt-5.5`) `gpt-5.5` 的 Responses 请求使用 `input_file` 内容项。每个文件只选择一种来源: | 来源 | 必需字段 | 写法 | |---|---|---| | 内联文件 | `type`、`filename`、`file_data` | `file_data` 使用完整 Data URL,例如 `data:application/pdf;base64,...` | | 外部地址 | `type`、`file_url` | 使用服务端可以访问的完整 HTTPS URL | 内联 PDF 请求示例: ```json { "model": "gpt-5.5", "input": [ { "role": "user", "content": [ { "type": "input_file", "filename": "brief.pdf", "file_data": "data:application/pdf;base64,BASE64_FILE_DATA" }, { "type": "input_text", "text": "提取这份文件的关键结论。" } ] } ], "max_output_tokens": 1024 } ``` 外部 PDF 使用以下文件项;URL 必须可由服务端访问: ```json {"type": "input_file", "file_url": "https://example.com/brief.pdf"} ``` 不使用 `file_id` 或 `/v1/files` 上传流程,其他服务商的文件 ID 不能跨服务复用。Base64 体积约为原文件的 4/3,还需计算 JSON 和文本开销;不要把单文件限制当成整次请求限制。格式或大小被拒绝后,转换为 PDF 或拆分再提交。 DOCX、PPTX 先在客户端转换为 PDF,再按上述文件字段发送。TXT、CSV、XLSX 可先提取文本或所需行列,说明列名和单位,再放入 `input_text`。不要把 Office 文件直接换成 PDF 的 MIME 类型。 文件接入验收需要同时检查响应状态和内容:使用含有已知标题、数字或标识的文件,确认回答确实引用了附件中的信息。HTTP 200、非空文本或 `status=completed` 都不能单独证明文件已被读取。若回答要求重新提供已经附带的文件,或无法回答附件中的已知事实,应按文件理解失败处理,不交付为摘要。保留请求 ID,联系支持确认当前密钥分组下该模型的文件处理能力;不要自动换模型或重复发送计费请求。 ### 响应读取 原始 HTTP JSON 使用类型化的 `output[]`,例如: ```json { "id": "resp_01JY7X", "object": "response", "model": "gpt-5.5", "output": [ { "id": "rs_01JY7X", "type": "reasoning", "summary": [] }, { "id": "msg_01JY7X", "type": "message", "status": "completed", "role": "assistant", "content": [ { "type": "output_text", "text": "这是模型返回的文本。", "annotations": [] } ] } ] } ``` OpenAI SDK 提供的 `response.output_text` 是把文本内容聚合后的便利属性,不是需要从原始 HTTP JSON 顶层读取的独立字段。只需要最终文本时可使用 SDK 的 `output_text`;处理推理、工具或多模态输出时,应遍历 `response.output[]`,按每个 Item 的 `type` 分支,再遍历消息 Item 的 `content[]`。 多轮会话在客户端保存历史。下一轮发送此前 `input`、上一轮完整 `output[]` 与新增用户消息;不要只保留可见文本而丢弃 reasoning 或工具项。不要发送 `previous_response_id`,也不要依赖 `GET /v1/responses/{id}` 恢复会话。请求返回 200 不代表上一轮上下文已被保存。 ## 7. POST `/v1/messages` Anthropic 原生 Messages API,适用于 Claude 系列。 ### 必需请求头 ```text x-api-key: YOUR_API_KEY anthropic-version: 2023-06-01 Content-Type: application/json ``` ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `model` | `string` | 是 | 使用 `GET /v1/models` 返回的完整 Claude 模型 ID | | `messages` | `array` | 是 | `user` 与 `assistant` 消息数组;图片使用 `image`,文档使用 `document` 内容块 | | `max_tokens` | `integer` | 是 | 最大输出 Token,填写正整数;上限随模型变化 | | `system` | `string` 或 `array` | 否 | 顶层系统提示;Messages API 没有 `system` 角色 | | `stream` | `boolean` | 否 | 是否使用 SSE 流式返回;默认 `false` | | `temperature` | `number` | 否 | `0–1`;Claude 4.7 及以后模型建议省略采样参数 | | `top_p` | `number` | 否 | `0–1`;通常与 `temperature` 只设置一个 | | `stop_sequences` | `string[]` | 否 | 自定义停止序列 | | `tools` | `array` | 否 | Anthropic 工具定义 | ### 最小请求 ```bash curl --request POST \ --url https://www.yunqiai.chat/v1/messages \ --header "x-api-key: YOUR_API_KEY" \ --header "anthropic-version: 2023-06-01" \ --header "Content-Type: application/json" \ --data '{ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [ {"role": "user", "content": "你好"} ] }' ``` ### Base64 图片输入 Claude 图片使用 `image` 内容块。`source.data` 填裸 Base64,MIME 类型单独填写在 `source.media_type`。 ```json { "model": "claude-sonnet-4-6", "max_tokens": 256, "messages": [ { "role": "user", "content": [ { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "BASE64_IMAGE_DATA" } }, {"type": "text", "text": "描述这张图片"} ] } ] } ``` 图片输入范围: - 支持 JPEG、PNG、GIF、WebP。 - 单张图片控制在 `8000×8000` 以内。 - Base64 编码后单张图片小于 `10 MB`。 - 整次请求体控制在 `32 MB` 以内。 ### 文档输入(`claude-opus-4-7`) Anthropic Messages 使用 `document` 内容块内联 PDF: | `source.type` | 必需字段 | 说明 | |---|---|---| | `base64` | `media_type`、`data` | `media_type` 填 `application/pdf`,`data` 填裸 Base64 | 可直接发送的 Base64 PDF 请求: ```json { "model": "claude-opus-4-7", "max_tokens": 1024, "messages": [ { "role": "user", "content": [ { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": "BASE64_PDF_DATA" } }, { "type": "text", "text": "总结这份文档,并列出三个关键结论。" } ] } ] } ``` 不使用 `/v1/files`、`file_id` 或外部 PDF URL。将本地文件编码后放入 `source.data`;不要添加 Data URL 前缀。较大的 PDF 先拆分,再分别提交。Base64 体积约增加 1/3,整次请求还包括提示词与其他内容块。 ### 响应结构 ```json { "id": "msg_01JY7X", "type": "message", "role": "assistant", "model": "claude-sonnet-4-6", "content": [ {"type": "text", "text": "你好!我是一个 AI 助手。"} ], "stop_reason": "end_turn" } ``` 遍历 `content[]`,分别处理文本块、工具调用块及其他协议内容块。 ## 8. POST `/v1beta/models/{model}:generateContent` Gemini 原生 Generate Content。文本、多模态理解和 Gemini 生图模型共用这一路径;模型 ID 位于 URL 中。 ### 文本与多模态参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `contents` | `array` | 是 | Content 数组,每项含 `role` 与 `parts` | | `systemInstruction` | `object` | 否 | 系统指令,结构同 Content | | `generationConfig.maxOutputTokens` | `integer` | 否 | 最大输出 Token;上限随模型变化 | | `generationConfig.temperature` | `number` | 否 | 通常为 `0–2`;默认值和范围以模型为准 | | `generationConfig.topP` | `number` | 否 | `0–1` 的核采样阈值 | | `generationConfig.stopSequences` | `string[]` | 否 | 停止序列 | | `safetySettings` | `array` | 否 | 按危害类别设置安全阈值 | ### 文本请求 ```bash curl --request POST \ --url https://www.yunqiai.chat/v1beta/models/gemini-3.5-flash:generateContent \ --header "x-goog-api-key: YOUR_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "contents": [ {"role": "user", "parts": [{"text": "你好,请介绍一下你自己"}]} ], "generationConfig": {"maxOutputTokens": 1024} }' ``` ### 内联媒体格式 媒体放在 `contents[].parts[].inlineData` 中,`data` 只放裸 Base64,`mimeType` 填实际 MIME 类型。媒体、提示词和系统指令合计控制在 `20 MB` 以内。 | 输入 | 支持格式与写法 | 用途 | |---|---|---| | 图片 | PNG、JPEG、WebP、HEIC、HEIF;`inlineData` | 识图、OCR、图表与界面分析 | | 视频 | MP4、MPEG、MOV、AVI、FLV、MPG、WebM、WMV、3GPP;`inlineData` | 摘要、事件提取、时间点问答 | | 音频 | 使用实际音频 MIME 类型;`inlineData` | 转写、摘要、说话内容分析 | | PDF | `application/pdf`;`inlineData` | 文档、扫描件与表格理解 | ### 图片输入 ```json { "contents": [ { "parts": [ {"text": "描述这张图片"}, { "inlineData": { "mimeType": "image/png", "data": "BASE64_IMAGE_DATA" } } ] } ], "generationConfig": {"maxOutputTokens": 2048} } ``` 输出预算需要同时容纳思考过程与最终回答。读取每个候选的 `finishReason`;`MAX_TOKENS` 表示预算耗尽,即使 HTTP 200 且已有文字,也不能标记为完整回答。保留部分结果和用量,提示调用方提高 `maxOutputTokens` 或缩小任务范围后重新发起请求,不自动重放。本例的 2048 是起始预算,不保证复杂任务一定完成。没有候选或没有所需输出时,检查 `promptFeedback` 和候选结束原因,不按空结果成功处理。 ### 视频参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `inlineData.mimeType` | `string` | 是 | 例如 `video/mp4` | | `inlineData.data` | `string` | 是 | 裸 Base64,不带 `data:video/...` 前缀 | | `videoMetadata.startOffset` | `duration` | 否 | 分析起点,例如 `40s` | | `videoMetadata.endOffset` | `duration` | 否 | 分析终点,例如 `80s` | | `videoMetadata.fps` | `number` | 否 | 默认约 `1 FPS`;长视频可低于 `1`,快速动作可适当提高 | ```json { "contents": [ { "parts": [ { "inlineData": { "mimeType": "video/mp4", "data": "BASE64_VIDEO_DATA" }, "videoMetadata": { "startOffset": "0s", "endOffset": "30s", "fps": 1 } }, {"text": "概括视频中的主要事件,并给出对应时间点。"} ] } ], "generationConfig": {"maxOutputTokens": 2048} } ``` 较长视频先压缩或按时间段裁切,再使用 `startOffset` 与 `endOffset` 分段分析。复杂视频任务可从 `maxOutputTokens: 2048` 起设置。 ### 文本响应结构 ```json { "candidates": [ { "content": { "role": "model", "parts": [{"text": "你好!我是一个 AI 助手。"}] }, "finishReason": "STOP" } ], "modelVersion": "gemini-3.5-flash" } ``` 遍历 `candidates[].content.parts[]`,分别读取 `text` 或 `inlineData`。 ## 9. Base64 与文件上传对照 | 协议 | 输入字段 | 传值形式 | 输出图片字段 | |---|---|---|---| | Chat Completions | `messages[].content[].image_url.url` | 完整 Data URL:`data:image/png;base64,...` | 视觉理解返回文本 | | Responses | `input_image.image_url` | 完整 Data URL:`data:image/png;base64,...` | 视觉理解返回文本 | | Responses 文件 | `input_file.file_data` | 完整 Data URL,并同时填写 `filename` | 文件理解返回文本 | | Anthropic Messages | `source.data` | 裸 Base64,并填写 `source.media_type` | 视觉理解返回文本 | | Anthropic PDF | `document.source.data` | 裸 Base64,`source.type` 为 `base64`,`media_type` 为 `application/pdf` | 文档理解返回文本 | | Gemini Generate Content | `inlineData.data` | 裸 Base64,并填写 `inlineData.mimeType` | Gemini 生图:`parts[].inlineData.data` | | OpenAI Images Edits | `image` 或重复的 `image[]` | `multipart/form-data` 文件 | `data[].b64_json` 或 `data[].url` | 如果现有图片只有 Base64,而接口要求 multipart 文件,先将 Base64 解码为图片文件,再上传到 `image` 字段。 ## 10. Gemini Image:生成与参考图编辑 适用模型: - `gemini-3.1-flash-image` - `gemini-3-pro-image-preview` 端点: ```text POST /v1beta/models/{model}:generateContent ``` 只传文字时生成图片;在同一个 `parts` 数组中加入 `inlineData` 时进行参考图生成或编辑。结果在当前请求中直接返回。 ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `contents` | `array` | 是 | 文本与参考图片输入 | | `contents[].role` | `string` | 否 | `user` 或 `model`;单轮可省略,多轮按对话顺序填写 | | `contents[].parts[].text` | `string` | 否 | 提示词;Flash 输入上下文最多 131,072 tokens,Pro 最多 65,536 tokens | | `contents[].parts[].inlineData` | `object` | 否 | Base64 参考图,可与文字放在同一个 `parts` 数组中 | | `systemInstruction` | `object` | 否 | 系统指令,计入输入上下文与请求体大小 | | `generationConfig.responseModalities` | `string[]` | 否 | `TEXT`、`IMAGE` 或二者;只要图片可设为 `["IMAGE"]` | | `generationConfig.imageConfig.aspectRatio` | `string` | 否 | 从对应模型的比例表中选择 | | `generationConfig.imageConfig.imageSize` | `string` | 否 | Flash:`512`、`1K`、`2K`、`4K`;Pro:`1K`、`2K`、`4K`;`K` 必须大写 | | `safetySettings` | `array` | 否 | 安全策略;同一类别不要重复传入 | | `inlineData.mimeType` | `string` | 图片输入时是 | `image/png`、`image/jpeg`、`image/webp`、`image/heic`、`image/heif` | | `inlineData.data` | `string` | 图片输入时是 | 裸 Base64,不带 `data:image/...` 前缀 | ### `gemini-3.1-flash-image` 比例与像素 | `aspectRatio` | `512` | `1K` | `2K` | `4K` | |---|---:|---:|---:|---:| | `1:1` | 512×512 | 1024×1024 | 2048×2048 | 4096×4096 | | `1:4` | 256×1024 | 512×2048 | 1024×4096 | 2048×8192 | | `1:8` | 192×1536 | 384×3072 | 768×6144 | 1536×12288 | | `2:3` | 424×632 | 848×1264 | 1696×2528 | 3392×5056 | | `3:2` | 632×424 | 1264×848 | 2528×1696 | 5056×3392 | | `3:4` | 448×600 | 896×1200 | 1792×2400 | 3584×4800 | | `4:1` | 1024×256 | 2048×512 | 4096×1024 | 8192×2048 | | `4:3` | 600×448 | 1200×896 | 2400×1792 | 4800×3584 | | `4:5` | 464×576 | 928×1152 | 1856×2304 | 3712×4608 | | `5:4` | 576×464 | 1152×928 | 2304×1856 | 4608×3712 | | `8:1` | 1536×192 | 3072×384 | 6144×768 | 12288×1536 | | `9:16` | 384×688 | 768×1376 | 1536×2752 | 3072×5504 | | `16:9` | 688×384 | 1376×768 | 2752×1536 | 5504×3072 | | `21:9` | 792×168 | 1584×672 | 3168×1344 | 6336×2688 | ### `gemini-3-pro-image-preview` 比例与像素 | `aspectRatio` | `1K` | `2K` | `4K` | |---|---:|---:|---:| | `1:1` | 1024×1024 | 2048×2048 | 4096×4096 | | `2:3` | 848×1264 | 1696×2528 | 3392×5056 | | `3:2` | 1264×848 | 2528×1696 | 5056×3392 | | `3:4` | 896×1200 | 1792×2400 | 3584×4800 | | `4:3` | 1200×896 | 2400×1792 | 4800×3584 | | `4:5` | 928×1152 | 1856×2304 | 3712×4608 | | `5:4` | 1152×928 | 2304×1856 | 4608×3712 | | `9:16` | 768×1376 | 1536×2752 | 3072×5504 | | `16:9` | 1376×768 | 2752×1536 | 5504×3072 | | `21:9` | 1584×672 | 3168×1344 | 6336×2688 | ### 参考图能力与请求大小 | 模型 | 参考图总数 | 物体保持 | 人物一致性 | 风格参考 | |---|---:|---:|---:|---:| | `gemini-3.1-flash-image` | 最多 14 张 | 最多 10 个 | 最多 4 个角色 | 随参考图一并描述 | | `gemini-3-pro-image-preview` | 最多 14 张 | 最多 6 个 | 最多 5 个角色 | 最多 3 张 | 内联图片、提示词和系统指令合计控制在 `20 MB` 以内。 ### 文生图请求 ```json { "contents": [ { "parts": [ {"text": "生成一张雨后的未来城市,霓虹倒影,电影感构图"} ] } ], "generationConfig": { "responseModalities": ["IMAGE"], "imageConfig": { "aspectRatio": "16:9", "imageSize": "2K" } } } ``` ### 参考图请求 ```json { "contents": [ { "parts": [ {"text": "保留主体,把背景改成夜景"}, { "inlineData": { "mimeType": "image/png", "data": "BASE64_IMAGE_DATA" } } ] } ], "generationConfig": { "responseModalities": ["IMAGE"], "imageConfig": { "aspectRatio": "1:1", "imageSize": "1K" } } } ``` 多轮连续编辑时,下一轮继续传入上一轮图片和新的文字要求。每轮明确说明“保留什么、只修改什么”。 ### 响应结构 ```json { "candidates": [ { "content": { "role": "model", "parts": [ { "inlineData": { "mimeType": "image/png", "data": "BASE64_IMAGE_DATA" } } ] }, "finishReason": "STOP" } ], "modelVersion": "gemini-3.1-flash-image" } ``` 遍历所有 `candidates[]` 和 `parts[]`;每遇到一个 `inlineData` 就按其 `mimeType` 选择扩展名,并将 `data` 解码保存。 ## 11. `gpt-6-astra` ### 接口与字段 模型 ID 固定使用 `gpt-6-astra`。Base URL 为 `https://www.yunqiai.chat`,鉴权为 `Authorization: Bearer YOUR_API_KEY`。 | 场景 | 接口 | 参数 | |---|---|---| | 对话、工具调用、结构化输出、图片与 PDF | `POST /v1/responses` | `input`、`reasoning.effort`、`max_output_tokens` | | 文本对话 | `POST /v1/chat/completions` | `messages`、`reasoning_effort`、`max_completion_tokens` | | 工具定义与回传 | Responses | `tools`、`tool_choice`、`function_call_output` | | JSON Schema | Responses | `text.format`,包含 `type=json_schema`、`name`、`schema`、`strict` | | 流式返回 | Responses | `stream=true`;以 `response.completed` 为完成事件 | `reasoning.effort` 支持 `low`、`medium`、`high`、`xhigh`、`max`,不要发送 `none` 或 `minimal`。不发送 `temperature`、`top_p`、`top_logprobs`;Chat Completions 也不发送 `logprobs`。输出预算包含推理与最终回答,预算耗尽时须检查 `status=incomplete`,不能当作完整回答。 工具调用使用 Responses,不把 Chat Completions 的 `tool_calls` 字段套入 Responses。模型返回 `function_call` 后由客户程序执行工具,并用同一个 `call_id` 回传 `function_call_output`。无状态续接时保留上一轮完整 `output[]`,不要只保留可见文本。完整协议见第 6、13、14 节。 ### 请求示例 ```python import os import requests response = requests.post( "https://www.yunqiai.chat/v1/responses", headers={"Authorization": "Bearer " + os.environ["YUNQIAI_API_KEY"]}, json={ "model": "gpt-6-astra", "input": "用三句话解释量子计算。", "reasoning": {"effort": "low"}, "max_output_tokens": 1024, }, timeout=(15, 180), ) response.encoding = "utf-8" if not response.ok: request_id = response.headers.get("x-request-id") or response.headers.get("request-id") raise RuntimeError(f"HTTP {response.status_code}; request_id={request_id}; body={response.text[:2000]}") result = response.json() if result.get("error") or result.get("status") != "completed": raise RuntimeError(str(result.get("error") or result.get("incomplete_details") or result.get("status"))) for item in result.get("output", []): if item.get("type") == "message": for part in item.get("content", []): if part.get("type") == "output_text": print(part["text"]) ``` 图片输入使用 `input_image.image_url`,可传图片 Data URL。PDF 使用 `input_file`,同时提供 `filename` 和 `file_data=data:application/pdf;base64,...`。具体结构见第 6、9 节。音频与视频不作为此模型的直接输入,PDF 与图片输入也不使用 Images 生成端点。 ### 客户端配置 Codex CLI / VS Code 的配置沿用第 16 节的 provider 与鉴权结构,将 `model` 设为 `gpt-6-astra`,`wire_api` 保持 `responses`,`model_reasoning_effort` 使用上面的合法档位。CC Switch 管理 Codex 时填写同样的模型 ID 与 `https://www.yunqiai.chat/v1`。不要将本模型填入仅支持 Anthropic Messages 的 Claude Code 原生配置。 参考:[官方模型参数](https://developers.openai.com/api/docs/models/gpt-6-astra)、[官方 API 参数约定](https://developers.openai.com/api/docs/guides/latest-model)。 ## 12. `gpt-image-2` 支持文本生图、参考图编辑、图生图、多图合成和蒙版局部重绘。 ### 端点 ```text POST /v1/images/generations POST /v1/images/edits ``` 文生图请求使用 `application/json`;参考图编辑请求使用 `multipart/form-data`。 ### 文生图参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `model` | `string` | 是 | 固定为 `gpt-image-2` | | `prompt` | `string` | 是 | `1–32,000` 字符 | | `size` | `string` | 否 | `auto` 或符合约束的 `WIDTHxHEIGHT`;无需另传 `resolution` | | `quality` | `string` | 否 | `low`、`medium`、`high`、`auto`;默认 `auto` | | `n` | `integer` | 否 | `1–10`;生成数量会直接影响费用 | | `output_format` | `string` | 否 | `png`、`jpeg`、`webp`;默认 `png` | | `output_compression` | `integer` | 否 | `0–100`;仅 JPEG / WebP,默认 `100` | | `background` | `string` | 否 | `auto` 或 `opaque`;默认 `auto` | | `moderation` | `string` | 否 | `auto` 或 `low`;默认 `auto` | | `stream` | `boolean` | 否 | 是否流式返回 | | `partial_images` | `integer` | 否 | `0–3`;仅流式请求,默认 `0` | | `user` | `string` | 否 | 终端用户标识,用于滥用监测 | ### 文生图请求 ```bash curl --request POST \ --url https://www.yunqiai.chat/v1/images/generations \ --header "Authorization: Bearer YOUR_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "gpt-image-2", "prompt": "雨后的未来城市,霓虹倒影,电影感构图", "size": "1536x1024", "quality": "medium", "output_format": "png", "n": 1 }' ``` ### 参考图编辑参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `model` | `string` | 是 | 固定为 `gpt-image-2` | | `image` / `image[]` | `file` 或 `file[]` | 是 | 上传 `1–16` 张 PNG、JPG 或 WebP 参考图;每张小于 `50 MB` | | `mask` | `file` | 否 | PNG,小于 `4 MB`;与第一张参考图尺寸一致并含 Alpha 通道 | | `prompt` | `string` | 是 | `1–32,000` 字符;说明需要保留和修改的画面内容 | 其余可选参数与上方文生图参数相同。 ### 自定义尺寸规则 | 约束 | 范围 | 说明 | |---|---:|---| | 宽高步进 | 16 px | 宽和高都必须是 16 的倍数 | | 输出比例 | 1:3–3:1 | 长边与短边之比不超过 3:1 | | 最长边 | ≤ 3840 px | 横图和竖图使用同一限制 | | 总像素 | 655,360–8,294,400 | 在范围内可自由组合宽高 | 常用尺寸: | `size` | 比例 | 场景 | |---|---:|---| | `1024x1024` | 1:1 | 方形图片 | | `1536x1024` | 3:2 | 横向图片 | | `1024x1536` | 2:3 | 竖向图片 | | `2048x2048` | 1:1 | 2K 方形图片 | | `2048x1152` | 16:9 | 2K 横图 | | `3840x2160` | 16:9 | 最大横图 | | `2160x3840` | 9:16 | 最大竖图 | ### 质量 | `quality` | 特点 | 场景 | |---|---|---| | `auto` | 自动选择 | 不想手动指定质量时 | | `low` | 速度最快、费用最低、细节较少 | 快速草稿、构图预览 | | `medium` | 速度、费用与细节较均衡 | 日常生图与常规编辑 | | `high` | 细节最丰富、生成更慢、费用较高 | 复杂场景、精细纹理与大量细节 | ### 多图与蒙版 - 多张参考图重复填写 `image[]`。 - 蒙版只作用于第一张参考图。 - 主体保持自动按高保真方式处理,无需额外参数。 - 蒙版用于指定主要修改区域;提示词同时写明位置、修改内容和需要保留的主体。 ```bash curl --request POST \ --url https://www.yunqiai.chat/v1/images/edits \ --header "Authorization: Bearer YOUR_API_KEY" \ --form "model=gpt-image-2" \ --form "image[]=@./scene.png" \ --form "image[]=@./product.png" \ --form "mask=@./mask.png" \ --form "prompt=把第二张图中的商品放到第一张场景中,保持商品外观不变" \ --form "size=1536x1024" \ --form "quality=medium" \ --form "n=1" ``` ### 响应结构 ```json { "created": 1785987000, "data": [ {"b64_json": "BASE64_IMAGE_DATA"} ], "output_format": "png", "quality": "medium", "size": "1536x1024" } ``` 当 `n` 大于 `1` 时,客户端必须遍历完整 `data[]`。不要只读取 `data[0]`。 ### Python 解码全部图片 ```python import base64 import requests for index, item in enumerate(response.data, start=1): b64_json = getattr(item, "b64_json", None) url = getattr(item, "url", None) if b64_json: image_bytes = base64.b64decode(b64_json) elif url: download = requests.get(url, timeout=(10, 300)) download.raise_for_status() image_bytes = download.content else: raise ValueError("image item contains neither b64_json nor url") with open(f"result-{index}.png", "wb") as file: file.write(image_bytes) ``` ## 12A. `gpt-image-2.5-flare` / `gpt-image-2.5-sunburst` 模型 ID 使用完整字符串,不省略后缀,不自动切换型号。本节参数适用于这两个型号。 ### 接口与鉴权 | 操作 | 方法与路径 | 请求体 | |---|---|---| | 文生图 | `POST /v1/images/generations` | JSON | | 参考图与蒙版编辑 | `POST /v1/images/edits?api-version=2025-04-01-preview` | multipart/form-data | 域名为 `https://www.yunqiai.chat`,鉴权头为 `Authorization: Bearer YOUR_API_KEY`。编辑时保留查询参数,multipart 的 boundary 由 HTTP 客户端生成。API 密钥需包含对应生图模型的权限。 ### 参数 | 字段 | 类型 | 必填 / 默认 | 说明 | |---|---|---|---| | `model` | string | 必填 | `gpt-image-2.5-flare` 或 `gpt-image-2.5-sunburst` | | `prompt` | string | 必填 | 生成或编辑指令,多图时按上传顺序说明各图作用 | | `size` | string | `auto` | `auto` 或 `WIDTHxHEIGHT`,如 `1024x1024`、`1536x1024`、`1024x1536`、`1536x864` | | `quality` | string | `auto` | `low`、`medium`、`high`、`xhigh`、`max`、`auto` | | `n` | integer | `1` | 请求图片数量;可传 `2`,返回时遍历完整 `data[]` | | `image` | file | 单图编辑必填 | PNG、JPEG、WebP 图片文件,不是文件路径字符串 | | `image[]` | file[] | 多图编辑必填 | 重复同名 multipart 字段,例如两张参考图各占一个 `image[]` | | `mask` | file | 编辑可选 | 带 Alpha 通道的 PNG,与第一张参考图宽高一致,透明区域为编辑区域 | | `output_format` | string | `png` | `png`、`jpeg`;不使用 `webp` 输出 | | `output_compression` | integer | 可选 | `0–100`,仅 JPEG 输出使用 | | `background` | string | `auto` | `auto`、`opaque`、`transparent`;透明背景使用 PNG | | `moderation` | string | `auto` | `auto`、`low`;不豁免内容政策 | | `response_format` | 不发送 | — | 图片直接由响应 `data[].b64_json` 返回 | | `input_fidelity` | 不发送 | — | 这两个型号不使用该字段 | 指定尺寸使用小写 `x` 分隔,宽高按 16 对齐,长短边比例不超过 3:1。`auto` 由服务决定尺寸;显示和排版读取实际解码后的宽高,不假定为正方形。不要额外发送 `resolution`。 `quality` 控制生成档位,`size` 控制像素尺寸。固定档位需显式传入;`auto` 不保证每次选择同一档位。返回用量读取 `usage`,费用以控制台账单为准,不从图片文件体积推算。 ### 参考图与蒙版 参考图通过独立文件字段传入,不把 Base64 拼进 `prompt`。多图请求使用重复 `image[]`,提示词中的图 1、图 2 按上传顺序对应。蒙版作用于第一张参考图,其他参考图提供辅助内容。生成式编辑可能重新绘制画面,不保证蒙版外逐像素不变。 ```bash curl --fail-with-body --max-time 300 \ 'https://www.yunqiai.chat/v1/images/edits?api-version=2025-04-01-preview' \ -H "Authorization: Bearer $YUNQIAI_API_KEY" \ -F "model=$YUNQIAI_IMAGE_MODEL" \ -F 'prompt=保留图1的构图,将图2的杯子放在画面中间' \ -F 'image[]=@reference.png;type=image/png' \ -F 'image[]=@product.png;type=image/png' \ -F 'size=1024x1024' -F 'quality=low' -F 'n=1' \ -o result.json ``` 局部编辑额外添加 `-F 'mask=@mask.png;type=image/png'`。上述命令适用于 Bash / WSL;PowerShell 使用 `curl.exe`,环境变量写为 `$env:YUNQIAI_API_KEY`,换行使用反引号。 ### 完整 Python 示例 依赖:`python -m pip install requests pillow`。设置 `YUNQIAI_API_KEY`、`YUNQIAI_IMAGE_MODEL`;`YUNQIAI_IMAGE_MODE` 可为 `generate`、`edit`、`multi`。编辑示例读取当前目录的 `reference.png`,多图另外读取 `product.png`。 ```python import base64 import io import os from contextlib import ExitStack from pathlib import Path import requests from PIL import Image base = "https://www.yunqiai.chat" model = os.environ["YUNQIAI_IMAGE_MODEL"] if model not in {"gpt-image-2.5-flare", "gpt-image-2.5-sunburst"}: raise ValueError("Use a complete GPT Image 2.5 model ID") headers = {"Authorization": "Bearer " + os.environ["YUNQIAI_API_KEY"]} mode = os.environ.get("YUNQIAI_IMAGE_MODE", "generate") payload = { "model": model, "prompt": "A blue ceramic mug with a yellow square on a white table", "size": "1024x1024", "quality": "low", "n": 1, } if mode == "generate": response = requests.post(base + "/v1/images/generations", headers=headers, json=payload, timeout=(15, 300)) elif mode in {"edit", "multi"}: paths = ["reference.png"] if mode == "edit" else ["reference.png", "product.png"] payload["prompt"] = "Keep the blue rectangle and YQ-A1 from the first image; change its red circle to green." if mode == "multi": payload["prompt"] += " Add the blue mug with its yellow square from the second image at the center." with ExitStack() as stack: files = [("image" if mode == "edit" else "image[]", (Path(path).name, stack.enter_context(open(path, "rb")), "image/png")) for path in paths] response = requests.post( base + "/v1/images/edits?api-version=2025-04-01-preview", headers=headers, data=payload, files=files, timeout=(15, 300)) else: raise ValueError("mode must be generate, edit or multi") # Retain the error body before raising; do not log request headers. response.encoding = "utf-8" if not response.ok: raise RuntimeError(f"HTTP {response.status_code}: {response.text[:1000]}") result = response.json() if result.get("error") or not result.get("data"): raise RuntimeError("No generated images in response") for index, item in enumerate(result["data"]): raw = base64.b64decode(item["b64_json"], validate=True) image = Image.open(io.BytesIO(raw)) image.load() path = Path(f"result-{index}.{image.format.lower()}") path.write_bytes(raw) print(path, image.size, result.get("quality"), result.get("usage")) ``` ### 响应与错误 成功响应的图片位于 `data[].b64_json`,先解码再识别文件格式,不根据文件名猜测。同步响应同时提供 `size`、`quality`、`output_format` 和 `usage`。自动尺寸和自动质量以返回结果为准。 检查 HTTP 状态、`error` 对象、图片数组、实际数量与解码结果。`usage.output_tokens_details.image_tokens` 为图像输出用量。输入格式支持 WebP 不代表输出也支持 WebP,不能把两个字段的枚举混为一谈。 连接超时或读取超时且尚未收到完整结果时,生成结果未知,不自动重发。此接口不使用 MJ 任务查询路径。错误信息记录 `request_id` 等排查信息,不记录请求鉴权头。 ## 12B. MJ / 悠船:图片生成与编辑 ### 接入约定 Base URL:`https://www.yunqiai.chat`。本章密钥从 `YOUCHUAN_API_KEY` 环境变量读取,值为开通 MJ 权限的 YunQi API 密钥,不是上游网站的机构密钥。生成与编辑发送 JSON。 ```http Authorization: Bearer Content-Type: application/json Accept: application/json ``` 不使用 `x-api-key`、`x-goog-api-key`、`x-youchuan-secret` 或 `x-youchuan-app`。环境变量名称只供客户端读取,不作为 HTTP 请求头名称。不要将密钥写进 URL、提示词、回调地址或参考图链接。 密钥分组需要包含相应 MJ 图片权限。MJ 文生图请求的 `model` 可使用 `mj_imagine` 或 `youchuan-image`,两个名称都可调用同一 MJ 接口;提示词字段为 `text`。版本由 `text` 末尾的 `--v` 或 `--niji` 指定,不写进 `model`。`/v1/tob/subscribe` 不用于生成;提交使用 `/v1/tob/diffusion`。 ### 文生图请求 ```json { "model": "mj_imagine", "text": "现代东方建筑,清晨薄雾,电影级建筑摄影,无文字 --v 8.1 --fast --hd --q 4 --raw --ar 16:9" } ``` POST `/v1/tob/diffusion`。提示词字段为 `text`,不是 `prompt`。只使用 `model`、`text` 与可选的 `callback` 请求体字段;`size`、`n`、`quality`、`aspectRatio`、`response_format` 不是此接口的生成参数,不要发送。版本、比例、质量等 MJ 参数均放在 `text` 末尾,参数前留空格,末尾不加逗号或句号。 ### 版本与质量 以下为 YunQi MJ 接口的版本选择与参数写法。版本使用 `--v` 或 `--niji` 二选一,并显式指定;未指定时该接口默认使用 v7。不要用 Midjourney 网站的默认版本替代本接口约定。 | 版本参数 | `--q` 可用值 | 说明 | |---|---|---| | `--v 5.1`、`--v 5.2`、`--v 6` | `0.25`、`0.5`、`1` | 默认 1 | | `--v 6.1` | `0.5`、`1`、`2` | 默认 1 | | `--v 7` | `1`、`2`、`4` | 默认 1 | | `--v 8`、`--v 8.1` | `1`、`4` | 默认 1 | | `--v 8.2` | `1`、`2`、`3`、`4` | 默认 1 | | `--niji 5`、`--niji 6` | `0.25`、`0.5`、`1` | 默认 1 | | `--niji 7` | 不发送 | 不支持 `--q` | `--q` 是 MJ 的生成质量参数,不是 OpenAI Images 的 `quality=low/medium/high`,也不是像素尺寸。它不承诺画面得分、手指正确率或逐档可见提升;输出尺寸读取实际图片。请求受理、响应回显参数或相同费用均不能独立证明底层的计算档位。 ### 参数字段 这些是 `text` 内的参数,不是独立 JSON 字段。“通用”不表示每个历史版本都支持全部参数;带版本限制的参数需同时满足下表与组合约束。 | 参数 | 值与默认值 | 用途与约束 | |---|---|---| | `--ar W:H` | 正整数比例,例如 `16:9`、`1:1`、`9:16`、`4:3`、`2:3`;默认 1:1 | 宽高比,不是固定像素宽高;V8.1/V8.2 的 SD 最大 14:1、HD 最大 4:1(含纵向对称比例) | | `--raw` | 开关 | 减少默认风格化;不同历史版本的 Raw 写法与兼容性不同 | | `--chaos` | 0–100;默认 0 | 同次生成的变化程度 | | `--seed` | 0–4294967295 的整数 | 随机种子;不保证跨版本、模式或服务更新后逐像素复现 | | `--stylize` / `--s` | 0–1000;默认 100 | 风格化强度 | | `--weird` / `--w` | 0–3000;默认 0 | 非常规造型程度 | | `--no` | 要排除的内容 | 负向描述,不保证绝对排除 | | `--tile` | 开关 | 无缝平铺;不与 v7 draft 组合 | | `--fast` | 开关 | 快速模式;不同时发送 `--turbo` | | `--turbo` | 开关 | 极速模式;本接口 v7、v8.1、v8.2 可用,费用以控制台结算为准 | | `--draft` | 开关 | 草图模式;本章示例使用 v7,后续增强使用 `/v1/tob/enhance` | | `--hd` | 开关;仅 v8.1/v8.2 | 原生 2K 档位;并非固定 2048×2048,像素尺寸随比例变化 | | `--exp` | 0–100;默认 0;v7/v8.1/v8.2 | 实验性美学强度 | | `--q` | 按上表版本选择 | 不套用其他版本的枚举 | ### 组合约束与示例 - `--fast` 与 `--turbo` 不同时使用;不要同时发送多个相冲突的版本或比例。 - niji 7 不发送 `--q`。 - v7 的 `--draft` 不与 `--tile`、`--oref` 组合。draft 请求不附加 `--q 4`,不据此承诺草图的质量档位。 - v7 的 `--oref` 不与 `--q 4` 组合。 - `--hd` 仅用于 v8.1/v8.2。不要向旧版本追加 HD 参数。 - 不使用独立 JSON 的 `size`、`n`、`response_format` 控制 MJ;结果数量以 `urls` 为准,不假定永远是四张。 以下只是参数组合语法,模型和配置由调用方选择,不自动替换版本、质量或模式。 | 组合 | text 末尾 | |---|---| | v7 | `--v 7 --fast --q 1 --s 100 --ar 16:9` | | v8.1 HD | `--v 8.1 --fast --hd --q 4 --raw --ar 16:9` | | v7 draft | `--v 7 --fast --draft --ar 1:1` | | niji 7 | `--niji 7 --fast --s 500 --ar 2:3` | ### 计费规则 MJ 按任务计费,不按返回图片数量直接相乘。操作类型和生成模式会改变费用;一次提交返回多张图片,仍按该任务规则计算,二次操作创建的新任务另行计费。两个可用模型名均按其控制台定价及以下适用倍率计算。 计算方式:**任务费用 = 基础任务价格 × 操作倍率 × Turbo 倍率 × Draft 倍率 × HD 倍率**。基础价格、币种和账户分组折扣以控制台为准;满足多个条件时相乘,不择一取最高值。 | 操作 | 操作倍率 | |---|---| | diffusion、reroll | 1× | | variation、inpaint、remix、edit、upload-paint、retexture | 1.5× | | upscale,type=0 | 1.5× | | upscale,type=1 | 2× | | pan、outpaint、remove-background、enhance | 1× | | 参数条件 | 附加倍率 | 适用范围 | |---|---|---| | Turbo 生效 | 2× | 使用 Turbo 模式的任务;不与 --fast 同时发送 | | --draft 生效且版本为 v7 | 0.5× | 仅 diffusion | | --hd 生效且版本为 v8.1 / v8.2 | 1.5× | 仅 diffusion | | 不满足以上条件 | 1× | 不附加相应倍率 | 例如,v7 普通生成是基础价格的 1×,v7 draft 是 0.5×,v8.1/v8.2 HD 是 1.5×,v8.1/v8.2 HD 加 Turbo 是 3×;前提是该参数组合被接口支持。倍率规则不意味着所有版本与参数都能任意组合。v5 专用 upscale type=2/3 的价格单独查看控制台,不套用 type=0/1。 本规则没有为 `--q` 或 `--ar` 单独设置附加倍率;不能将 q4 直接解读为四倍费用,也不能由倍率推断生成质量。`cost.fastCost`、`cost.feeCost` 是服务返回的费用信息,不直接当作客户币种金额。估算使用以上规则,实际扣费以控制台账单为准;失败任务的结算状态也需查账单,不能仅凭 HTTP 状态推断。 ### 任务生命周期 1. POST 提交后保存响应 `id`,作为查询与后续操作的 `jobId`。 2. GET `/v1/tob/job/{jobId}` 查询原任务。`status=1` 为执行中,`2` 为成功,`3` 为失败。 3. 成功时按原始 `urls[]` 下标保存媒体。`audits[]` 相同位置的非空内容为审核限制原因;空 URL 不表示可下载结果。 4. 二次操作提交后返回新的 `id`,继续查询新任务。不可把源任务完成状态当作二次操作的完成状态。 `imageNo` 从 0 开始;只能引用该任务实际返回的图片位置。过滤无效项时保留原始下标,不重新编号。 ```json { "id": "TASK_ID", "status": 1, "urls": [], "audits": [], "comment": "执行中", "cost": {"fastCost": 1, "feeCost": 60}, "request_id": "REQUEST_ID" } ``` HTTP 200 只表示本次 HTTP 请求完成,仍须判断任务状态。失败保存并输出 `comment`、`cost` 和完整任务响应(包括存在时的 `reason`、`error_code`、`error_source`、`request_id`),仅写入受控业务日志,不公开鉴权信息或签名 URL。未知状态应保留原响应,不显示为成功。`cost` 为服务返回的任务费用信息,客户结算以控制台账单为准。 ### 图片接口 响应示例中的 cost 数值仅用于展示结构,不是价格承诺。版本和参数语法参见[悠船模型参数](https://tob.youchuan.cn/docs/guides/parameters/models);本章接入域名和鉴权以 YunQi 约定为准,不复制上游机构鉴权头。 下表字段均为请求体字段,`?` 表示可选。`jobId` 为 string,`imageNo`、`type`、`direction`、`mode` 为 integer,`scale` 为 number。二次操作的源任务必须已完成。 | POST 路径 | 用途 | 字段与取值 | |---|---|---| | `/v1/tob/diffusion` | 文生图 / URL 参考图 | `model` 为 `mj_imagine` 或 `youchuan-image`;`text` 非空字符串;版本、比例等参数写在 text 中 | | `/v1/tob/variation` | 变化 | `jobId`、`imageNo`、`type`;0 轻微,1 强烈;`remixPrompt?` | | `/v1/tob/upscale` | 高清 | `jobId`、`imageNo`、`type`;0 标准,1 创意,2 为 v5_2x,3 为 v5_4x,类型需与源版本匹配 | | `/v1/tob/reroll` | 重新生成 | `jobId` | | `/v1/tob/pan` | 定向延展 | `jobId`、`imageNo`、`direction`、`scale`、`remixPrompt?`;direction:0 下、1 右、2 上、3 左;scale:1.1–3 | | `/v1/tob/outpaint` | 周边扩图 | `jobId`、`imageNo`、`scale`、`remixPrompt?`;scale:1.1–2 | | `/v1/tob/inpaint` | 区域重绘 | `jobId`、`imageNo`、`mask`、`remixPrompt?` | | `/v1/tob/remix` | 提示词重塑 | `jobId`、`imageNo`、`remixPrompt`、`mode?`;0 强变化(默认),1 弱变化 | | `/v1/tob/edit` | 画布编辑 | `jobId`、`imageNo`、`canvas`、`imgPos`、`remixPrompt`、`mask?` | | `/v1/tob/upload-paint` | URL 图片画布编辑 | `imgUrl`、`mask`、`canvas`、`imgPos`、`remixPrompt` | | `/v1/tob/retexture` | 材质 / 风格转绘 | `imgUrl`、`remixPrompt`;提示词版本不低于 6.1 | | `/v1/tob/remove-background` | 移除背景 | `imgUrl`;输入 PNG、JPEG,输出不作为二次生成的源任务 | | `/v1/tob/enhance` | 草稿增强 | `jobId`、`imageNo`;仅使用 `--draft` 生成的任务 | `text` 为 1–8192 字符;v8 系列正文长度不超过 1024 字符,不含图片引用和附加参数。版本写在提示词中,例如 `--v 7`,比例写为 `--ar 3:2`。不要把 MJ 版本当成 GPT Images 的 `model` 字段。 高级编辑与转绘结果只支持继续执行高清。原始任务所用版本会影响可用的二次操作;不得把 v5 的放大类型应用到其他版本。`imgUrl` 必须是服务端可直接读取的图片 URL,不是网页、本机路径或需要登录才能下载的链接。 ### 蒙版与画布对象 | 字段 | 类型 | 语义 | |---|---|---| | `canvas.width` / `canvas.height` | integer | 编辑画布宽高,像素;生成结果可能按模型规格重新缩放 | | `imgPos.width` / `imgPos.height` | integer | 原图放置到新画布后的宽高,像素 | | `imgPos.x` / `imgPos.y` | integer | 原图在新画布中的左上角坐标 | | `mask.areas` | object[] | 多个重绘区域,每项含原图 width、height 与 points | | `mask.areas[].points` | number[] | 多边形顶点按 x、y 成对排列,原点在左上角 | | `mask.url` | string | 与原图同尺寸的黑白蒙版 URL,白色区域重绘 | `mask.areas` 与 `mask.url` 二选一。画布尺寸用于构图与坐标变换,不保证输出图片与 canvas 宽高逐像素相等。这里的黑白蒙版与 GPT Images 的 Alpha 蒙版不同,不要直接混用。生成式编辑不保证未选区域逐像素不变。 ### 各操作请求体 将以下 JSON 对象中某个操作的值作为该 POST 接口的请求体,而不是把整个对象一起提交。`SOURCE_TASK_ID`、`DRAFT_TASK_ID`、`IMAGE_URL` 均需替换为自己的实际值。画布和蒙版坐标示例以 1024×1024 原图为基础,输入尺寸改变时同时调整。 ```json { "diffusion": {"model": "mj_imagine", "text": "A blue ceramic mug with a yellow square on a white table --v 7"}, "variation": {"jobId": "SOURCE_TASK_ID", "imageNo": 0, "type": 0}, "upscale": {"jobId": "SOURCE_TASK_ID", "imageNo": 0, "type": 0}, "reroll": {"jobId": "SOURCE_TASK_ID"}, "pan": {"jobId": "SOURCE_TASK_ID", "imageNo": 0, "direction": 1, "scale": 1.5, "remixPrompt": "Extend the white table to the right"}, "outpaint": {"jobId": "SOURCE_TASK_ID", "imageNo": 0, "scale": 1.5}, "inpaint": { "jobId": "SOURCE_TASK_ID", "imageNo": 0, "mask": {"areas": [{"width": 1024, "height": 1024, "points": [270, 260, 850, 260, 850, 930, 270, 930]}]}, "remixPrompt": "A bright red ceramic mug with a white star, white table, studio photograph" }, "remix": {"jobId": "SOURCE_TASK_ID", "imageNo": 0, "remixPrompt": "A blue mug on a red table --v 7", "mode": 0}, "edit": { "jobId": "SOURCE_TASK_ID", "imageNo": 0, "canvas": {"width": 1280, "height": 1024}, "imgPos": {"width": 1024, "height": 1024, "x": 128, "y": 0}, "remixPrompt": "Extend the white tabletop around the blue mug" }, "upload-paint": { "imgUrl": "IMAGE_URL", "mask": {"areas": [{"width": 1024, "height": 1024, "points": [270, 260, 850, 260, 850, 930, 270, 930]}]}, "canvas": {"width": 1280, "height": 1024}, "imgPos": {"width": 1024, "height": 1024, "x": 128, "y": 0}, "remixPrompt": "A bright red ceramic mug with a white star, white table, studio photograph --v 6.1" }, "retexture": {"imgUrl": "IMAGE_URL", "remixPrompt": "Watercolor painting of a blue mug --v 6.1"}, "remove-background": {"imgUrl": "IMAGE_URL"}, "enhance": {"jobId": "DRAFT_TASK_ID", "imageNo": 0} } ``` ### 参考图、风格与主体引用 图片 URL 按顺序放在 `text` 开头,再写提示词。这是 MJ 协议定义的图片引用方式,不使用 GPT Images 的 multipart `image[]`,也不接受任意裸 Base64 代替图片 URL。 ```json {"model":"mj_imagine","text":"IMAGE_URL_1 IMAGE_URL_2 A blue mug on a red table --iw 1 --ar 3:2 --v 7"} ``` 四种引用方式有不同语义,不能相互替换: | 引用方式 | text 写法 | 适用范围与权重 | |---|---|---| | 普通垫图 | `IMAGE_URL_1 IMAGE_URL_2 描述 --iw 1 --v 7` | 影响内容、构图和色彩;可放多张公开图片,`--iw` 对普通垫图整体生效,不是每张图的索引 | | 风格参考 | `描述 --sref STYLE_URL_1 STYLE_URL_2 --sw 100 --sv 6 --v 7` | 参考风格,不保证人物或产品身份;`--sw` 0–1000,默认 100;`--sv` 选择风格参考算法,不是主模型版本 | | 角色参考 | `描述 --cref PERSON_URL --cw 100 --v 6.1` | v6、v6.1、niji 6;`--cw` 0–100,默认 100;低权重偏向脸部,高权重更多保留发型、服装 | | 万物引用 | `描述 --oref OBJECT_URL --ow 200 --v 7` | 仅 v7;一次一个引用图片;`--ow` 1–1000,默认 100;不与 draft、q4 组合 | 普通垫图的 `--iw`:v6/v6.1/v7/v8.1/v8.2 为 0–3,niji 7 为 0–2;旧版本不要套用这些范围。v7 的 `--sv` 可为 1–6;v6.1/niji 6 为 1–4;v8.1/v8.2 只用 6。不要把 `--sv 6` 写成 `--v 6`。多张普通垫图或风格参考均以空格分隔 URL;角色/万物引用并非可编辑图层或 Photoshop 式像素锁定。 引用图片用 PNG、JPEG、WebP 或 GIF 的 HTTP(S) 直链,不能是 HTML 网页、本机路径、`file://`、裸 Base64 或登录后才可读的资源。图片必须是调用方有权使用的内容。优先使用稳定的公开直链;如果使用签名 URL,完整保留签名参数,并确保在任务读取图片期间有效。客户业务服务接收外部 URL 时应限制协议、域名和内网访问,避免任意 URL 下载造成 SSRF。 ### 可恢复的提交与查询示例 依赖:`python -m pip install requests pillow`。将下列代码保存为项目中的 `mj.py`,密钥从 `YOUCHUAN_API_KEY` 读取。请求体放入 UTF-8 JSON 文件,例如 `request.json`。该环境变量装入的是 YunQi 密钥;通用示例的 `YUNQIAI_API_KEY` 不会被本程序自动读取。 ```bash python mj.py submit diffusion request.json python mj.py resume TASK_ID ``` `submit` 会保存 `mj-task.json` 后开始查询;`resume` 只查询已有任务,不新建收费任务。每个任务单独保存到 `mj-output/TASK_ID/`,避免后续操作覆盖原图。 ```python import argparse import io import json import os import re import time from datetime import datetime, timezone from email.utils import parsedate_to_datetime from pathlib import Path import requests from PIL import Image BASE = "https://www.yunqiai.chat" MODEL = "mj_imagine" MODELS = {"mj_imagine", "youchuan-image"} HEADERS = { "Authorization": "Bearer " + os.environ["YOUCHUAN_API_KEY"], "Content-Type": "application/json", "Accept": "application/json", } OPERATIONS = { "diffusion", "variation", "upscale", "reroll", "pan", "outpaint", "inpaint", "remix", "edit", "upload-paint", "retexture", "remove-background", "enhance", } def persist(path, value): path.write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8") def parse(response): response.encoding = "utf-8" try: data = response.json() except ValueError: data = {"body_excerpt": response.text[:1000]} if not response.ok or not isinstance(data, dict) or data.get("error"): raise RuntimeError(f"HTTP {response.status_code}: {json.dumps(data, ensure_ascii=False)}") return data def delay_for(response): value = response.headers.get("Retry-After", "5") try: return max(1, float(value)) except ValueError: try: return max(1, (parsedate_to_datetime(value) - datetime.now(timezone.utc)).total_seconds()) except (ValueError, TypeError, OverflowError): return 5 def poll(job_id, folder, wait_seconds=360): deadline = time.monotonic() + wait_seconds while time.monotonic() < deadline: remaining = deadline - time.monotonic() if remaining <= 0: break try: response = requests.get(BASE + "/v1/tob/job/" + job_id, headers=HEADERS, timeout=(min(10, remaining), min(30, remaining))) except (requests.Timeout, requests.ConnectionError): time.sleep(min(5, max(0, deadline - time.monotonic()))) continue if response.status_code in {429, 500, 502, 503, 504}: time.sleep(min(delay_for(response), max(0, deadline - time.monotonic()))) continue job = parse(response) persist(folder / "result.json", job) if job.get("id") != job_id: raise RuntimeError("Task id mismatch; inspect result.json") status = job.get("status") if status == 2: return job if status == 3: # Full diagnostics are for private storage, not public logs. raise RuntimeError("Task failed: " + json.dumps(job, ensure_ascii=False)) if status != 1: raise RuntimeError("Unknown task status: " + json.dumps(job, ensure_ascii=False)) time.sleep(min(5, max(0, deadline - time.monotonic()))) raise TimeoutError("Polling budget exhausted; resume the same task: " + job_id) def download(job, folder): audits = job.get("audits") or [] saved, errors = [], [] for index, url in enumerate(job.get("urls") or []): if not url or (index < len(audits) and audits[index]): errors.append({"index": index, "error": "unavailable or restricted result"}) continue try: # Storage requests must not carry the API authorization header. response = requests.get(url, timeout=(15, 120)) response.raise_for_status() with Image.open(io.BytesIO(response.content)) as image: image.load() path = folder / f"mj-imagine-{job['id']}-{index}.png" temporary = path.with_suffix(".png.part") image.save(temporary, format="PNG") temporary.replace(path) saved.append({"index": index, "file": str(path), "size": list(image.size)}) except (requests.RequestException, OSError, ValueError) as exc: errors.append({"index": index, "error": type(exc).__name__}) persist(folder / "downloads.json", {"saved": saved, "errors": errors}) for item in saved: print(item["file"]) if errors or not saved: raise RuntimeError("Incomplete downloads; inspect downloads.json and resume the same job. Do not resubmit.") def main(): parser = argparse.ArgumentParser() parser.add_argument("--wait-seconds", type=float, default=360) commands = parser.add_subparsers(dest="command", required=True) submit = commands.add_parser("submit") submit.add_argument("operation", choices=sorted(OPERATIONS)) submit.add_argument("body_file") resume = commands.add_parser("resume") resume.add_argument("job_id") args = parser.parse_args() if args.wait_seconds <= 0: raise ValueError("wait-seconds must be positive") data = None if args.command == "submit": body = json.loads(Path(args.body_file).read_text(encoding="utf-8-sig")) if args.operation == "diffusion": if set(body) - {"model", "text", "callback"}: raise ValueError("Diffusion accepts model, text and optional callback; put generation parameters in text") body.setdefault("model", MODEL) if body["model"] not in MODELS or not isinstance(body.get("text"), str) or not body["text"].strip(): raise ValueError("Use model=mj_imagine or youchuan-image and a nonempty text string") # Submission is never automatically retried, including after a timeout. data = parse(requests.post(BASE + "/v1/tob/" + args.operation, headers=HEADERS, json=body, timeout=(15, 120))) job_id = data.get("id") else: job_id = args.job_id if not isinstance(job_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]+", job_id): raise ValueError("Missing or invalid task id; inspect submission without resubmitting") folder = Path("mj-output") / job_id folder.mkdir(parents=True, exist_ok=True) if data is not None: persist(folder / "submission.json", data) persist(Path("mj-task.json"), {"id": job_id}) print("Task:", job_id, flush=True) job = poll(job_id, folder, args.wait_seconds) download(job, folder) if __name__ == "__main__": main() ``` 图片解码用于避免保存错误响应;业务中仍需核对画面内容是否满足要求。下载失败时从原任务继续查询或下载,不重新提交生成。 ### 超时、限流与通知 每隔 5 秒查询,示例默认等待预算为 360 秒;HTTP 请求自身也有独立超时,因此不是严格的程序总运行时限。六分钟是客户端预算,不代表任务会在六分钟内结束或已取消。预算到期保留 id,之后执行 `python mj.py resume TASK_ID`。 | 查询结果 | 客户端处理 | |---|---| | `status=1` | 继续查询同一 id | | `status=2` | 检查 urls/audits,立即下载全部可用图片 | | `status=3` | 停止查询,保存 comment、cost 与完整响应;不自动新建任务 | | HTTP 400 | 请求参数错误,停止查询并修正 | | HTTP 401 / 403 | 检查密钥、权限与任务归属,停止自动轮询 | | HTTP 404 | 任务不存在或不可访问;核对 id,不持续轮询 | | HTTP 429 | 按 Retry-After 等待,再查询同一 id | | HTTP 500 / 502 / 503 / 504 或短暂网络错误 | 预算内延迟重试 GET,不重复 POST | | 未知 status / 非 JSON 响应 | 保存诊断并停止,不显示为成功 | 提交阶段超时且未拿到 id 时,任务是否创建未知,不自动重复提交,先通过控制台或客服核对原请求。不要自动更换版本重试,避免额外费用和改变客户的生成要求。 ### 图片保存与存储 成功后遍历原始 `urls` 数组,立即下载所有可用图片,分别保存为 `mj-imagine-{JobID}-{原始序号}.png`。示例使用 Pillow 实际编码为 PNG,不只是将 JPEG 改后缀;序号从 0 开始,与后续 `imageNo` 对应。单张下载失败仍处理其他图片,并在 `downloads.json` 记录失败位置;之后从原任务恢复下载,不再次生成。 [悠船存储说明](https://tob.youchuan.cn/docs/guides)规定上游生成图片默认保留 30 天。这不是签名 URL 的有效期,也不是 YunQi 的永久存储承诺。不要长期依赖响应 URL;客户需自行保存到受控对象存储。下载时保留 URL 查询参数,但不要携带 API 鉴权头。 需要通知时,生成和编辑接口可传选填 `callback` URL(reroll 除外)。业务接收通知后按任务 id 主动查询确认,以查询结果作为状态依据;回调处理应幂等,不假定通知只到达一次,也不将猜测的签名头作为认证协议。没有公网通知地址时使用上面的查询流程。 ## 13. 工具调用与结构化输出 本节分别采用 OpenAI Chat Completions、OpenAI Responses 与 Anthropic Messages 的原生工具字段,三种结构不能混用。先通过 `GET /v1/models` 确认模型可用,再从一个只含单个函数工具的最小请求开始。 工具只由模型提出调用请求,实际执行必须在应用侧完成。Agent 应校验工具名称和参数,再调用本地函数或外部服务;不要直接执行未经校验的命令或 SQL。 ### Chat Completions 工具调用 定义工具: ```json { "model": "gpt-5.6-sol", "messages": [ {"role": "user", "content": "上海今天的天气怎么样?"} ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"], "additionalProperties": false } } } ], "tool_choice": "auto" } ``` 模型提出调用时,读取 `choices[].message.tool_calls[]`。`function.arguments` 是 JSON 字符串,需要解析并校验。 ```json { "choices": [ { "message": { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_01", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"上海\"}" } } ] } } ] } ``` 执行工具后,把原 assistant 消息和工具结果加入下一次请求。工具结果消息的 `tool_call_id` 必须与调用 ID 一致。 ```json { "role": "tool", "tool_call_id": "call_01", "content": "晴,28°C" } ``` ### Responses 工具调用 ```json { "model": "gpt-5.6-sol", "input": "查询上海天气", "tools": [ { "type": "function", "name": "get_weather", "description": "查询指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"], "additionalProperties": false } } ] } ``` 遍历 `output[]` 中全部 `function_call`,校验工具名和 `arguments`,执行后以原 `call_id` 回传。下一轮 `input` 为原始历史 + 完整 `output[]` + 全部 `function_call_output`,并继续发送 `tools`。保留 reasoning 项,不拼造 ID,不依赖 `previous_response_id`。下例是内容结构节选;运行代码必须使用实际响应项。 ```json { "model": "gpt-5.6-sol", "input": [ {"role": "user", "content": "查询上海天气"}, {"type": "function_call", "call_id": "call_01", "name": "get_weather", "arguments": "{\"city\":\"上海\"}"}, { "type": "function_call_output", "call_id": "call_01", "output": "晴,28°C" } ] } ``` ### Responses 完整多轮示例 安装 `requests`,设置 `YUNQIAI_API_KEY` 与客户选定的 `YUNQIAI_MODEL`(本示例用于本文 GPT 文本模型)。示例订单查询为本地只读固定数据,可替换为业务查询;工具执行需要权限校验,有副作用的工具还需要确认和业务去重。每轮保留完整响应项,不依赖服务端会话存储。 ```python import json import os import requests url = 'https://www.yunqiai.chat/v1/responses' headers = {'Authorization': 'Bearer ' + os.environ['YUNQIAI_API_KEY']} model = os.environ['YUNQIAI_MODEL'] tools = [{ 'type': 'function', 'name': 'lookup_order', 'description': 'Read an order status by ID.', 'parameters': {'type': 'object', 'properties': {'order_id': {'type': 'string'}}, 'required': ['order_id'], 'additionalProperties': False}, 'strict': True, }] history = [{'role': 'user', 'content': 'Use lookup_order for order YQ-8264 and tell me its status.'}] for turn in range(5): payload = {'model': model, 'input': history, 'tools': tools, 'max_output_tokens': 2048, 'reasoning': {'effort': 'low'}} if turn == 0: payload['tool_choice'] = 'required' response = requests.post(url, headers=headers, json=payload, timeout=(15, 180)) response.encoding = 'utf-8' if not response.ok: request_id = response.headers.get('x-request-id') or response.headers.get('request-id') raise RuntimeError(f'HTTP {response.status_code}; request_id={request_id}; body={response.text[:2000]}') data = response.json() if data.get('error') or data.get('status') != 'completed': detail = {k: data.get(k) for k in ['id', 'status', 'error', 'incomplete_details', 'request_id']} raise RuntimeError('Incomplete or failed response; do not execute tools: ' + json.dumps(detail, ensure_ascii=False)) output = data.get('output', []) history.extend(output) calls = [item for item in output if item.get('type') == 'function_call'] if not calls: texts = [part['text'] for item in output if item.get('type') == 'message' for part in item.get('content', []) if part.get('type') == 'output_text'] if not texts: raise RuntimeError('No final text; inspect refusal or other output items.') print('\n'.join(texts)) break for item in calls: args = json.loads(item['arguments']) if item['name'] != 'lookup_order' or args != {'order_id': 'YQ-8264'}: raise RuntimeError('Unexpected tool or arguments') # Replace this read-only fixture with an authorized business lookup. result = {'order_id': 'YQ-8264', 'status': 'shipped'} history.append({'type': 'function_call_output', 'call_id': item['call_id'], 'output': json.dumps(result)}) else: raise RuntimeError('Tool round limit exceeded') ``` ### Anthropic Messages 工具调用 ```json { "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [ {"role": "user", "content": "查询上海天气"} ], "tools": [ { "name": "get_weather", "description": "查询指定城市的天气", "input_schema": { "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"] } } ] } ``` 模型提出调用时,遍历 `content[]`,找到 `type` 为 `tool_use` 的内容块,读取 `id`、`name` 和 `input`。下一次请求应保留包含该 `tool_use` 的完整 assistant 消息,再追加一条带 `tool_result` 的 `user` 消息;`tool_use_id` 必须与原调用 ID 一致: ```json { "role": "user", "content": [ { "type": "tool_result", "tool_use_id": "toolu_01", "content": "晴,28°C" } ] } ``` ### Chat Completions JSON Schema 输出 需要稳定 JSON 时,使用 `response_format`。应用侧仍应解析并校验最终 JSON。 ```json { "model": "gpt-5.5", "messages": [ {"role": "user", "content": "提取姓名和年龄:小明今年 18 岁"} ], "response_format": { "type": "json_schema", "json_schema": { "name": "person", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"} }, "required": ["name", "age"], "additionalProperties": false } } } } ``` ## 14. 流式与非流式返回 ### SSE 通用解析规则 - Chat Completions、Responses、Anthropic Messages 与开启流式的 GPT Images 使用 `text/event-stream`。 - 按空行切分 SSE 事件;同一事件有多行 `data:` 时先按换行拼接,再解析 JSON。`event:` 是事件名,冒号开头的行是注释或心跳。 - `data:` 后可以有一个空格,也可以没有;不要只匹配 `data: `。HTTP 响应按 UTF-8 解码,不把任意网络分块直接送入 JSON 解析器。 - 一次网络读取可能只有半条事件,也可能包含多条事件,不要把网络分块直接当成 SSE 事件。 - 使用 SDK 时优先遍历 SDK 提供的事件对象;手动解析时仍应按下方各协议的完成标记判断成功。 - 连接在完成标记前断开时,把本次结果视为不完整;保留已经收到的增量,但不要把它当作完整回答。 手动使用 requests 时,先设置 `response.encoding = "utf-8"`,用下面的迭代器读取已完成的事件,再按对应协议解析 JSON 或处理 `[DONE]`。迭代器不发起请求、不自动重连;其参数为已通过 HTTP 状态检查且使用 `stream=True` 打开的响应对象。 ```python def iter_sse_data(response): data_lines = [] for line in response.iter_lines(decode_unicode=True): if line == "": if data_lines: data = "\n".join(data_lines) data_lines = [] if data: yield data continue if line.startswith(":"): continue field, _, value = line.partition(":") if value.startswith(" "): value = value[1:] if field == "data": data_lines.append(value) # A frame without its terminating blank line is incomplete. ``` ### Chat Completions 流 每个 JSON 数据块的 `object` 通常为 `chat.completion.chunk`。按 `choices[].index` 分组处理: | 字段 | 处理方式 | |---|---| | `choices[].delta.role` | 初始化该候选的角色 | | `choices[].delta.content` | 按到达顺序追加文本 | | `choices[].delta.tool_calls[]` | 先按 `choices[].index` 区分候选,再按工具调用的 `index` 分组;保留 `id` 与函数名,并拼接 `function.arguments` 字符串 | | `choices[].finish_reason` | 记录该候选的结束原因;不是整个 SSE 的替代终止标记 | | `data: [DONE]` | Chat Completions 流正常结束 | ```text data: {"id":"chatcmpl_01","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null}]} data: {"id":"chatcmpl_01","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: [DONE] ``` 工具参数可能跨多个 chunk;只有在对应工具调用结束后,才对拼接完成的 `function.arguments` 做 JSON 解析和参数校验。 ### Responses 流 Responses 事件在 JSON 的 `type` 中标识类型。常用事件如下: | `type` | 关键字段与处理 | |---|---| | `response.created` | 初始化响应状态,保存响应 ID | | `response.output_item.added` | 按 `output_index` 登记新的类型化 Item;函数调用需保存 Item `id`、`name` 与回传结果所需的 `call_id` | | `response.output_text.delta` | 把 `delta` 追加到对应文本内容 | | `response.function_call_arguments.delta` | 按 `item_id` 或 `output_index` 找到对应函数调用并拼接 `delta`;完成后再解析 JSON | | `response.output_item.done` | 用完成后的 Item 替换本地聚合版本;只有 `item.type` 为 `function_call` 时才写入工具调用集合 | | `response.completed` | 正常结束;其中的 `response.output[]` 是最终完整响应 | | `response.failed`、`response.incomplete`、`error` | 不要标记为成功;保留事件中的错误或不完整原因 | `response.output_text.delta` 只用于流式聚合;非流式原始 HTTP 响应仍读取类型化的 `output[]`。SDK 的 `output_text` 是完成后聚合文本的便利属性。 Responses 的 `error` 流事件读取事件顶层的 `code`、`message` 与 `param`;如兼容层同时返回嵌套 `error`,可先取嵌套对象,否则读取事件本身。工具执行完成后,使用先前保存的 `call_id` 构造 `function_call_output`。 ### Anthropic Messages 流 Anthropic SSE 同时提供 `event:` 名称和 JSON `data.type`。生命周期顺序为: ```text message_start content_block_start content_block_delta content_block_stop message_delta message_stop ``` 一个响应可以包含多组 `content_block_start` → `content_block_delta` → `content_block_stop`。按内容块 `index` 聚合: - `content_block_start.content_block.type: tool_use`:保存该工具调用的 `id` 与 `name`;后续 `tool_result.tool_use_id` 必须使用这个 `id`。 - `delta.type: text_delta`:追加 `delta.text`。 - `delta.type: input_json_delta`:追加 `delta.partial_json`;到 `content_block_stop` 后再把完整字符串解析为工具参数 JSON。 - `message_delta`:读取最终 `stop_reason` 与增量 usage。 - `ping`:心跳事件,可忽略内容但应保持连接。 - `type: error`:流失败,读取 `error.type`、`error.message` 与可用的 `request_id`。 - 收到 `message_stop` 才表示消息流正常结束。 ### GPT Images 流 对支持 `stream` 的 Images Generations / Edits 请求: - 将 `stream` 设为 `true`,`partial_images` 可设为 `0–3`。 - `partial_images: 0` 只返回最终图;大于 `0` 时可返回中间图,但实际中间图数量可能少于请求值。 - 图像事件的 `type` 为 `image_generation.partial_image`,图片序号在 `partial_image_index`,图片 Base64 在 `b64_json`。 - 中间图和最终图使用同一种事件类型。保存每个事件;流正常结束后,把最后收到的图像事件作为最终图。不要等待未定义的 `image_generation.completed` 事件。 - 若连接异常结束或未收到任何图像事件,本次流不完整,不要把某张中间图标记为最终图。 ```json { "type": "image_generation.partial_image", "partial_image_index": 0, "b64_json": "BASE64_IMAGE_DATA" } ``` ### 非流式图片与超时 - Gemini Image 使用 `generateContent`;HTTP 请求完成后遍历 `candidates[].content.parts[].inlineData`。 - GPT Images 使用 `stream: false` 时遍历 `data[]`,逐项处理 `b64_json` 或 `url`。 - 图片生成通常比文本请求耗时更长,客户端可先把超时设置为 `3–5 分钟`,再按实际请求大小调整。这是客户端等待时间,不是服务端完成时限。 - GPT Images / Gemini Image 如果在收到完整响应前超时,调用结果处于未知状态,没有已发布的查询端点确认这类请求;不要自动立即重试。MJ 使用 12B 的异步任务查询,不能套用这条同步接口规则。 ## 15. OpenAI SDK ### Python ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ["YUNQIAI_API_KEY"], base_url="https://www.yunqiai.chat/v1", max_retries=0, ) response = client.chat.completions.create( model="gpt-5.6-sol", messages=[{"role": "user", "content": "你好"}], ) print(response.choices[0].message.content) ``` ### Node.js ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.YUNQIAI_API_KEY, maxRetries: 0, baseURL: "https://www.yunqiai.chat/v1", }); const response = await client.chat.completions.create({ model: "gpt-5.6-sol", messages: [{ role: "user", content: "你好" }], }); console.log(response.choices[0].message.content); ``` ## 16. Agent 客户端配置 先备份并合并现有配置,不整文件覆盖,不修改用户的权限规则。示例中的模型仅用于展示字段,主模型、别名和子代理模型由客户确定;不要自动切换型号。对每个使用的型号确认密钥权限。 通用 Python / Node 示例读取 `YUNQIAI_API_KEY`;MJ 专用示例读取 `YOUCHUAN_API_KEY`,同样填入具有相应权限的 YunQi 密钥。PowerShell 当前会话可设置 `$env:YUNQIAI_API_KEY = "YOUR_API_KEY"`;Bash 可设置 `export YUNQIAI_API_KEY="YOUR_API_KEY"`。通过私有环境注入真实密钥,不保留在仓库、共享脚本或 Shell 历史中。cURL 示例使用 Bash 换行与引号;Windows PowerShell 不要直接粘贴 Bash 的反斜杠续行,可运行 Python 示例。 ### Codex `~/.codex/config.toml`: ```toml model = "gpt-5.6-sol" model_provider = "yunqi" model_reasoning_effort = "high" model_verbosity = "high" cli_auth_credentials_store = "file" [model_providers.yunqi] name = "YunQi AI" base_url = "https://www.yunqiai.chat/v1" requires_openai_auth = true wire_api = "responses" ``` `~/.codex/auth.json`: ```json { "auth_mode": "apikey", "OPENAI_API_KEY": "YOUR_API_KEY" } ``` ### Codex 环境变量鉴权(另一种方式) 若需保留已有 `auth.json`,合并以下 provider,密钥由运行进程的 `YUNQIAI_API_KEY` 提供。此方式不要同时设置 `requires_openai_auth = true` 或其他 Bearer token 字段;上面的 auth.json 方式与此方式二选一。 ```toml model = "gpt-5.6-sol" model_provider = "yunqi" [model_providers.yunqi] name = "YunQi AI" base_url = "https://www.yunqiai.chat/v1" env_key = "YUNQIAI_API_KEY" wire_api = "responses" ``` 配置字段见 [Codex 配置参考](https://developers.openai.com/codex/config-reference/)。使用 auth.json 方式会切换登录身份,请先备份原有凭据。 ### Claude Code `~/.claude/settings.json`: ```json { "$schema": "https://json.schemastore.org/claude-code-settings.json", "env": { "ANTHROPIC_BASE_URL": "https://www.yunqiai.chat", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6", "ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-8", "CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6" }, "model": "claude-sonnet-4-6" } ``` ### CC Switch 在对应客户端标签页添加自定义 YunQi AI 服务商。 | 字段 | Claude Code 标签页 | Codex 标签页 | |---|---|---| | API 格式 | Anthropic Messages(原生) | OpenAI Responses | | Base URL | `https://www.yunqiai.chat` | `https://www.yunqiai.chat/v1` | | 最终路径 | `/v1/messages` | `/v1/responses` | | API Key | 控制台创建的 YunQi Key | 控制台创建的 YunQi Key | | 模型 | 客户选定的 Claude 完整型号 | 客户选定的 GPT 完整型号 | Claude 认证字段使用 `ANTHROPIC_API_KEY` 或 `ANTHROPIC_AUTH_TOKEN`,只保留一种有效来源,避免旧环境变量覆盖。Codex 的 `model_provider` 与 provider 表名一致,`wire_api = "responses"`。不要把 /v1/messages 填成 Base URL。 保存后启用对应标签页的服务商,退出旧客户端会话并重启。若使用 CC Switch 本地代理接管,配置中的回环地址可能是正常代理入口;确认代理运行,不要同时手工改写它管理的配置。 地址测速、模型列表和生成请求验证不同环节。最终用选定模型发送短消息,再检查流式和工具调用;测速失败本身不能证明模型接口不兼容。 - DNS / TLS / timeout:检查域名拼写、系统时间、DNS、代理和证书链。域名为 `www.yunqiai.chat`。 - 域名失败而 IP 成功:检查域名代理规则、IPv4/IPv6 路由、SNI 与证书。HTTP IP 成功不代表 HTTPS 域名连接正常。 - 401 / 403:检查启用的服务商、鉴权字段冲突、Key 和权限。 - 404:检查重复 /v1、完整路径和模型 ID。 - 429 / 503:读取正文区分额度、限流、分组或渠道问题,保留请求 ID。 不要关闭 TLS 校验、使用 `--insecure` 或将带 Key 的调用降级到 HTTP。HTTPS IP 需要证书也覆盖该 IP。客户端可能使用不同的代理与证书存储,不能仅凭浏览器能访问就断言 API 客户端正常。 字段界面以安装版本为准,参考 [CC Switch 服务商配置](https://github.com/farion1231/cc-switch/blob/main/docs/user-manual/en/2-providers/2.1-add.md)。 ### VS Code、WSL、SSH 与容器 配置放在实际运行客户端的环境。Windows 本机的 `~` 为 `%USERPROFILE%`;macOS、Linux、WSL 和 SSH 为该环境的用户目录。WSL 不会自动读取 Windows 的个人配置。 VS Code Remote SSH、WSL 或容器中,先确认扩展运行在本机还是远程,再修改对应环境的 `~/.claude/settings.json` 或 `~/.codex/config.toml`。更新环境变量后完全退出并重启 VS Code 和客户端;集成终端设置的变量不会反向传给已运行的扩展。 Claude Code 在新会话用 `/status` 检查设置来源、`/model` 检查型号。项目设置或组织策略可能覆盖用户设置,JSON 不允许注释和尾逗号。不要为排查连接问题关闭工具权限。Claude 网页版聊天不通过这些本地配置接入。参考 [Claude Code 设置](https://code.claude.com/docs/en/settings)。 ### WorkBuddy | 字段 | 值 | |---|---| | 提供商 | `自定义 / Custom` | | 接口地址 | `https://www.yunqiai.chat/v1/chat/completions` | | API Key | `YOUR_API_KEY` | | 模型名称 | `gpt-5.6-sol` 或 `GET /v1/models` 返回的其他文本模型 | | 高级配置 | 勾选“工具调用”和“自定义协议”;图片输入和推理模式按模型能力开启 | WorkBuddy 填写完整接口地址,不是 Base URL。 ### Cherry Studio、Chatbox 与其他 OpenAI Compatible 客户端 | 字段 | 值 | |---|---| | 服务名称 | `YunQi AI` | | Base URL | `https://www.yunqiai.chat/v1` | | API Key | `YOUR_API_KEY` | | 模型 | 客户选定的完整模型 ID | 如果客户端可自动读取模型,使用 `GET /v1/models`;否则手动填写完整模型 ID。 ## 17. 错误与重试 ### 错误响应结构 先按 HTTP 状态判断成功或失败,再根据所用协议解析错误体。常见 envelope 如下。 OpenAI Compatible、Responses 与 Images: ```json { "error": { "message": "The requested model was not found.", "type": "invalid_request_error", "param": "model", "code": "model_not_found" } } ``` Anthropic Messages: ```json { "type": "error", "error": { "type": "authentication_error", "message": "Invalid API key" }, "request_id": "req_01JY7X" } ``` Gemini Generate Content: ```json { "error": { "code": 400, "message": "Invalid request.", "status": "INVALID_ARGUMENT" } } ``` 代码不要只匹配错误文案。OpenAI 风格优先读取 `error.code`、`error.type`、`error.param`、`error.message`;Anthropic 读取顶层 `type`、`error.type`、`error.message`、`request_id`;Gemini 读取 `error.code`、`error.status`、`error.message`。若响应不是 JSON,保留 HTTP 状态、`Content-Type` 和经过长度限制的文本正文。 ### Request ID - OpenAI 风格响应优先读取 `x-request-id` 响应头。 - Anthropic 响应读取 `request-id` 响应头,并兼容错误体中的 `request_id`。 - HTTP 头名称不区分大小写。若上述字段不存在,记录请求时间、模型、方法、路径和 HTTP 状态,不要自行生成一个值冒充服务端 Request ID。 ```javascript function readErrorDetails(response, body) { const objectBody = body && typeof body === "object" ? body : {}; const envelope = objectBody.error && typeof objectBody.error === "object" ? objectBody.error : objectBody; return { requestId: response.headers.get("x-request-id") ?? response.headers.get("request-id") ?? objectBody.request_id ?? null, code: envelope.code ?? envelope.type ?? envelope.status ?? null, message: envelope.message ?? response.statusText, }; } ``` | HTTP 状态 | 含义 | Agent 处理方式 | |---:|---|---| | `400` | 请求错误 | 检查 JSON、必填参数、参数类型、图片尺寸和媒体编码 | | `401` | 鉴权失败 | 检查 API Key、鉴权头格式和是否混入空格 | | `403` | 权限、分组或余额不足 | 按正文的 `code` 或 `error.code` 区分原因;`insufficient_user_quota` 表示账号余额不足以预扣费,需补充账号额度;权限错误则检查账号与密钥所属分组。不要自动换 Key、模型、端点或重复提交 | | `404` | 路径或模型不存在 | 检查 Base URL、接口路径,并重新读取 `GET /v1/models` | | `429` | 限流或额度不足 | 先读取错误正文;限流按 `Retry-After` 等待,额度不足先处理额度 | | `500` | 服务错误 | 保存 Request ID;POST 结果可能未知,不自动重放 | | `502` / `503` | 服务暂时不可用 | 保存请求 ID,核对密钥分组与渠道;不自动重放 POST 或替换型号 | ### 最短排查顺序 1. 调用 `GET /v1/models`,确认 API Key 有效并取得真实可见模型。401/403 时先停止生成调用;密钥未过期不代表所属分组权限仍有效。 2. 用只包含必填字段的最小请求复现。 3. 去掉工具、图片、流式输出和高级采样参数。 4. 记录请求时间、模型、接口、HTTP 状态和 Request ID。 5. 联系支持时不要发送 API Key。 ### 重试规则 - `400`、`401`、`403`、`404`:先处理请求、鉴权、权限或额度问题,不要原样重试;403 必须结合错误正文判断,不能直接归类为密钥失效。账号余额与 API Key 的额度上限是两项独立限制,新建 Key 或提高 Key 上限不会增加账号余额。 - `429`:先区分额度不足与限流。额度不足先处理额度;明确限流时遵守 `Retry-After`(秒数或 HTTP 日期),限定总等待时间与尝试次数;不要重复执行业务工具。 - `408`、`5xx`、超时、连接断开:POST 可能已经被服务端接受,不自动重放。记录实际请求 ID 和状态,只有业务接受重复调用与计费风险时才发起新请求。 - 已取得 MJ 任务 id 后,GET 查询可按指数退避重试同一个 id,不重新提交生成。 - GPT Images / Gemini Image 请求可使用 `3–5 分钟` 的客户端超时;结果未知时不自动重发。MJ 的提交与查询分开处理,拿到任务 id 后持续查询原任务,具体见 12B。 ## 18. Agent 完成检查 - 已调用 `GET /v1/models`,请求中的模型 ID 与返回值完全一致。 - 已按模型选择正确协议和鉴权请求头。 - Base URL 与完整接口地址没有混填。 - API Key 只保存在环境变量或本机私有配置中。 - 已从最小请求开始,再逐项加入高级参数。 - Base64 使用了对应协议要求的 Data URL 或裸 Base64 写法。 - Responses PDF 使用 `input_file.file_data + filename` 或可访问的 `file_url`;Anthropic PDF 使用内联 `document.source`,不使用文件 ID。 - multipart 图片编辑已上传实际文件,而不是把 Base64 字符串直接放入文件字段。 - Chat Completions 遍历 `choices[]`;Responses 遍历 `output[]`;Messages 遍历 `content[]`;Gemini 遍历 `candidates[].content.parts[]`;Images 遍历 `data[]`。 - 流式请求按对应协议的完成标记结束;完成标记前断线的结果没有被当作完整回答。 - 图片客户端超时已按请求耗时调整;超时后不会自动重复提交。 - 错误日志保留 Request ID,但不包含 API Key、文件正文或图片 Base64。 ## 19. 协议参考 - OpenAI Responses API: - OpenAI Responses 流式输出: - OpenAI 文件输入: - OpenAI 图片与视觉输入: - OpenAI 图片生成指南: - OpenAI Images Generations: - OpenAI Images Edits: - Anthropic Messages API: - Anthropic Messages 流式输出: - Anthropic 图片输入: - Anthropic PDF 输入: - Gemini Generate Content: - Gemini 图片理解: - Gemini 视频理解: - Gemini 图片生成: