解耦 Tiantoken 与 VectorEngine 的图片请求超时

- AppConfig 新增 tiantoken_image_request_timeout_ms(默认 1000000),env 只读 TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS
- VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS 只写 vector_engine_image_request_timeout_ms,不再被 Tiantoken 变量覆盖
- Tiantoken 图片 client 与 OpenAiImageSettings.request_timeout_ms 改用新字段,两个 provider 各用自己的超时
- config 测试补 Tiantoken / VectorEngine 双向不覆盖断言,deploy env 示例补 VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000
- 同步开发运维文档与 decision-log
This commit is contained in:
2026-09-22 14:08:09 +08:00
parent b0bb71a79e
commit c0d4e76a01
6 changed files with 39 additions and 9 deletions
+26 -6
View File
@@ -18,6 +18,7 @@ const DEFAULT_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS: u64 = 600;
const DEFAULT_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS: u64 = 900;
const DEFAULT_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS: u64 = 1_800;
pub(crate) const DEFAULT_VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS: u64 = 1_000_000;
pub(crate) const DEFAULT_TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS: u64 = 1_000_000;
pub(crate) const DEFAULT_ELEVENLABS_REQUEST_TIMEOUT_MS: u64 = 180_000;
const DEFAULT_EDITOR_BGFILTER_BASE_URL: &str = "http://58.87.105.82/bgfilter";
const DEFAULT_EDITOR_BGFILTER_SINGLE_IMAGE_ESTIMATE_MS: u64 = 5_000;
@@ -225,6 +226,12 @@ pub struct AppConfig {
/// 启动时落到 `AppConfig` 字段,使 `AppState` 构造与测试构造复用同一条路径。
pub tiantoken_base_url: String,
pub tiantoken_api_key: Option<String>,
/// Tiantoken 图片链路(GPT Image 2.5 生成 / 编辑)的单次 attempt 超时。
///
/// 与 `vector_engine_image_request_timeout_ms` 相互独立,只由
/// `TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS` 配置,避免一个 provider 的超时设置
/// 顺带改写另一个 provider 的 deadline。
pub tiantoken_image_request_timeout_ms: u64,
pub elevenlabs_base_url: String,
pub elevenlabs_api_key: Option<String>,
pub elevenlabs_request_timeout_ms: u64,
@@ -541,6 +548,7 @@ impl Default for AppConfig {
vector_engine_audio_request_timeout_ms: 180_000,
tiantoken_base_url: String::new(),
tiantoken_api_key: None,
tiantoken_image_request_timeout_ms: DEFAULT_TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS,
elevenlabs_base_url: String::new(),
elevenlabs_api_key: None,
elevenlabs_request_timeout_ms: DEFAULT_ELEVENLABS_REQUEST_TIMEOUT_MS,
@@ -1338,18 +1346,25 @@ impl AppConfig {
config.vector_engine_api_key = read_first_non_empty_env(&["VECTOR_ENGINE_API_KEY"]);
if let Some(vector_engine_image_request_timeout_ms) =
read_first_positive_u64_env(&["VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS"])
{
// 单次 attempt 上限允许按环境收短;worker 调用还会受整次任务的绝对 deadline 约束。
config.vector_engine_image_request_timeout_ms = vector_engine_image_request_timeout_ms;
}
// Tiantoken 只读取自己的环境变量,不从 VectorEngine 配置回退;启动时冻结进 AppConfig,
// 供 AppState 构造编辑器 LLM / 图片 client 时复用同一条配置路径。
config.tiantoken_base_url =
read_first_non_empty_env(&["TIANTOKEN_BASE_URL"]).unwrap_or_default();
config.tiantoken_api_key = read_first_non_empty_env(&["TIANTOKEN_API_KEY"]);
if let Some(tiantoken_image_request_timeout_ms) = read_first_positive_u64_env(&[
"TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS",
"VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS",
]) {
// 单次 attempt 上限允许按环境收短;worker 调用还会受整次任务的绝对 deadline 约束
config.vector_engine_image_request_timeout_ms = tiantoken_image_request_timeout_ms;
if let Some(tiantoken_image_request_timeout_ms) =
read_first_positive_u64_env(&["TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS"])
{
// Tiantoken 图片链路独立计时:`TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS` 不再改写
// VectorEngine 的图片超时,两个 provider 各用自己的配置与默认值
config.tiantoken_image_request_timeout_ms = tiantoken_image_request_timeout_ms;
}
if let Some(vector_engine_audio_request_timeout_ms) =
@@ -2288,6 +2303,7 @@ mod tests {
std::env::remove_var("VECTOR_ENGINE_BASE_URL");
std::env::remove_var("VECTOR_ENGINE_API_KEY");
std::env::remove_var("VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS");
std::env::remove_var("TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS");
std::env::remove_var("HYPER3D_BASE_URL");
std::env::remove_var("DASHSCOPE_SCENE_IMAGE_MODEL");
std::env::remove_var("DASHSCOPE_REFERENCE_IMAGE_MODEL");
@@ -2306,6 +2322,7 @@ mod tests {
std::env::set_var("VECTOR_ENGINE_BASE_URL", "https://vector.internal.example");
std::env::set_var("VECTOR_ENGINE_API_KEY", "vector-engine-key");
std::env::set_var("VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS", "210000");
std::env::set_var("TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS", "180000");
std::env::set_var("HYPER3D_BASE_URL", "https://model.internal.example/api/v2");
std::env::set_var("DASHSCOPE_SCENE_IMAGE_MODEL", "scene-model");
std::env::set_var("DASHSCOPE_REFERENCE_IMAGE_MODEL", "reference-model");
@@ -2332,6 +2349,8 @@ mod tests {
"https://vector.internal.example"
);
assert_eq!(config.vector_engine_image_request_timeout_ms, 210_000);
// 两个 provider 的图片超时互不覆盖:Tiantoken 用 TIANTOKEN_*VectorEngine 用 VECTOR_ENGINE_*。
assert_eq!(config.tiantoken_image_request_timeout_ms, 180_000);
assert_eq!(
config.hyper3d_base_url,
"https://model.internal.example/api/v2"
@@ -2357,6 +2376,7 @@ mod tests {
std::env::remove_var("VECTOR_ENGINE_BASE_URL");
std::env::remove_var("VECTOR_ENGINE_API_KEY");
std::env::remove_var("VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS");
std::env::remove_var("TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS");
std::env::remove_var("HYPER3D_BASE_URL");
std::env::remove_var("DASHSCOPE_SCENE_IMAGE_MODEL");
std::env::remove_var("DASHSCOPE_REFERENCE_IMAGE_MODEL");
@@ -108,7 +108,7 @@ pub(crate) fn require_openai_image_settings(
Ok(OpenAiImageSettings {
base_url: base_url.to_string(),
api_key: api_key.to_string(),
request_timeout_ms: state.config.vector_engine_image_request_timeout_ms.max(1),
request_timeout_ms: state.config.tiantoken_image_request_timeout_ms.max(1),
request_deadline: None,
external_api_audit_state: Some(state.clone()),
external_api_audit_user_id: None,
+1 -1
View File
@@ -645,7 +645,7 @@ impl AppState {
ImageProvider::Tiantoken,
tiantoken_base_url.clone(),
tiantoken_api_key.clone(),
config.vector_engine_image_request_timeout_ms,
config.tiantoken_image_request_timeout_ms,
)?;
let llm_client = build_llm_client(&config)?;
let vector_engine_llm_client = build_vector_engine_llm_client(