feat(rust): 支持 image 同步转流式 SSE、free_team_first 调度预设及账户错误自动重试

- openai:image 同步响应桥接为流式 SSE(image_generation.completed / image_edit.completed 事件)
- image 请求解析与前置校验移除模型白名单,支持任意自定义模型名
- 调度器新增 free_team_first 预设(mode: both / free_only / team_only)
- pool config 解析重构:新增 POOL_ALLOWED_SCHEDULING_PRESETS 白名单,规范化 mode 字段
- 错误分类器新增账户/账单错误模式集,升级为 RetryUpstreamFailure 而非 StopSemanticClientError
- 修复 stream execution 中上游 headers 与输出 headers 混用导致 content-type 判断错误的问题
- codex image 工具始终写入 action 字段(generate/edit),仅 generate 操作填充默认 size/quality
- 前端:pool 节点组只展示实际执行过的候选节点,全部 skipped 时折叠为最后一个节点
- Redis 测试就绪检测从 TCP 连接改为 PING/PONG 协议验证
This commit is contained in:
fawney19
2026-04-24 10:05:55 +08:00
parent a9d10163af
commit 5d710d9d10
16 changed files with 1388 additions and 204 deletions

View File

@@ -7,7 +7,7 @@ use super::{
use crate::tests::{
any, build_router_with_state, build_state_with_execution_runtime_override, json, start_server,
to_bytes, AppState, Arc, Body, Json, Mutex, Request, Router, StatusCode, EXECUTION_PATH_HEADER,
EXECUTION_PATH_LOCAL_AI_PUBLIC,
EXECUTION_PATH_LOCAL_AI_PUBLIC, EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS,
};
use axum::response::IntoResponse;
@@ -522,7 +522,7 @@ async fn gateway_rejects_invalid_claude_count_tokens_payload_without_hitting_fal
}
#[tokio::test]
async fn gateway_rejects_gpt_image_2_on_chat_completions_without_hitting_fallback_probe() {
async fn gateway_does_not_locally_reject_image_model_name_on_chat_completions() {
let fallback_probe_hits = Arc::new(Mutex::new(0usize));
let fallback_probe_hits_clone = Arc::clone(&fallback_probe_hits);
let fallback_probe = Router::new().route(
@@ -567,18 +567,13 @@ async fn gateway_rejects_gpt_image_2_on_chat_completions_without_hitting_fallbac
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(
response
.headers()
.get(EXECUTION_PATH_HEADER)
.and_then(|value| value.to_str().ok()),
Some(EXECUTION_PATH_LOCAL_AI_PUBLIC)
);
let payload: serde_json::Value = response.json().await.expect("json body should parse");
assert_eq!(
payload["detail"],
"图片模型仅支持通过 /v1/images/generations、/v1/images/edits 或 /v1/images/variations 调用"
Some(EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS)
);
assert_eq!(*fallback_probe_hits.lock().expect("mutex should lock"), 0);