修复 Agent 音频失败脱敏
Project CI / Repository checks (pull_request) Successful in 1m19s
Project CI / Frontend tests (pull_request) Successful in 2m59s
Project CI / Backend tests (pull_request) Successful in 3m57s
Project CI / Native shell tests (pull_request) Successful in 13m28s

复用外部生成任务的用户可见错误投影

按 Agent 工具名称映射音效任务并在回填前脱敏

补充音效和背景音乐失败回归测试
This commit is contained in:
2026-08-08 10:26:38 +00:00
parent 6a6d1f85d6
commit 0edefc67ec
3 changed files with 79 additions and 5 deletions
@@ -1,5 +1,8 @@
use crate::http_error::AppError;
use crate::state::AppState;
use crate::{
editor_agent::tool::editor_agent_audio_generation_job_kind,
external_generation::user_visible_external_generation_error, http_error::AppError,
state::AppState,
};
use platform_editor_agent::agent::tools::context::EditorToolContext;
use serde_json::Value;
use shared_contracts::editor_agent::{
@@ -87,10 +90,18 @@ pub async fn reconcile_editor_agent_tool_calls(
}
}
"failed" => {
let tool_name = document.messages[message_index]
.tool_call
.as_ref()
.expect("reconcile candidate contains a tool call")
.tool_name
.clone();
mark_job_failed(
&mut document.messages[message_index],
job.last_error_message
.unwrap_or_else(|| "生成失败".to_string()),
editor_agent_job_failure_message(
tool_name.as_str(),
job.last_error_message,
),
);
last_reconcile_error = None;
reconciled.push(document.messages[message_index].clone());
@@ -124,6 +135,14 @@ fn should_retry_result_reconcile(retry_count: u8) -> bool {
retry_count < EDITOR_AGENT_RESULT_RECONCILE_MAX_RETRIES
}
fn editor_agent_job_failure_message(tool_name: &str, last_error_message: Option<String>) -> String {
let Some(job_kind) = editor_agent_audio_generation_job_kind(tool_name) else {
return last_error_message.unwrap_or_else(|| "生成失败".to_string());
};
user_visible_external_generation_error(job_kind, last_error_message)
.unwrap_or_else(|| "生成失败".to_string())
}
fn mark_job_failed(message: &mut EditorAgentMessage, error: String) {
let tool_call = message
.tool_call
@@ -219,6 +238,7 @@ pub(crate) fn reconcile_completed_editor_agent_tool_call_for_test(
#[cfg(test)]
mod tests {
use super::*;
use platform_editor_agent::agent::tools::generate_background_music::GenerateBackgroundMusicTool;
use platform_editor_agent::agent::tools::generate_sound_effect::GenerateSoundEffectTool;
use platform_editor_agent::framework::tool::Tool;
use serde_json::json;
@@ -310,6 +330,46 @@ mod tests {
assert_eq!(tool_call.external_job_id.as_deref(), Some("job-1"));
}
#[test]
fn failed_sound_effect_jobs_hide_internal_reason_codes_in_agent_tool_calls() {
for internal_error in [
"translation_invalid",
"elevenlabs_http_failed",
"ELEVENLABS_BASE_URL 未配置",
] {
let mut message = pending_tool_message();
mark_job_failed(
&mut message,
editor_agent_job_failure_message(
GenerateSoundEffectTool::NAME,
Some(internal_error.to_string()),
),
);
let tool_call = message.tool_call.expect("tool call should remain present");
assert_eq!(tool_call.status, EditorAgentToolCallStatus::Failed);
assert_eq!(
tool_call.error.as_deref(),
Some("音效生成失败,请稍后重试。")
);
assert!(
!message.text.contains(internal_error),
"Agent 会话文本不应保留内部失败码:{internal_error}"
);
}
}
#[test]
fn failed_background_music_jobs_use_the_same_user_visible_error_policy() {
assert_eq!(
editor_agent_job_failure_message(
GenerateBackgroundMusicTool::NAME,
Some("请求 Vector Engine 背景音乐任务失败:connection refused".to_string()),
),
"背景音乐生成失败,请稍后重试。"
);
}
#[test]
fn completed_legacy_tool_call_with_null_defaults_still_reconciles() {
let mut message: EditorAgentMessage = serde_json::from_value(json!({
@@ -86,6 +86,17 @@ impl EditorAgentToolError {
}
}
/// `get_external_generation_job_generated_artifacts` 的精简读取结果不带 `job_kind`
/// Agent 会话回填只能从已持久化的 tool name 恢复音频任务类型,以复用普通任务读取侧的
/// 用户可见错误投影。
pub(crate) fn editor_agent_audio_generation_job_kind(tool_name: &str) -> Option<&'static str> {
match tool_name {
GenerateSoundEffectTool::NAME => Some(EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND),
GenerateBackgroundMusicTool::NAME => Some(EDITOR_BACKGROUND_MUSIC_GENERATION_JOB_KIND),
_ => None,
}
}
impl Display for EditorAgentToolError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@@ -207,7 +207,10 @@ fn map_external_generation_job_status(
/// 任务的原始失败文本可能包含内部 provider、分割模型、请求端点或 HTTP 传输层细节,因此
/// 只能在普通用户读取边界替换成稳定文案。
/// 原始值仍留在任务记录、tracing 和后台审计路径中。
fn user_visible_external_generation_error(job_kind: &str, error: Option<String>) -> Option<String> {
pub(crate) fn user_visible_external_generation_error(
job_kind: &str,
error: Option<String>,
) -> Option<String> {
if job_kind == EDITOR_BACKGROUND_REMOVAL_JOB_KIND && error.is_some() {
return Some("去除背景失败,请稍后重试。".to_string());
}