统一 VectorEngine LLM 客户端命名

将 editor_agent_llm_client 重命名为 vector_engine_llm_client
更新图标生成、角色动画、美术 Agent 与应用状态调用
同步排障文档中的客户端名称
This commit is contained in:
2026-08-05 14:30:30 +08:00
parent 755f58ddfb
commit e9f50248b9
5 changed files with 26 additions and 26 deletions
@@ -3833,7 +3833,7 @@
## 画布 Agent 的规划请求不能关闭瞬时失败重试
- 现象:美术 Agent 对话返回红色错误气泡 `completion error: LLM 请求超时,累计尝试 1 次`;HTTP 本身仍返回 200,前端 20 分钟 transport timeout 没有触发。
- 原因:规划请求虽然有 Agent 专用单次 timeout,但 `editor_agent_llm_client``max_retries` 硬编码为 0VectorEngine `gpt-5.4-mini` 的偶发长尾、连接超时或可重试上游状态会在第一次失败后直接持久化成 system error。framework 的英文 `completion error` 前缀也被原样暴露给用户。
- 原因:规划请求虽然有 Agent 专用单次 timeout,但 `vector_engine_llm_client``max_retries` 硬编码为 0VectorEngine `gpt-5.4-mini` 的偶发长尾、连接超时或可重试上游状态会在第一次失败后直接持久化成 system error。framework 的英文 `completion error` 前缀也被原样暴露给用户。
- 处理:120 秒改为前端软提示阈值:POST 仍 pending 时显示不入库的“仍在处理中,请耐心等待”;provider 明确断开/失败才写正式错误。专用 provider 单 attempt 使用 8 分钟 hard timeout,请求发起阶段读取 `GENARRATIVE_LLM_MAX_RETRIES`,但画布 Agent 最多重试 1 次且重试退避最多 60 秒。不要只计算单次 complete 的最坏时间:runner 还可因非法 JSON/工具校验失败进入后续轮次,必须从 handler 入口开始计算 18 分钟总 deadline,进入 `agent.prompt(...)` 时扣除会话锁/上下文准备已用时间,为持久化和前端 20 分钟 timeout 留出余量。响应头后的体读取/解析错误按明确失败收口,必须使用真实 attempt 计数;规划、配置和定价错误对用户统一为中文,原始诊断只记后端日志。重试发生在任何生成工具执行前,不会重复提交生成任务或扣费,不要通过提高前端 timeout 或 runner `max_turns` 掩盖 provider 重试缺失。
- 验证:`platform-editor-agent` 测试锁定 8 分钟 hard timeout 与中文错误;前端 fake timer 用例锁定 120 秒前只显示思考动画、到点后显示耐心等待、成功/失败后移除;`platform-llm` 回归用例锁定第二次 attempt 成功响应头后的 body timeout 仍报累计 2 次;`api-server` 测试锁定专用 client retry、18 分钟整体 deadline 与中文直达错误。运行态排障按同一 request id 对齐 `platform_llm` failure stage 与 `/messages` 总耗时,并确认仍 pending 的请求不再在 120 秒形成错误气泡。
- 关联:`server-rs/crates/platform-editor-agent/src/agent/agent.rs``server-rs/crates/platform-agent-harness/src/error.rs``server-rs/crates/api-server/src/state.rs``src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts``src/components/image-editor/EditorAgentConversation/MessageBubble.tsx``src/services/image-editor/editorAgentClient.ts`
@@ -744,9 +744,9 @@ pub(crate) async fn generate_editor_character_animation_for_owner(
// 背景色决策:预扣泥点之后才发起(显式传入 → 视觉 LLM 按源图主体配色自动决策 → 默认色)。
// 决策失败会让本 future 返回 Err,走 execute_billable 的失败退款,用户不损失泥点。
let screen_background_decision = resolve_editor_screen_background_color(
state.llm_client(),
state.editor_agent_llm_client(),
EditorScreenBackgroundDecisionInput {
state.llm_client(),
state.vector_engine_llm_client(),
EditorScreenBackgroundDecisionInput {
kind: EditorScreenBackgroundDecisionKind::CharacterAnimation,
screen_color: payload.screen_color.clone(),
prompt: payload.prompt_text.clone(),
@@ -754,7 +754,7 @@ pub(crate) async fn generate_editor_character_animation_for_owner(
reference_count: 1,
source_image_data_url: Some(source_data_url.clone()),
},
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
state: &state,
context: crate::external_api_audit::ExternalApiAuditContext {
user_id: Some(owner_user_id.clone()),
@@ -201,7 +201,7 @@ pub async fn editor_agent_message(
let tool_context = context::build_tool_context(&document);
// Build and run agent
let Some(llm_client) = state.editor_agent_llm_client() else {
let Some(llm_client) = state.vector_engine_llm_client() else {
tracing::warn!(
conversation_id = %conversation.conversation_id,
"美术 Agent LLM 客户端未配置"
@@ -1892,9 +1892,9 @@ pub(crate) async fn generate_editor_image_for_owner(
let screen_background_decision = if is_character_generation {
Some(
resolve_editor_screen_background_color(
state.llm_client(),
state.editor_agent_llm_client(),
EditorScreenBackgroundDecisionInput {
state.llm_client(),
state.vector_engine_llm_client(),
EditorScreenBackgroundDecisionInput {
kind: EditorScreenBackgroundDecisionKind::Character,
screen_color: requested_screen_color.clone(),
prompt: role_setting.clone(),
@@ -1902,7 +1902,7 @@ pub(crate) async fn generate_editor_image_for_owner(
reference_count,
source_image_data_url: None,
},
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
state,
context: crate::external_api_audit::ExternalApiAuditContext {
user_id: caller.audit_subject_user_id.clone(),
@@ -5072,9 +5072,9 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
async {
// 背景色决策:预扣泥点之后才发起(决策失败→future 返回 Err→失败退款)。
let screen_background_decision = resolve_editor_screen_background_color(
state.llm_client(),
state.editor_agent_llm_client(),
EditorScreenBackgroundDecisionInput {
state.llm_client(),
state.vector_engine_llm_client(),
EditorScreenBackgroundDecisionInput {
kind: EditorScreenBackgroundDecisionKind::IconSpritesheet,
screen_color: requested_screen_color.clone(),
prompt: user_prompt.clone(),
@@ -5082,7 +5082,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
reference_count,
source_image_data_url: None,
},
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
state,
context: crate::external_api_audit::ExternalApiAuditContext {
user_id: caller.audit_subject_user_id.clone(),
@@ -6156,9 +6156,9 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
async {
// 背景色决策:预扣泥点之后才发起(决策失败→future 返回 Err→失败退款)。
let screen_background_decision = resolve_editor_screen_background_color(
state.llm_client(),
state.editor_agent_llm_client(),
EditorScreenBackgroundDecisionInput {
state.llm_client(),
state.vector_engine_llm_client(),
EditorScreenBackgroundDecisionInput {
kind: EditorScreenBackgroundDecisionKind::UiDesignAssetExtraction,
screen_color: requested_screen_color.clone(),
prompt: decision_prompt.clone(),
@@ -6166,7 +6166,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
reference_count,
source_image_data_url: None,
},
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
Some(&crate::editor_screen_background_decision::EditorScreenBackgroundDecisionAuditContext {
state,
context: crate::external_api_audit::ExternalApiAuditContext {
user_id: caller.audit_subject_user_id.clone(),
+8 -8
View File
@@ -265,7 +265,7 @@ pub struct AppStateInner {
wallet_refund_outbox: Option<Arc<WalletRefundOutbox>>,
editor_generation_pricing_store: EditorGenerationPricingStore,
llm_client: Option<LlmClient>,
editor_agent_llm_client: Option<LlmClient>,
vector_engine_llm_client: Option<LlmClient>,
matting_client: Option<MattingClient>,
bgfilter_provider_http_client: reqwest::Client,
bgfilter_worker_http_client: reqwest::Client,
@@ -513,7 +513,7 @@ impl AppState {
)
.map_err(|error| AppStateInitError::DependencyUnavailable(error.to_string()))?;
let llm_client = build_llm_client(&config)?;
let editor_agent_llm_client = build_editor_agent_llm_client(&config)?;
let vector_engine_llm_client = build_vector_engine_llm_client(&config)?;
let matting_client = build_matting_client(&config)?;
let bgfilter_provider_http_client = build_bgfilter_provider_http_client(&config)?;
let bgfilter_worker_http_client = build_bgfilter_worker_http_client(&config)?;
@@ -570,7 +570,7 @@ impl AppState {
wallet_refund_outbox,
editor_generation_pricing_store,
llm_client,
editor_agent_llm_client,
vector_engine_llm_client,
matting_client,
bgfilter_provider_http_client,
bgfilter_worker_http_client,
@@ -1281,8 +1281,8 @@ impl AppState {
self.llm_client.as_ref()
}
pub fn editor_agent_llm_client(&self) -> Option<&LlmClient> {
self.editor_agent_llm_client.as_ref()
pub fn vector_engine_llm_client(&self) -> Option<&LlmClient> {
self.vector_engine_llm_client.as_ref()
}
pub fn matting_client(&self) -> Option<&MattingClient> {
@@ -2126,7 +2126,7 @@ fn build_llm_client(config: &AppConfig) -> Result<Option<LlmClient>, AppStateIni
Ok(Some(LlmClient::new(llm_config)?))
}
fn build_editor_agent_llm_client(
fn build_vector_engine_llm_client(
config: &AppConfig,
) -> Result<Option<LlmClient>, AppStateInitError> {
// 中文注释:Apimart 已于 2026-06 弃用,LLM 文本调用统一迁移到 VectorEngine。
@@ -2429,7 +2429,7 @@ mod tests {
let state = AppState::new(AppConfig::default()).expect("state should build");
assert!(state.llm_client().is_none());
assert!(state.editor_agent_llm_client().is_none());
assert!(state.vector_engine_llm_client().is_none());
}
#[test]
@@ -2454,7 +2454,7 @@ mod tests {
let state = AppState::new(config).expect("state should build");
let client = state
.editor_agent_llm_client()
.vector_engine_llm_client()
.expect("editor agent LLM client should exist");
assert_eq!(