Add explicit background control to image generation (#47484)

## What changed

Add the boolean `transparent_background` argument to `image_gen.imagegen` for generation and editing. Map `true` to `transparent` and `false` or omission to `opaque`, replacing the previous `auto` background setting.

Update tool guidance to request transparency for background removal and cutouts, and to preserve existing transparency during edits unless the user asks to change it.

## Testing

Add integration coverage for transparent, opaque, and omitted background arguments across generation, editing, and code-mode generation. Verify that `null`, string, and numeric values return a validation error without dispatching an image request.

GitOrigin-RevId: b10ac200a619c72cc1787da41b49d65d0cde05dd
This commit is contained in:
alicec-oai
2026-09-23 07:15:30 +00:00
committed by copyberry
parent 0a2eb4696c
commit 40eac3ce8a
4 changed files with 199 additions and 31 deletions
+9 -2
View File
@@ -62,6 +62,7 @@ async fn omitted_references_generate_with_fixed_defaults() {
request_for_call_args(
&ImagegenArgs {
prompt: "paint a moonlit lake".to_string(),
transparent_background: false,
referenced_image_paths: None,
num_last_images_to_include: None,
},
@@ -72,7 +73,7 @@ async fn omitted_references_generate_with_fixed_defaults() {
.expect("generation request should build"),
ImageRequest::Generate(ImageGenerationRequest {
prompt: "paint a moonlit lake".to_string(),
background: Some(ImageBackground::Auto),
background: Some(ImageBackground::Opaque),
model: "gpt-image-2".to_string(),
n: None,
quality: Some(ImageQuality::Auto),
@@ -151,6 +152,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
request_for_call_args(
&ImagegenArgs {
prompt: "change the lighting".to_string(),
transparent_background: false,
referenced_image_paths: None,
num_last_images_to_include: Some(5),
},
@@ -172,6 +174,7 @@ async fn recent_image_fallback_rejects_file_backed_image_in_requested_window() {
let error = request_for_call_args(
&ImagegenArgs {
prompt: "change the lighting".to_string(),
transparent_background: false,
referenced_image_paths: None,
num_last_images_to_include: Some(1),
},
@@ -208,6 +211,7 @@ async fn recent_image_fallback_rejects_file_backed_tool_output_in_requested_wind
let error = request_for_call_args(
&ImagegenArgs {
prompt: "change the lighting".to_string(),
transparent_background: false,
referenced_image_paths: None,
num_last_images_to_include: Some(1),
},
@@ -252,6 +256,7 @@ async fn conflicting_image_selectors_return_tool_error() {
let error = request_for_call_args(
&ImagegenArgs {
prompt: "change the lighting".to_string(),
transparent_background: false,
referenced_image_paths: Some(vec![
"/tmp/image.png"
.try_into()
@@ -276,6 +281,7 @@ async fn too_many_referenced_image_paths_return_tool_error() {
let error = request_for_call_args(
&ImagegenArgs {
prompt: "change the lighting".to_string(),
transparent_background: false,
referenced_image_paths: Some(
(0..6)
.map(|index| {
@@ -304,6 +310,7 @@ async fn recent_image_fallback_requires_requested_count() {
let error = request_for_call_args(
&ImagegenArgs {
prompt: "change the lighting".to_string(),
transparent_background: false,
referenced_image_paths: None,
num_last_images_to_include: Some(2),
},
@@ -430,7 +437,7 @@ fn expected_edit_request(prompt: &str, images: &[&str]) -> ImageEditRequest {
})
.collect(),
prompt: prompt.to_string(),
background: Some(ImageBackground::Auto),
background: Some(ImageBackground::Opaque),
model: "gpt-image-2".to_string(),
n: None,
quality: Some(ImageQuality::Auto),
+11 -3
View File
@@ -89,6 +89,9 @@ impl ImageGenerationTool {
#[serde(deny_unknown_fields)]
struct ImagegenArgs {
prompt: String,
/// Whether the output should have a transparent background. Defaults to false.
#[serde(default)]
transparent_background: bool,
#[schemars(length(max = 5))]
referenced_image_paths: Option<Vec<AbsolutePathBuf>>,
#[schemars(range(min = 1, max = 5))]
@@ -120,7 +123,7 @@ impl<'call> ToolExecutor<ToolCall<'call>> for ImageGenerationTool {
ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME)
}
/// Advertises the model contract: a rewritten prompt and optional edit references.
/// Advertises a rewritten prompt, background choice, and optional edit references.
fn spec(&self) -> ToolSpec {
imagegen_tool_spec()
}
@@ -427,6 +430,11 @@ async fn request_for_call_args(
history: &[ResponseItem],
environments: &[ToolEnvironment<'_>],
) -> Result<ImageRequest, FunctionCallError> {
let background = if args.transparent_background {
ImageBackground::Transparent
} else {
ImageBackground::Opaque
};
let paths = args.referenced_image_paths.as_deref().unwrap_or_default();
if paths.len() > MAX_EDIT_IMAGES {
return Err(FunctionCallError::RespondToModel(format!(
@@ -437,7 +445,7 @@ async fn request_for_call_args(
(true, None) => {
return Ok(ImageRequest::Generate(ImageGenerationRequest {
prompt: args.prompt.clone(),
background: Some(ImageBackground::Auto),
background: Some(background),
model: IMAGE_MODEL.to_string(),
n: None,
quality: Some(ImageQuality::Auto),
@@ -481,7 +489,7 @@ async fn request_for_call_args(
Ok(ImageRequest::Edit(ImageEditRequest {
images,
prompt: args.prompt.clone(),
background: Some(ImageBackground::Auto),
background: Some(background),
model: IMAGE_MODEL.to_string(),
n: None,
quality: Some(ImageQuality::Auto),