mirror of
https://github.com/openai/codex.git
synced 2026-10-29 16:09:06 +00:00
Gate audio history by model input modalities (#33982)
## What changed - Add `audio` to the model input modalities exposed by the protocol and generated app-server schemas. - Preserve audio in prompts for models that advertise audio input, and replace historical audio with an omission marker for models that do not. ## Testing - Cover prompt normalization with and without audio support. - Extend the model-switching integration test to verify that switching from a multimodal model to a text-only model strips prior image and audio content. GitOrigin-RevId: c18e47ad602720216303ff9abb5bca4d508391ee
This commit is contained in:
@@ -135,9 +135,8 @@ impl ContextManager {
|
||||
}
|
||||
|
||||
/// Returns the history prepared for sending to the model. This applies a proper
|
||||
/// normalization and drops un-suited items. When `input_modalities` does not
|
||||
/// include `InputModality::Image`, images are stripped from messages and tool
|
||||
/// outputs.
|
||||
/// normalization and drops un-suited items. Unsupported image and audio content
|
||||
/// is stripped from messages and tool outputs according to `input_modalities`.
|
||||
pub(crate) fn for_prompt(mut self, input_modalities: &[InputModality]) -> Vec<ResponseItem> {
|
||||
self.normalize_history(input_modalities);
|
||||
self.items
|
||||
@@ -355,7 +354,7 @@ impl ContextManager {
|
||||
/// This function enforces a couple of invariants on the in-memory history:
|
||||
/// 1. every call (function/custom) has a corresponding output entry
|
||||
/// 2. every output has a corresponding call entry
|
||||
/// 3. when images are unsupported, image content is stripped from messages and tool outputs
|
||||
/// 3. unsupported image and audio content is stripped from messages and tool outputs
|
||||
fn normalize_history(&mut self, input_modalities: &[InputModality]) {
|
||||
// all function/tool calls must have a corresponding output
|
||||
normalize::ensure_call_outputs_present(&mut self.items);
|
||||
@@ -365,6 +364,9 @@ impl ContextManager {
|
||||
|
||||
// strip images when model does not support them
|
||||
normalize::strip_images_when_unsupported(input_modalities, &mut self.items);
|
||||
|
||||
// strip audio when model does not support it
|
||||
normalize::strip_audio_when_unsupported(input_modalities, &mut self.items);
|
||||
}
|
||||
|
||||
fn process_item(&self, item: &ResponseItem, policy: TruncationPolicy) -> ResponseItem {
|
||||
|
||||
@@ -471,7 +471,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
fn for_prompt_strips_media_when_model_does_not_support_it() {
|
||||
let items = vec![
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
@@ -484,6 +484,9 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
image_url: "https://example.com/img.png".to_string(),
|
||||
detail: Some(DEFAULT_IMAGE_DETAIL),
|
||||
},
|
||||
ContentItem::InputAudio {
|
||||
audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(),
|
||||
},
|
||||
ContentItem::InputText {
|
||||
text: "caption".to_string(),
|
||||
},
|
||||
@@ -554,6 +557,10 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
text: "image content omitted because you do not support image input"
|
||||
.to_string(),
|
||||
},
|
||||
ContentItem::InputText {
|
||||
text: "audio content omitted because you do not support audio input"
|
||||
.to_string(),
|
||||
},
|
||||
ContentItem::InputText {
|
||||
text: "caption".to_string(),
|
||||
},
|
||||
@@ -635,6 +642,21 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
} else {
|
||||
panic!("expected Message");
|
||||
}
|
||||
|
||||
let audio_message = ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputAudio {
|
||||
audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
};
|
||||
let with_audio = create_history_with_items(vec![audio_message.clone()]);
|
||||
assert_eq!(
|
||||
with_audio.for_prompt(&[InputModality::Text, InputModality::Audio]),
|
||||
vec![audio_message]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -12,6 +12,8 @@ use tracing::info;
|
||||
|
||||
const IMAGE_CONTENT_OMITTED_PLACEHOLDER: &str =
|
||||
"image content omitted because you do not support image input";
|
||||
const AUDIO_CONTENT_OMITTED_PLACEHOLDER: &str =
|
||||
"audio content omitted because you do not support audio input";
|
||||
// Changing this value would change model-visible IDs and invalidate prompt caches.
|
||||
const SYNTHETIC_OUTPUT_ID_NAMESPACE: Uuid = Uuid::from_u128(0x90d38d3e_6a5b_4d52_bfe2_2f1e634bfac4);
|
||||
|
||||
@@ -366,3 +368,26 @@ pub(crate) fn strip_images_when_unsupported(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip audio content from messages when the model does not support audio.
|
||||
/// When `input_modalities` contains `InputModality::Audio`, no stripping is performed.
|
||||
pub(crate) fn strip_audio_when_unsupported(
|
||||
input_modalities: &[InputModality],
|
||||
items: &mut [ResponseItem],
|
||||
) {
|
||||
if input_modalities.contains(&InputModality::Audio) {
|
||||
return;
|
||||
}
|
||||
|
||||
for item in items.iter_mut() {
|
||||
if let ResponseItem::Message { content, .. } = item {
|
||||
for content_item in content.iter_mut() {
|
||||
if matches!(content_item, ContentItem::InputAudio { .. }) {
|
||||
*content_item = ContentItem::InputText {
|
||||
text: AUDIO_CONTENT_OMITTED_PLACEHOLDER.to_string(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user