兼容图片工具的历史 gpt-image-2 模型别名
api-server 的图片类 Agent 工具在反序列化前把 gpt-image-2 / gpt-image-2-c 归一为 gpt-image-2.5,旧持久化工具调用不再被校验直接拒绝 EditImageTool 计价改用 gpt-image-2.5 业务模型常量,并新增别名归一单测、更新受影响的校验与计价断言
This commit is contained in:
@@ -28,7 +28,7 @@ use platform_editor_agent::agent::tools::generate_video::{
|
||||
};
|
||||
use platform_editor_agent::framework::error::PromptError;
|
||||
use platform_editor_agent::framework::tool::{Tool, ToolDyn, null_tool_args_as_missing};
|
||||
use platform_image::GPT_IMAGE_2_MODEL;
|
||||
use platform_image::{GPT_IMAGE_2_5_BUSINESS_NAME, GPT_IMAGE_2_C_MODEL, GPT_IMAGE_2_MODEL};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
@@ -224,6 +224,45 @@ fn parse_internal<T: DeserializeOwned>(
|
||||
})
|
||||
}
|
||||
|
||||
/// 把持久化的历史 GPT Image 2 模型别名(`gpt-image-2` / `gpt-image-2-c`)改写为当前
|
||||
/// 业务模型 `gpt-image-2.5`。
|
||||
///
|
||||
/// 平台层图片工具只接受 `gemini-3.1-flash-image-preview` 与 `gpt-image-2.5`,但历史会话
|
||||
/// 里仍可能保存着 `gpt-image-2` 的工具调用参数。这里在进入强类型校验前做一次别名归一,
|
||||
/// 既让旧调用继续可用,也把计价统一到当前业务模型档位。
|
||||
fn normalize_legacy_image_model_args(value: &Value) -> Value {
|
||||
let Some(model) = value.get("model").and_then(Value::as_str) else {
|
||||
return value.clone();
|
||||
};
|
||||
if !matches!(model.trim(), GPT_IMAGE_2_MODEL | GPT_IMAGE_2_C_MODEL) {
|
||||
return value.clone();
|
||||
}
|
||||
let mut normalized = value.clone();
|
||||
if let Some(fields) = normalized.as_object_mut() {
|
||||
fields.insert(
|
||||
"model".to_string(),
|
||||
Value::String(GPT_IMAGE_2_5_BUSINESS_NAME.to_string()),
|
||||
);
|
||||
}
|
||||
normalized
|
||||
}
|
||||
|
||||
/// 图片类工具的 `validate_args` 入口:先归一历史模型别名,再走强类型反序列化。
|
||||
fn parse_invalid_image_args<T: DeserializeOwned>(
|
||||
tool_name: &str,
|
||||
value: &Value,
|
||||
) -> Result<T, EditorAgentToolError> {
|
||||
parse_invalid_args(tool_name, &normalize_legacy_image_model_args(value))
|
||||
}
|
||||
|
||||
/// 图片类工具的 `pricing` 入口:即使调用方直接传入原始 args,也要先归一历史模型别名。
|
||||
fn parse_internal_image_args<T: DeserializeOwned>(
|
||||
label: &str,
|
||||
value: &Value,
|
||||
) -> Result<T, EditorAgentToolError> {
|
||||
parse_internal(label, &normalize_legacy_image_model_args(value))
|
||||
}
|
||||
|
||||
/// SFX V2 的显式 `duration: null` 表示自动时长,必须原样保留,不能被通用的
|
||||
/// “顶层 null 当缺省”兼容层恢复成手动 5 秒。但其余字段仍要走该兼容层:
|
||||
/// `model` 是无 Option 的 String,LLM 传 `model: null` 时若不剥离会直接反序列化失败。
|
||||
@@ -311,7 +350,7 @@ fn editor_agent_image_mud_points(
|
||||
|
||||
impl EditorAgentTool for GenerateImageTool {
|
||||
fn validate_args(&self, args: &Value) -> Result<Value, EditorAgentToolError> {
|
||||
let args: GenerateImageToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateImageToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
GenerateImageTool::validate_args(self, &args)
|
||||
.map_err(|error| EditorAgentToolError::invalid_args(error.to_string()))?;
|
||||
serialize_normalized_args(Self::NAME, &args)
|
||||
@@ -322,7 +361,7 @@ impl EditorAgentTool for GenerateImageTool {
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
args: &Value,
|
||||
) -> Result<u32, EditorAgentToolError> {
|
||||
let args: GenerateImageToolArgs = parse_internal("generate image args", args)?;
|
||||
let args: GenerateImageToolArgs = parse_internal_image_args("generate image args", args)?;
|
||||
Ok(editor_agent_image_mud_points(
|
||||
pricing,
|
||||
None,
|
||||
@@ -337,7 +376,8 @@ impl EditorAgentTool for GenerateImageTool {
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
) -> Result<EditorAgentToolCallDisplayArgs, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(pricing, args)?;
|
||||
let args: GenerateImageToolArgs = parse_internal("generate image display args", args)?;
|
||||
let args: GenerateImageToolArgs =
|
||||
parse_internal_image_args("generate image display args", args)?;
|
||||
let mut display_args = EditorAgentToolCallDisplayArgs::default();
|
||||
push_image_generation_display_args(
|
||||
&mut display_args,
|
||||
@@ -358,7 +398,7 @@ impl EditorAgentTool for GenerateImageTool {
|
||||
context: &EditorAgentPrepareJobContext<'_>,
|
||||
) -> Result<PreparedEditorAgentJob, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(context.pricing, args)?;
|
||||
let args: GenerateImageToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateImageToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
let title = args.prompt.clone();
|
||||
let reference_image_srcs = resolve_image_ids(&args.reference_image_ids, &self.context)?;
|
||||
let payload = EditorImageGenerationRequest {
|
||||
@@ -397,7 +437,7 @@ impl EditorAgentTool for GenerateImageTool {
|
||||
args: &Value,
|
||||
result: &Value,
|
||||
) -> Result<String, EditorAgentToolError> {
|
||||
let args: GenerateImageToolArgs = parse_internal("generate image args", args)?;
|
||||
let args: GenerateImageToolArgs = parse_internal_image_args("generate image args", args)?;
|
||||
let result: EditorImageGenerationResult = parse_internal("generate image result", result)?;
|
||||
Ok(GenerateImageTool::format_execute_message(
|
||||
self, &args, &result,
|
||||
@@ -418,7 +458,7 @@ impl EditorAgentTool for GenerateImageTool {
|
||||
|
||||
impl EditorAgentTool for GenerateCharacterTool {
|
||||
fn validate_args(&self, args: &Value) -> Result<Value, EditorAgentToolError> {
|
||||
let args: GenerateCharacterToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateCharacterToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
GenerateCharacterTool::validate_args(self, &args)
|
||||
.map_err(|error| EditorAgentToolError::invalid_args(error.to_string()))?;
|
||||
serialize_normalized_args(Self::NAME, &args)
|
||||
@@ -429,7 +469,8 @@ impl EditorAgentTool for GenerateCharacterTool {
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
args: &Value,
|
||||
) -> Result<u32, EditorAgentToolError> {
|
||||
let args: GenerateCharacterToolArgs = parse_internal("generate character args", args)?;
|
||||
let args: GenerateCharacterToolArgs =
|
||||
parse_internal_image_args("generate character args", args)?;
|
||||
Ok(editor_agent_image_mud_points(
|
||||
pricing,
|
||||
Some("character"),
|
||||
@@ -445,7 +486,7 @@ impl EditorAgentTool for GenerateCharacterTool {
|
||||
) -> Result<EditorAgentToolCallDisplayArgs, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(pricing, args)?;
|
||||
let args: GenerateCharacterToolArgs =
|
||||
parse_internal("generate character display args", args)?;
|
||||
parse_internal_image_args("generate character display args", args)?;
|
||||
let mut display_args = EditorAgentToolCallDisplayArgs::default();
|
||||
push_image_generation_display_args(
|
||||
&mut display_args,
|
||||
@@ -466,7 +507,7 @@ impl EditorAgentTool for GenerateCharacterTool {
|
||||
context: &EditorAgentPrepareJobContext<'_>,
|
||||
) -> Result<PreparedEditorAgentJob, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(context.pricing, args)?;
|
||||
let args: GenerateCharacterToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateCharacterToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
let title = args.prompt.clone();
|
||||
let reference_image_srcs = resolve_image_ids(&args.reference_image_ids, &self.context)?;
|
||||
let payload = EditorImageGenerationRequest {
|
||||
@@ -505,7 +546,8 @@ impl EditorAgentTool for GenerateCharacterTool {
|
||||
args: &Value,
|
||||
result: &Value,
|
||||
) -> Result<String, EditorAgentToolError> {
|
||||
let args: GenerateCharacterToolArgs = parse_internal("generate character args", args)?;
|
||||
let args: GenerateCharacterToolArgs =
|
||||
parse_internal_image_args("generate character args", args)?;
|
||||
let result: EditorImageGenerationResult =
|
||||
parse_internal("generate character result", result)?;
|
||||
Ok(GenerateCharacterTool::format_execute_message(
|
||||
@@ -528,7 +570,7 @@ impl EditorAgentTool for GenerateCharacterTool {
|
||||
|
||||
impl EditorAgentTool for GenerateUiDesignTool {
|
||||
fn validate_args(&self, args: &Value) -> Result<Value, EditorAgentToolError> {
|
||||
let args: GenerateUiDesignToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateUiDesignToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
GenerateUiDesignTool::validate_args(self, &args)
|
||||
.map_err(|error| EditorAgentToolError::invalid_args(error.to_string()))?;
|
||||
serialize_normalized_args(Self::NAME, &args)
|
||||
@@ -539,7 +581,8 @@ impl EditorAgentTool for GenerateUiDesignTool {
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
args: &Value,
|
||||
) -> Result<u32, EditorAgentToolError> {
|
||||
let args: GenerateUiDesignToolArgs = parse_internal("generate UI design args", args)?;
|
||||
let args: GenerateUiDesignToolArgs =
|
||||
parse_internal_image_args("generate UI design args", args)?;
|
||||
Ok(editor_agent_image_mud_points(
|
||||
pricing,
|
||||
Some("ui-design"),
|
||||
@@ -555,7 +598,7 @@ impl EditorAgentTool for GenerateUiDesignTool {
|
||||
) -> Result<EditorAgentToolCallDisplayArgs, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(pricing, args)?;
|
||||
let args: GenerateUiDesignToolArgs =
|
||||
parse_internal("generate UI design display args", args)?;
|
||||
parse_internal_image_args("generate UI design display args", args)?;
|
||||
let mut display_args = EditorAgentToolCallDisplayArgs::default();
|
||||
push_image_generation_display_args(
|
||||
&mut display_args,
|
||||
@@ -576,7 +619,7 @@ impl EditorAgentTool for GenerateUiDesignTool {
|
||||
context: &EditorAgentPrepareJobContext<'_>,
|
||||
) -> Result<PreparedEditorAgentJob, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(context.pricing, args)?;
|
||||
let args: GenerateUiDesignToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateUiDesignToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
let title = args.prompt.clone();
|
||||
let reference_image_srcs = resolve_image_ids(&args.reference_image_ids, &self.context)?;
|
||||
let payload = EditorImageGenerationRequest {
|
||||
@@ -615,7 +658,8 @@ impl EditorAgentTool for GenerateUiDesignTool {
|
||||
args: &Value,
|
||||
result: &Value,
|
||||
) -> Result<String, EditorAgentToolError> {
|
||||
let args: GenerateUiDesignToolArgs = parse_internal("generate UI design args", args)?;
|
||||
let args: GenerateUiDesignToolArgs =
|
||||
parse_internal_image_args("generate UI design args", args)?;
|
||||
let result: EditorImageGenerationResult =
|
||||
parse_internal("generate UI design result", result)?;
|
||||
Ok(GenerateUiDesignTool::format_execute_message(
|
||||
@@ -638,7 +682,7 @@ impl EditorAgentTool for GenerateUiDesignTool {
|
||||
|
||||
impl EditorAgentTool for EditImageTool {
|
||||
fn validate_args(&self, args: &Value) -> Result<Value, EditorAgentToolError> {
|
||||
let args: EditImageToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: EditImageToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
if let Some(error) = EditImageTool::validate_args(self, &args) {
|
||||
return Err(EditorAgentToolError::invalid_args(error.to_string()));
|
||||
}
|
||||
@@ -650,11 +694,11 @@ impl EditorAgentTool for EditImageTool {
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
args: &Value,
|
||||
) -> Result<u32, EditorAgentToolError> {
|
||||
let _: EditImageToolArgs = parse_internal("edit image args", args)?;
|
||||
let _: EditImageToolArgs = parse_internal_image_args("edit image args", args)?;
|
||||
Ok(editor_agent_image_mud_points(
|
||||
pricing,
|
||||
Some("quick-edit"),
|
||||
GPT_IMAGE_2_MODEL,
|
||||
GPT_IMAGE_2_5_BUSINESS_NAME,
|
||||
Some("1K"),
|
||||
))
|
||||
}
|
||||
@@ -665,7 +709,7 @@ impl EditorAgentTool for EditImageTool {
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
) -> Result<EditorAgentToolCallDisplayArgs, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(pricing, args)?;
|
||||
let args: EditImageToolArgs = parse_internal("edit image display args", args)?;
|
||||
let args: EditImageToolArgs = parse_internal_image_args("edit image display args", args)?;
|
||||
let mut display_args = EditorAgentToolCallDisplayArgs::default();
|
||||
push_string_display_arg(&mut display_args, "prompt", "修改要求", args.prompt);
|
||||
push_string_display_arg(&mut display_args, "model", "模型", args.model);
|
||||
@@ -694,7 +738,7 @@ impl EditorAgentTool for EditImageTool {
|
||||
context: &EditorAgentPrepareJobContext<'_>,
|
||||
) -> Result<PreparedEditorAgentJob, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(context.pricing, args)?;
|
||||
let args: EditImageToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: EditImageToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
let source_reference_id = self
|
||||
.context
|
||||
.image_metadata(&args.object_image_id)
|
||||
@@ -739,7 +783,7 @@ impl EditorAgentTool for EditImageTool {
|
||||
args: &Value,
|
||||
result: &Value,
|
||||
) -> Result<String, EditorAgentToolError> {
|
||||
let args: EditImageToolArgs = parse_internal("edit image args", args)?;
|
||||
let args: EditImageToolArgs = parse_internal_image_args("edit image args", args)?;
|
||||
let result: EditorImageEditResult = parse_internal("edit image result", result)?;
|
||||
Ok(EditImageTool::format_execute_message(self, args, result))
|
||||
}
|
||||
@@ -785,7 +829,7 @@ fn resolve_icon_spec_reference_id<'a>(
|
||||
|
||||
impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
fn validate_args(&self, args: &Value) -> Result<Value, EditorAgentToolError> {
|
||||
let args: GenerateIconSpritesheetToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateIconSpritesheetToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
let args = GenerateIconSpritesheetTool::normalize_args(self, args)
|
||||
.map_err(|error| EditorAgentToolError::invalid_args(error.to_string()))?;
|
||||
resolve_icon_spec_reference_id(&self.context, &args.reference_image_id)?;
|
||||
@@ -798,7 +842,7 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
args: &Value,
|
||||
) -> Result<u32, EditorAgentToolError> {
|
||||
let args: GenerateIconSpritesheetToolArgs =
|
||||
parse_internal("generate icon spritesheet args", args)?;
|
||||
parse_internal_image_args("generate icon spritesheet args", args)?;
|
||||
Ok(editor_agent_image_mud_points(
|
||||
pricing,
|
||||
Some("icon"),
|
||||
@@ -814,7 +858,7 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
) -> Result<EditorAgentToolCallDisplayArgs, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(pricing, args)?;
|
||||
let args: GenerateIconSpritesheetToolArgs =
|
||||
parse_internal("generate icon spritesheet display args", args)?;
|
||||
parse_internal_image_args("generate icon spritesheet display args", args)?;
|
||||
let mut display_args = EditorAgentToolCallDisplayArgs::default();
|
||||
push_string_display_arg(
|
||||
&mut display_args,
|
||||
@@ -855,7 +899,7 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
context: &EditorAgentPrepareJobContext<'_>,
|
||||
) -> Result<PreparedEditorAgentJob, EditorAgentToolError> {
|
||||
let price_mud_points = self.pricing(context.pricing, args)?;
|
||||
let args: GenerateIconSpritesheetToolArgs = parse_invalid_args(Self::NAME, args)?;
|
||||
let args: GenerateIconSpritesheetToolArgs = parse_invalid_image_args(Self::NAME, args)?;
|
||||
let reference_id =
|
||||
resolve_icon_spec_reference_id(&self.context, &args.reference_image_id)?.to_string();
|
||||
let reference_image_srcs = resolve_image_ids(&args.reference_image_ids, &self.context)?;
|
||||
@@ -901,7 +945,7 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
result: &Value,
|
||||
) -> Result<String, EditorAgentToolError> {
|
||||
let args: GenerateIconSpritesheetToolArgs =
|
||||
parse_internal("generate icon spritesheet args", args)?;
|
||||
parse_internal_image_args("generate icon spritesheet args", args)?;
|
||||
let result: EditorIconSpritesheetResult =
|
||||
parse_internal("generate icon spritesheet result", result)?;
|
||||
Ok(GenerateIconSpritesheetTool::format_execute_message(
|
||||
@@ -1248,7 +1292,9 @@ mod tests {
|
||||
use spacetime_client::{EditorCanvasRecord, EditorCanvasViewportRecord};
|
||||
|
||||
use super::*;
|
||||
use crate::editor_generation_config::load_editor_generation_pricing_from_paths;
|
||||
use crate::editor_generation_config::{
|
||||
EDITOR_IMAGE_MODEL_GPT_IMAGE_2_5_GENERATION, load_editor_generation_pricing_from_paths,
|
||||
};
|
||||
|
||||
fn pricing() -> EditorGenerationPricingConfig {
|
||||
load_editor_generation_pricing_from_paths(None).expect("default editor pricing should load")
|
||||
@@ -1348,7 +1394,7 @@ mod tests {
|
||||
"unknown": "drop-me"
|
||||
}))
|
||||
.expect("typed UI validation should pass");
|
||||
assert_eq!(normalized["model"], GPT_IMAGE_2_MODEL);
|
||||
assert_eq!(normalized["model"], GPT_IMAGE_2_5_BUSINESS_NAME);
|
||||
assert_eq!(normalized["aspect_ratio"], "1:1");
|
||||
assert_eq!(normalized["image_size"], "1K");
|
||||
assert!(normalized.get("unknown").is_none());
|
||||
@@ -1493,6 +1539,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dyn_validation_normalizes_legacy_gpt_image_2_alias() {
|
||||
let edit = editor_agent_tool(EditImageTool::NAME, &context_with_image("image-1"))
|
||||
.expect("edit tool should resolve");
|
||||
let normalized = edit
|
||||
.validate_args(&json!({
|
||||
"object_image_id": "image-1",
|
||||
"prompt": "把背景换成夜晚",
|
||||
"model": GPT_IMAGE_2_MODEL
|
||||
}))
|
||||
.expect("legacy edit args should validate");
|
||||
assert_eq!(normalized["model"], GPT_IMAGE_2_5_BUSINESS_NAME);
|
||||
|
||||
let image = editor_agent_tool(GenerateImageTool::NAME, &EditorToolContext::default())
|
||||
.expect("image tool should resolve");
|
||||
let normalized = image
|
||||
.validate_args(&json!({
|
||||
"prompt": "生成图片",
|
||||
"model": GPT_IMAGE_2_C_MODEL,
|
||||
"image_size": "2K"
|
||||
}))
|
||||
.expect("legacy generation alias should validate");
|
||||
assert_eq!(normalized["model"], GPT_IMAGE_2_5_BUSINESS_NAME);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dyn_pricing_keeps_all_existing_tool_formulas() {
|
||||
let pricing = pricing();
|
||||
@@ -1510,6 +1581,7 @@ mod tests {
|
||||
),
|
||||
(
|
||||
GenerateUiDesignTool::NAME,
|
||||
// 历史持久化的 `gpt-image-2` 会先归一为 `gpt-image-2.5`,所以计价仍走当前档位。
|
||||
json!({ "prompt": "界面", "model": platform_image::GPT_IMAGE_2_MODEL, "image_size": "2K" }),
|
||||
5,
|
||||
),
|
||||
@@ -1552,9 +1624,10 @@ mod tests {
|
||||
#[test]
|
||||
fn dyn_pricing_uses_the_supplied_runtime_snapshot() {
|
||||
let mut pricing = pricing();
|
||||
// 历史 `gpt-image-2` 输入会归一为 `gpt-image-2.5-flare-c`,所以要改这个档位的价格。
|
||||
pricing
|
||||
.models
|
||||
.get_mut(GPT_IMAGE_2_MODEL)
|
||||
.get_mut(EDITOR_IMAGE_MODEL_GPT_IMAGE_2_5_GENERATION)
|
||||
.expect("gpt image pricing should exist")
|
||||
.prices
|
||||
.insert("2K".to_string(), 37);
|
||||
|
||||
Reference in New Issue
Block a user