feat: add OpenAI image generation controls

This commit is contained in:
2ue
2026-05-05 03:26:54 +08:00
parent 4de28fec8c
commit 6faa344916
85 changed files with 6086 additions and 568 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-02
@@ -0,0 +1,46 @@
## Context
现有普通 Responses 流式已经具备较完整的断连续写能力:客户端写失败后可继续 drain 上游,并且具备数据间隔超时和 keepalive。图片流式路径目前仍然采用更直接的读写方式,客户端写失败会立即返回,上游读取也更容易跟随客户端取消而结束。
本次变更只针对图片流式路径,不改变普通文本流式路径的配置和行为。系统已经存在普通流式的后端超时配置,因此这里不引入页面级超时设置;图片流式只需要独立的后端默认值,让图片生成有更长的容忍窗口。
## Goals / Non-Goals
**Goals:**
- 图片流式在客户端断开后继续读取上游,尽量保留最终图片结果与计费结果。
- 图片流式使用独立于普通流式的超时与 keepalive 默认值。
- 不修改现有普通流式配置项的含义,不要求管理员新增页面配置。
- 维持图片计费与图片结果计数的一致性。
**Non-Goals:**
- 不设计新的前端配置页面。
- 不修改普通文本流式的超时策略。
- 不改变图片计费公式或分组倍率语义。
## Decisions
1. **使用独立的图片流式配置键**
- 选择:在后端配置中增加图片流式专用 `image_stream_data_interval_timeout` / `image_stream_keepalive_interval`
- 原因:图片流式耗时显著更长,复用普通流式默认值会过早触发超时;独立键能避免影响现有文本流式。
- 备选方案:直接复用普通流式配置并在代码里按路径放大倍数。这个方案会让普通流式和图片流式共享语义,后续难以维护。
2. **继续使用上下文 detach,而不是依赖客户端上下文**
- 选择:图片流式请求向上游发起时使用 `context.WithoutCancel` 派生的上下文。
- 原因:客户端断开时不应自动取消上游请求,否则无法收集最终图片结果,也无法完成图片计费。
- 备选方案:仍使用 `c.Request.Context()` 并只在写失败后继续 drain。这个方案在客户端取消场景下无法保证上游读取继续进行。
3. **只改图片流式路径,不改普通流式路径**
- 选择:`/v1/images/*``Responses + image_generation` 两条图片流式链路单独处理。
- 原因:风险最小,避免回归普通文本流式和现有超时配置。
- 备选方案:统一重构所有流式处理。这个方案范围更大,验证成本更高,不符合本次“尽量少改现有行为”的目标。
4. **不新增页面配置**
- 选择:图片流式独立超时默认值写入后端配置,沿用当前配置加载方式。
- 原因:用户明确要求和当前设置行为统一,不需要额外页面输入项。
- 备选方案:前端增加图片超时配置项。这个方案会改变现有运维方式,也容易引入误配。
## Risks / Trade-offs
- [Risk] 图片流式继续 drain 上游后,客户端已经断开但服务端仍占用连接与协程资源。→ [Mitigation] 只对图片流式启用更长但仍有限的专用超时,并保持与普通流式同样的 keepalive/超时退出机制。
- [Risk] 图片流式与普通流式的默认超时不同,运维如果只关注通用配置可能忽略图片专用值。→ [Mitigation] 在配置示例中明确标注图片流式专用默认值和用途。
- [Risk] 断连后继续读取可能导致日志中出现“客户端断开但最终成功”的状态。→ [Mitigation] 保留现有图片计费结果返回语义,同时让调用方在结果与错误并存时优先使用结果对象。
@@ -0,0 +1,25 @@
## Why
图片流式路径目前没有和普通 Responses 流式一致的断连续写策略,也没有独立于普通流式的超时控制。由于图片生成耗时更长,如果继续沿用普通流式处理方式,客户端断开时容易中断上游读取,影响图片产物收集与按图计费的准确性。
## What Changes
- 为 OpenAI Images API 和 `Responses + image_generation` 流式路径补充独立的上游续读策略,客户端断开后继续 drain 上游,尽量保留最终图片结果和计费结果。
- 为图片流式路径使用独立的流数据间隔超时与 keepalive 策略,默认比普通流式更长,不新增页面配置项。
- 保持现有普通流式配置与行为不变,避免影响已经配置好的普通文本分组。
- 让图片流式路径在超时、断连、写入失败等场景下保持图片计费语义一致。
## Capabilities
### New Capabilities
- `image-stream-resilience`: 图片流式路径的断连续读、独立超时和计费保留能力。
### Modified Capabilities
- `image-generation-billing-accounting`: 图片流式结果计数和计费结果的稳定性行为发生改变,但计费契约不变。
## Impact
- 影响 `backend/internal/service/openai_images.go``backend/internal/service/openai_images_responses.go` 的流式实现。
- 影响 `backend/internal/config/config.go``deploy/config.example.yaml` 中图片流式默认值和校验逻辑。
- 影响 `backend/internal/service/openai_images_test.go``backend/internal/config/config_test.go` 以及新增的图片流式稳定性测试。
- 不新增前端页面设置,不改变普通流式配置项名称和语义。
@@ -0,0 +1,53 @@
## ADDED Requirements
### Requirement: Image stream resilience
The system SHALL keep image generation stream processing active after downstream client disconnects so long as upstream reading can continue, in order to preserve final image outputs and billing results.
#### Scenario: Images API stream survives downstream disconnect
- **WHEN** `/v1/images/generations` is streamed to a client
- **AND** the downstream writer returns an error before the upstream stream completes
- **THEN** the service continues draining the upstream stream
- **AND** it still counts final image outputs if the upstream later emits them
- **AND** the request can still complete with image billing metadata
#### Scenario: Responses image tool stream survives downstream disconnect
- **WHEN** a `/v1/responses` request uses `image_generation` and is streamed to a client
- **AND** the downstream writer returns an error before the upstream stream completes
- **THEN** the service continues draining the upstream stream
- **AND** it still counts final image outputs if the upstream later emits them
- **AND** the request can still complete with image billing metadata
#### Scenario: Client disconnect does not force image stream to downgrade to text billing
- **WHEN** a successful image stream request has already produced final image outputs
- **AND** the downstream client disconnects before the final flush
- **THEN** the request remains billed as an image request
- **AND** the image count is preserved in the forward result
### Requirement: Image stream timeout isolation
The system SHALL use image-specific streaming timeout settings for image generation stream paths, and these settings SHALL be independent from the ordinary text streaming timeout values.
#### Scenario: Image stream uses dedicated timeout defaults
- **WHEN** an image generation stream path is executed
- **THEN** it uses the image-specific data interval timeout and keepalive interval defaults
- **AND** it does not rely on the ordinary text stream timeout defaults
#### Scenario: Ordinary stream settings remain unchanged
- **WHEN** a normal non-image streaming request is executed
- **THEN** the existing ordinary stream timeout configuration and behavior remain unchanged
#### Scenario: Image stream timeout is longer than ordinary stream timeout
- **WHEN** the image streaming timeout defaults are compared with the ordinary streaming defaults
- **THEN** the image streaming timeout is configured to allow a longer wait window than ordinary text streaming
### Requirement: Image stream billing consistency
The system SHALL keep the image billing result consistent even when image stream handling uses retries, keepalive writes, or downstream disconnect recovery.
#### Scenario: Final image count is preserved after reconnect-unsafe downstream failure
- **WHEN** the downstream client disconnects after at least one final image output has been observed upstream
- **THEN** the forward result retains the final image count
- **AND** usage recording can still proceed with image billing metadata
#### Scenario: Image stream timeout does not silently switch billing mode
- **WHEN** an image stream times out before any final image output is observed
- **THEN** the request is handled as a failed image stream
- **AND** it does not fall back to ordinary text billing semantics
@@ -0,0 +1,20 @@
## 1. Config and defaults
- [x] 1.1 Add image-specific stream timeout fields to gateway config.
- [x] 1.2 Register image stream timeout defaults in the config loader.
- [x] 1.3 Add config validation for image stream timeout ranges.
- [x] 1.4 Expose image stream timeout defaults in `deploy/config.example.yaml`.
## 2. Image stream runtime behavior
- [x] 2.1 Detach image stream upstream contexts from client cancellation.
- [x] 2.2 Add image-specific data interval timeout handling to `/v1/images/*` streaming.
- [x] 2.3 Add image-specific data interval timeout handling to `Responses + image_generation` streaming.
- [x] 2.4 Preserve upstream draining after downstream write failures in both image stream paths.
## 3. Tests and verification
- [x] 3.1 Add config tests for image stream timeout defaults and validation.
- [x] 3.2 Add image streaming disconnect tests for the Images API path.
- [x] 3.3 Add image streaming disconnect tests for the Responses image tool path.
- [x] 3.4 Run focused Go tests for the touched config and image service paths.