diff --git a/crates/lingua/src/providers/google/adapter.rs b/crates/lingua/src/providers/google/adapter.rs index 6133d954..b4113847 100644 --- a/crates/lingua/src/providers/google/adapter.rs +++ b/crates/lingua/src/providers/google/adapter.rs @@ -31,6 +31,11 @@ use crate::universal::{ UniversalStreamChunk, UniversalUsage, UserContent, }; +/// Internal extras key used to stash generationConfig fields that don't have +/// universal equivalents (candidateCount, speechConfig, responseModalities, etc.) +/// so they survive a roundtrip through the universal format. +const GENERATION_CONFIG_EXTRAS_KEY: &str = "_generationConfigExtras"; + /// Adapter for Google AI GenerateContent API. pub struct GoogleAdapter; @@ -67,37 +72,49 @@ impl ProviderAdapter for GoogleAdapter { .map_err(|e| TransformError::ToUniversalFailed(e.to_string()))?; // Extract params from generationConfig (now typed in params struct) - let (temperature, top_p, top_k, max_tokens, stop, reasoning) = - if let Some(config) = &typed_params.generation_config { - let max_tokens = config.max_output_tokens; - // Convert Google's thinkingConfig to ReasoningConfig - // thinkingBudget: 0 means disabled - let reasoning = config.thinking_config.as_ref().map(|tc| { - let is_disabled = tc.thinking_budget == Some(0); - let budget_tokens = tc.thinking_budget; - // Derive effort from budget_tokens - let effort = budget_tokens - .map(|b| crate::universal::reasoning::budget_to_effort(b, None)); - crate::universal::ReasoningConfig { - enabled: Some(!is_disabled), - effort, - budget_tokens, - canonical: Some(crate::universal::ReasoningCanonical::BudgetTokens), - ..Default::default() - } - }); - let stop = config.stop_sequences.clone().filter(|s| !s.is_empty()); - ( - config.temperature, - config.top_p, - config.top_k, - max_tokens, - stop, - reasoning, - ) - } else { - (None, None, None, None, None, None) - }; + let ( + temperature, + top_p, + top_k, + max_tokens, + stop, + reasoning, + seed, + presence_penalty, + frequency_penalty, + ) = if let Some(config) = &typed_params.generation_config { + let max_tokens = config.max_output_tokens; + // Convert Google's thinkingConfig to ReasoningConfig + // thinkingBudget: 0 means disabled + let reasoning = config.thinking_config.as_ref().map(|tc| { + let is_disabled = tc.thinking_budget == Some(0); + let budget_tokens = tc.thinking_budget; + // Derive effort from budget_tokens + let effort = + budget_tokens.map(|b| crate::universal::reasoning::budget_to_effort(b, None)); + crate::universal::ReasoningConfig { + enabled: Some(!is_disabled), + effort, + budget_tokens, + canonical: Some(crate::universal::ReasoningCanonical::BudgetTokens), + ..Default::default() + } + }); + let stop = config.stop_sequences.clone().filter(|s| !s.is_empty()); + ( + config.temperature, + config.top_p, + config.top_k, + max_tokens, + stop, + reasoning, + config.seed, + config.presence_penalty, + config.frequency_penalty, + ) + } else { + (None, None, None, None, None, None, None, None, None) + }; // Convert tools using typed conversions let tools = typed_params @@ -127,9 +144,9 @@ impl ProviderAdapter for GoogleAdapter { tools, tool_choice, response_format, - seed: None, // Google doesn't support seed - presence_penalty: None, - frequency_penalty: None, + seed, + presence_penalty, + frequency_penalty, stream: None, // Google uses endpoint-based streaming // New canonical fields - Google doesn't support most of these parallel_tool_calls: None, @@ -142,12 +159,50 @@ impl ProviderAdapter for GoogleAdapter { extras: Default::default(), }; - // Use extras captured automatically via #[serde(flatten)] - if !typed_params.extras.is_empty() { - params.extras.insert( - ProviderFormat::Google, - typed_params.extras.into_iter().collect(), - ); + // Collect Google-specific extras: serde-flatten unknowns + known fields + // that don't map to universal params. + let mut google_extras: Map = typed_params.extras.into_iter().collect(); + + if let Some(v) = typed_params.safety_settings { + google_extras.insert("safetySettings".into(), v); + } + if let Some(v) = typed_params.cached_content { + google_extras.insert("cachedContent".into(), Value::String(v)); + } + + // Preserve generationConfig fields that don't have universal equivalents. + // Serialize the whole config, strip fields we already handle, keep the rest. + if let Some(config) = &typed_params.generation_config { + if let Ok(Value::Object(mut config_map)) = serde_json::to_value(config) { + // Remove fields handled canonically above + for key in &[ + "temperature", + "topP", + "topK", + "maxOutputTokens", + "stopSequences", + "thinkingConfig", + "responseMimeType", + "responseSchema", + "seed", + "presencePenalty", + "frequencyPenalty", + ] { + config_map.remove(*key); + } + // Remove null entries + config_map.retain(|_, v| !v.is_null()); + if !config_map.is_empty() { + google_extras.insert( + GENERATION_CONFIG_EXTRAS_KEY.into(), + Value::Object(config_map), + ); + } + } + } + + if !google_extras.is_empty() { + params.extras.insert(ProviderFormat::Google, google_extras); } Ok(UniversalRequest { @@ -220,13 +275,23 @@ impl ProviderAdapter for GoogleAdapter { .map(|r| !r.is_effectively_disabled()) .unwrap_or(false); let has_response_format = req.params.response_format.is_some(); + let has_gen_config_extras = req + .params + .extras + .get(&ProviderFormat::Google) + .and_then(|e| e.get(GENERATION_CONFIG_EXTRAS_KEY)) + .is_some(); let has_params = req.params.temperature.is_some() || req.params.top_p.is_some() || req.params.top_k.is_some() || req.params.output_token_budget().is_some() || req.params.stop.is_some() + || req.params.seed.is_some() + || req.params.presence_penalty.is_some() + || req.params.frequency_penalty.is_some() || has_reasoning - || has_response_format; + || has_response_format + || has_gen_config_extras; if has_params { // Convert ReasoningConfig to Google's thinkingConfig @@ -254,6 +319,9 @@ impl ProviderAdapter for GoogleAdapter { max_output_tokens: req.params.output_token_budget(), stop_sequences, thinking_config, + seed: req.params.seed, + presence_penalty: req.params.presence_penalty, + frequency_penalty: req.params.frequency_penalty, ..Default::default() }; @@ -262,11 +330,30 @@ impl ProviderAdapter for GoogleAdapter { apply_response_format_to_generation_config(&mut config, format); } - obj.insert( - "generationConfig".into(), - serde_json::to_value(config) - .map_err(|e| TransformError::SerializationFailed(e.to_string()))?, - ); + let mut config_value = serde_json::to_value(config) + .map_err(|e| TransformError::SerializationFailed(e.to_string()))?; + + // Strip null entries (e.g. responseSchema which is Box> + // and always serializes, producing null when None) + if let Some(config_map) = config_value.as_object_mut() { + config_map.retain(|_, v| !v.is_null()); + } + + // Merge back generationConfig extras (candidateCount, speechConfig, etc.) + if let Some(extras) = req.params.extras.get(&ProviderFormat::Google) { + if let Some(Value::Object(config_extras)) = extras.get(GENERATION_CONFIG_EXTRAS_KEY) + { + if let Some(config_map) = config_value.as_object_mut() { + for (k, v) in config_extras { + if !config_map.contains_key(k) { + config_map.insert(k.clone(), v.clone()); + } + } + } + } + } + + obj.insert("generationConfig".into(), config_value); } // Add tools if present @@ -296,6 +383,10 @@ impl ProviderAdapter for GoogleAdapter { // Merge back provider-specific extras (only for Google) if let Some(extras) = req.params.extras.get(&ProviderFormat::Google) { for (k, v) in extras { + // _generationConfigExtras is merged into generationConfig above + if k == GENERATION_CONFIG_EXTRAS_KEY { + continue; + } // Don't overwrite canonical fields we already handled if !obj.contains_key(k) { obj.insert(k.clone(), v.clone()); @@ -526,6 +617,11 @@ mod tests { use super::*; use crate::serde_json::json; + /// Parse a reconstructed JSON Value back into typed GoogleParams for assertions. + fn parse_params(value: &Value) -> GoogleParams { + serde_json::from_value(value.clone()).expect("should parse as GoogleParams") + } + #[test] fn test_google_detect_request() { let adapter = GoogleAdapter; @@ -553,7 +649,6 @@ mod tests { }); let universal = adapter.request_to_universal(payload).unwrap(); - // Use approximate comparison due to f32->f64 conversion precision assert!((universal.params.temperature.unwrap() - 0.7).abs() < 0.001); assert_eq!( universal.params.token_budget, @@ -561,8 +656,9 @@ mod tests { ); let reconstructed = adapter.request_from_universal(&universal).unwrap(); - assert!(reconstructed.get("contents").is_some()); - assert!(reconstructed.get("generationConfig").is_some()); + let params = parse_params(&reconstructed); + assert!(params.contents.is_some()); + assert!(params.generation_config.is_some()); } #[test] @@ -577,10 +673,113 @@ mod tests { }); let universal = adapter.request_to_universal(payload).unwrap(); - // safetySettings is a known key, so it won't be in extras - // but it should be preserved through serialization + let reconstructed = adapter.request_from_universal(&universal).unwrap(); + let params = parse_params(&reconstructed); + assert!(params.contents.is_some()); + assert_eq!( + params.safety_settings, + Some(json!([{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"}])) + ); + } + + #[test] + fn test_google_roundtrip_generation_config_extras() { + let adapter = GoogleAdapter; + let payload = json!({ + "contents": [{"role": "user", "parts": [{"text": "hi"}]}], + "generationConfig": { + "temperature": 0.5, + "seed": 42, + "presencePenalty": 0.3, + "frequencyPenalty": 0.7, + "candidateCount": 2, + "responseLogprobs": true, + "responseModalities": ["TEXT"], + "mediaResolution": "MEDIA_RESOLUTION_LOW" + } + }); + + let universal = adapter.request_to_universal(payload).unwrap(); + assert_eq!(universal.params.seed, Some(42)); + assert!((universal.params.presence_penalty.unwrap() - 0.3).abs() < 0.001); + assert!((universal.params.frequency_penalty.unwrap() - 0.7).abs() < 0.001); let reconstructed = adapter.request_from_universal(&universal).unwrap(); - assert!(reconstructed.get("contents").is_some()); + let params = parse_params(&reconstructed); + let config = params.generation_config.unwrap(); + assert_eq!(config.seed, Some(42)); + assert_eq!(config.candidate_count, Some(2)); + assert_eq!(config.response_logprobs, Some(true)); + assert!(config.response_modalities.is_some()); + assert!(config.media_resolution.is_some()); + } + + #[test] + fn test_google_roundtrip_cached_content() { + let adapter = GoogleAdapter; + let payload = json!({ + "contents": [{"role": "user", "parts": [{"text": "hi"}]}], + "cachedContent": "cachedContents/abc123" + }); + + let universal = adapter.request_to_universal(payload).unwrap(); + let reconstructed = adapter.request_from_universal(&universal).unwrap(); + let params = parse_params(&reconstructed); + assert_eq!(params.cached_content, Some("cachedContents/abc123".into())); + } + + #[test] + fn test_google_openai_google_roundtrip_seed_and_penalties() { + use crate::processing::adapters::adapter_for_format; + use crate::providers::openai::params::OpenAIChatParams; + + let google = adapter_for_format(ProviderFormat::Google).unwrap(); + let openai = adapter_for_format(ProviderFormat::ChatCompletions).unwrap(); + + let google_payload = json!({ + "model": "gemini-2.0-flash", + "contents": [{"role": "user", "parts": [{"text": "hello"}]}], + "generationConfig": { + "seed": 42, + "presencePenalty": 0.5, + "frequencyPenalty": 0.8, + "candidateCount": 2, + "responseModalities": ["TEXT"], + "temperature": 0.7 + }, + "safetySettings": [{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_ONLY_HIGH"}], + "cachedContent": "cachedContents/test123" + }); + + // Google -> Universal + let universal = google.request_to_universal(google_payload.clone()).unwrap(); + assert_eq!(universal.params.seed, Some(42)); + assert!((universal.params.presence_penalty.unwrap() - 0.5).abs() < 0.001); + assert!((universal.params.frequency_penalty.unwrap() - 0.8).abs() < 0.001); + + // Universal -> OpenAI ChatCompletions + let openai_payload = openai.request_from_universal(&universal).unwrap(); + let openai_params: OpenAIChatParams = + serde_json::from_value(openai_payload.clone()).unwrap(); + assert_eq!(openai_params.seed, Some(42)); + + // OpenAI -> Universal (back) + let universal_2 = openai.request_to_universal(openai_payload).unwrap(); + assert_eq!(universal_2.params.seed, Some(42)); + + // Universal -> Google (back) + let google_out = google.request_from_universal(&universal_2).unwrap(); + let params = parse_params(&google_out); + let config = params.generation_config.unwrap(); + // Universal params survive cross-provider roundtrip + assert_eq!(config.seed, Some(42)); + assert!(config.presence_penalty.is_some()); + assert!(config.frequency_penalty.is_some()); + assert!(config.temperature.is_some()); + + // Google-specific extras (candidateCount, responseModalities, safetySettings, + // cachedContent) are stored under ProviderFormat::Google in extras, so they + // survive a Google->Google roundtrip but NOT a cross-provider trip through OpenAI. + // This is expected: OpenAI doesn't know about Google's candidateCount etc. } } diff --git a/crates/lingua/src/providers/google/convert.rs b/crates/lingua/src/providers/google/convert.rs index ca633d88..6792bf0f 100644 --- a/crates/lingua/src/providers/google/convert.rs +++ b/crates/lingua/src/providers/google/convert.rs @@ -55,12 +55,7 @@ impl TryFromLLM for Message { type Error = ConvertError; fn try_from(content: GoogleContent) -> Result { - let role = content - .role - .as_deref() - .ok_or(ConvertError::MissingRequiredField { - field: "role".to_string(), - })?; + let role = content.role.as_deref().unwrap_or("user"); let parts = content.parts.ok_or(ConvertError::MissingRequiredField { field: "parts".to_string(), })?; @@ -877,6 +872,23 @@ mod tests { assert_eq!(fc.name.as_deref(), Some("get_weather")); } + #[test] + fn test_google_content_to_message_missing_role_defaults_to_user() { + let content = GoogleContent { + role: None, + parts: Some(vec![text_part("Hello".to_string())]), + }; + + let message = >::try_from(content).unwrap(); + match message { + Message::User { content } => match content { + UserContent::String(s) => assert_eq!(s, "Hello"), + _ => panic!("Expected string content"), + }, + _ => panic!("Expected user message"), + } + } + #[test] fn test_google_to_universal_simple() { let request = GenerateContentRequest { diff --git a/crates/lingua/tests/fuzz/Makefile b/crates/lingua/tests/fuzz/Makefile index 8eb222d5..9b6e480f 100644 --- a/crates/lingua/tests/fuzz/Makefile +++ b/crates/lingua/tests/fuzz/Makefile @@ -1,4 +1,4 @@ -.PHONY: run stats prune run-openai stats-openai prune-openai run-responses stats-responses prune-responses run-anthropic stats-anthropic prune-anthropic run-two-arm stats-two-arm prune-two-arm run-three-arm stats-three-arm prune-three-arm refresh-snapshots +.PHONY: run stats prune run-openai stats-openai prune-openai run-responses stats-responses prune-responses run-anthropic stats-anthropic prune-anthropic run-google stats-google prune-google run-two-arm stats-two-arm prune-two-arm run-google-two-arm stats-google-two-arm prune-google-two-arm run-three-arm stats-three-arm prune-three-arm refresh-snapshots run: run-openai @@ -33,6 +33,24 @@ stats-anthropic: prune-anthropic: cargo test -p lingua --test fuzz anthropic_roundtrip_prune_snapshots -- --ignored --nocapture --exact +run-google: + cargo test -p lingua --test fuzz google_roundtrip -- --ignored --nocapture --exact + +stats-google: + cargo test -p lingua --test fuzz google_roundtrip_stats -- --ignored --nocapture --exact + +prune-google: + cargo test -p lingua --test fuzz google_roundtrip_prune_snapshots -- --ignored --nocapture --exact + +run-google-two-arm: + cargo test -p lingua --test fuzz chat_google_two_arm -- --ignored --nocapture --exact + +stats-google-two-arm: + cargo test -p lingua --test fuzz chat_google_two_arm_stats -- --ignored --nocapture --exact + +prune-google-two-arm: + cargo test -p lingua --test fuzz chat_google_two_arm_prune_snapshots -- --ignored --nocapture --exact + run-two-arm: cargo test -p lingua --test fuzz chat_anthropic_two_arm -- --ignored --nocapture --exact @@ -64,8 +82,14 @@ refresh-snapshots: -cargo test -p lingua --test fuzz chat_anthropic_two_arm_stats -- --ignored --nocapture --exact -cargo test -p lingua --test fuzz chat_responses_anthropic_three_arm -- --ignored --nocapture --exact -cargo test -p lingua --test fuzz chat_responses_anthropic_three_arm_stats -- --ignored --nocapture --exact + -cargo test -p lingua --test fuzz google_roundtrip -- --ignored --nocapture --exact + -cargo test -p lingua --test fuzz google_roundtrip_stats -- --ignored --nocapture --exact + -cargo test -p lingua --test fuzz chat_google_two_arm -- --ignored --nocapture --exact + -cargo test -p lingua --test fuzz chat_google_two_arm_stats -- --ignored --nocapture --exact cargo test -p lingua --test fuzz openai_roundtrip_prune_snapshots -- --ignored --nocapture --exact cargo test -p lingua --test fuzz responses_roundtrip_prune_snapshots -- --ignored --nocapture --exact cargo test -p lingua --test fuzz anthropic_roundtrip_prune_snapshots -- --ignored --nocapture --exact cargo test -p lingua --test fuzz chat_anthropic_two_arm_prune_snapshots -- --ignored --nocapture --exact cargo test -p lingua --test fuzz chat_responses_anthropic_three_arm_prune_snapshots -- --ignored --nocapture --exact + cargo test -p lingua --test fuzz google_roundtrip_prune_snapshots -- --ignored --nocapture --exact + cargo test -p lingua --test fuzz chat_google_two_arm_prune_snapshots -- --ignored --nocapture --exact diff --git a/crates/lingua/tests/fuzz/main.rs b/crates/lingua/tests/fuzz/main.rs index 37db5d19..1d24e312 100644 --- a/crates/lingua/tests/fuzz/main.rs +++ b/crates/lingua/tests/fuzz/main.rs @@ -25,6 +25,8 @@ const SNAPSHOT_SUITE_ANTHROPIC: &str = "anthropic-roundtrip"; const SNAPSHOT_SUITE_CHAT_ANTHROPIC_TWO_ARM: &str = "chat-anthropic-two-arm"; const SNAPSHOT_SUITE_CHAT_RESPONSES_ANTHROPIC_THREE_ARM: &str = "chat-responses-anthropic-three-arm"; +const SNAPSHOT_SUITE_GOOGLE: &str = "google-roundtrip"; +const SNAPSHOT_SUITE_CHAT_GOOGLE_TWO_ARM: &str = "chat-google-two-arm"; fn workspace_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) @@ -497,6 +499,123 @@ fn assert_responses_roundtrip_verbose(payload: &Value) -> Result { assert_provider_roundtrip_verbose(ProviderFormat::Responses, payload) } +fn assert_google_roundtrip(payload: &Value) -> Option> { + assert_provider_roundtrip(ProviderFormat::Google, payload) +} + +fn assert_google_roundtrip_verbose(payload: &Value) -> Result { + assert_provider_roundtrip_verbose(ProviderFormat::Google, payload) +} + +fn assert_chat_google_two_arm(payload: &Value) -> Option> { + let chat = adapter_for_format(ProviderFormat::ChatCompletions)?; + let google = adapter_for_format(ProviderFormat::Google)?; + + let universal_1 = match chat.request_to_universal(payload.clone()) { + Ok(v) => v, + Err(e) => return Some(vec![format!("chat->universal error: {e}")]), + }; + let google_1 = match google.request_from_universal(&universal_1) { + Ok(v) => v, + Err(e) => return Some(vec![format!("universal->google(1) error: {e}")]), + }; + let universal_2 = match google.request_to_universal(google_1.clone()) { + Ok(v) => v, + Err(e) => return Some(vec![format!("google->universal(1) error: {e}")]), + }; + let google_2 = match google.request_from_universal(&universal_2) { + Ok(v) => v, + Err(e) => return Some(vec![format!("universal->google(2) error: {e}")]), + }; + let universal_3 = match google.request_to_universal(google_2.clone()) { + Ok(v) => v, + Err(e) => return Some(vec![format!("google->universal(2) error: {e}")]), + }; + let chat_out = match chat.request_from_universal(&universal_3) { + Ok(v) => v, + Err(e) => return Some(vec![format!("universal->chat error: {e}")]), + }; + + let mut issues = Vec::new(); + let universal_1_json = serde_json::to_value(&universal_1).unwrap_or(Value::Null); + let universal_2_json = serde_json::to_value(&universal_2).unwrap_or(Value::Null); + append_diff_issues( + "universal(1->2):", + &universal_1_json, + &universal_2_json, + &mut issues, + ); + append_diff_issues("google(1->2):", &google_1, &google_2, &mut issues); + append_diff_issues("chat(final):", payload, &chat_out, &mut issues); + + Some(issues) +} + +fn assert_chat_google_two_arm_verbose(payload: &Value) -> Result { + let chat = adapter_for_format(ProviderFormat::ChatCompletions) + .ok_or_else(|| "No chat-completions adapter".to_string())?; + let google = adapter_for_format(ProviderFormat::Google) + .ok_or_else(|| "No google adapter".to_string())?; + + let universal_1 = chat + .request_to_universal(payload.clone()) + .map_err(|e| format!("chat->universal error: {e}"))?; + let google_1 = google + .request_from_universal(&universal_1) + .map_err(|e| format!("universal->google(1) error: {e}"))?; + let universal_2 = google + .request_to_universal(google_1.clone()) + .map_err(|e| format!("google->universal(1) error: {e}"))?; + let google_2 = google + .request_from_universal(&universal_2) + .map_err(|e| format!("universal->google(2) error: {e}"))?; + let universal_3 = google + .request_to_universal(google_2.clone()) + .map_err(|e| format!("google->universal(2) error: {e}"))?; + let chat_out = chat + .request_from_universal(&universal_3) + .map_err(|e| format!("universal->chat error: {e}"))?; + + let mut issues = Vec::new(); + let universal_1_json = serde_json::to_value(&universal_1).unwrap_or(Value::Null); + let universal_2_json = serde_json::to_value(&universal_2).unwrap_or(Value::Null); + append_diff_issues( + "universal(1->2):", + &universal_1_json, + &universal_2_json, + &mut issues, + ); + append_diff_issues("google(1->2):", &google_1, &google_2, &mut issues); + append_diff_issues("chat(final):", payload, &chat_out, &mut issues); + + if issues.is_empty() { + return Ok(true); + } + + Err(format!( + "chat->universal->google->universal->google->universal->chat mismatch:\n{}\n\n\ + chat_input: {}\n\ + universal_1: {}\n\ + google_1: {}\n\ + universal_2: {}\n\ + google_2: {}\n\ + universal_3: {}\n\ + chat_output: {}", + issues + .iter() + .map(|i| format!(" {i}")) + .collect::>() + .join("\n"), + as_pretty_json(payload), + as_pretty_json(&universal_1), + as_pretty_json(&google_1), + as_pretty_json(&universal_2), + as_pretty_json(&google_2), + as_pretty_json(&universal_3), + as_pretty_json(&chat_out), + )) +} + fn assert_chat_responses_anthropic_three_arm(payload: &Value) -> Option> { let chat = adapter_for_format(ProviderFormat::ChatCompletions)?; let responses = adapter_for_format(ProviderFormat::Responses)?; @@ -666,7 +785,9 @@ fn assert_chat_responses_anthropic_three_arm_verbose(payload: &Value) -> Result< // ============================================================================ mod strategies { - use super::schema_strategy::{load_openapi_definitions, strategy_for_schema_name}; + use super::schema_strategy::{ + load_discovery_definitions, load_openapi_definitions, strategy_for_schema_name, + }; use super::*; fn specs_dir() -> String { @@ -701,6 +822,50 @@ mod strategies { ) .boxed() } + + pub fn arb_google_payload() -> BoxedStrategy { + let defs = + load_discovery_definitions(&format!("{}/specs/google/discovery.json", specs_dir())); + strategy_for_schema_name("GenerateContentRequest", &defs) + .prop_filter("payload must parse as Google params", |payload| { + lingua::providers::google::try_parse_google(payload).is_ok() + }) + .prop_filter( + "contents must be valid Google API input (valid roles, non-empty parts with data)", + |payload| { + let part_has_data = |part: &Value| { + part.get("text").and_then(Value::as_str).is_some() + || part.get("inlineData").and_then(|d| d.get("data")).is_some() + || part + .get("fileData") + .and_then(|d| d.get("fileUri")) + .is_some() + || part + .get("functionCall") + .and_then(|d| d.get("name")) + .is_some() + || part + .get("functionResponse") + .and_then(|d| d.get("name")) + .is_some() + }; + payload + .get("contents") + .and_then(|c| c.as_array()) + .is_some_and(|contents| { + contents.iter().all(|entry| { + matches!( + entry.get("role").and_then(Value::as_str), + Some("user" | "model") + ) && entry.get("parts").and_then(|p| p.as_array()).is_some_and( + |parts| !parts.is_empty() && parts.iter().all(part_has_data), + ) + }) + }) + }, + ) + .boxed() + } } // ============================================================================ @@ -978,6 +1143,19 @@ fn chat_responses_anthropic_three_arm_saved_snapshots() { ); } +#[test] +fn google_roundtrip_saved_snapshots() { + run_saved_snapshots_suite(SNAPSHOT_SUITE_GOOGLE, assert_google_roundtrip); +} + +#[test] +fn chat_google_two_arm_saved_snapshots() { + run_saved_snapshots_suite( + SNAPSHOT_SUITE_CHAT_GOOGLE_TWO_ARM, + assert_chat_google_two_arm, + ); +} + /// Prune fuzz snapshots in a loop until stable: /// - remove malformed request/meta pairs /// - remove orphan meta files @@ -1018,6 +1196,21 @@ fn chat_responses_anthropic_three_arm_prune_snapshots() { ); } +#[test] +#[ignore] +fn google_roundtrip_prune_snapshots() { + run_prune_snapshots_suite(SNAPSHOT_SUITE_GOOGLE, assert_google_roundtrip); +} + +#[test] +#[ignore] +fn chat_google_two_arm_prune_snapshots() { + run_prune_snapshots_suite( + SNAPSHOT_SUITE_CHAT_GOOGLE_TWO_ARM, + assert_chat_google_two_arm, + ); +} + /// Fail on the first error with verbose output (input, output, diff). /// Use for debugging a specific issue. #[test] @@ -1085,6 +1278,32 @@ fn chat_responses_anthropic_three_arm() { ); } +#[test] +#[ignore] +fn google_roundtrip() { + run_fail_fast_suite( + SNAPSHOT_SUITE_GOOGLE, + "google", + "request-roundtrip", + strategies::arb_google_payload(), + assert_google_roundtrip, + assert_google_roundtrip_verbose, + ); +} + +#[test] +#[ignore] +fn chat_google_two_arm() { + run_fail_fast_suite( + SNAPSHOT_SUITE_CHAT_GOOGLE_TWO_ARM, + "chat-completions", + "chat-google-two-arm", + strategies::arb_openai_payload(), + assert_chat_google_two_arm, + assert_chat_google_two_arm_verbose, + ); +} + /// Run all cases and report an aggregated summary of unique issues. /// Use for triaging the full scope of failures. #[test] @@ -1151,3 +1370,29 @@ fn chat_responses_anthropic_three_arm_stats() { assert_chat_responses_anthropic_three_arm, ); } + +#[test] +#[ignore] +fn google_roundtrip_stats() { + run_stats_suite( + SNAPSHOT_SUITE_GOOGLE, + "google", + "request-roundtrip", + "Google roundtrip fuzz", + strategies::arb_google_payload(), + assert_google_roundtrip, + ); +} + +#[test] +#[ignore] +fn chat_google_two_arm_stats() { + run_stats_suite( + SNAPSHOT_SUITE_CHAT_GOOGLE_TWO_ARM, + "chat-completions", + "chat-google-two-arm", + "Chat->Google two-arm fuzz", + strategies::arb_openai_payload(), + assert_chat_google_two_arm, + ); +} diff --git a/crates/lingua/tests/fuzz/schema_strategy.rs b/crates/lingua/tests/fuzz/schema_strategy.rs index e1f0ff76..67532908 100644 --- a/crates/lingua/tests/fuzz/schema_strategy.rs +++ b/crates/lingua/tests/fuzz/schema_strategy.rs @@ -322,6 +322,20 @@ pub fn load_openapi_definitions(spec_path: &str) -> Map { .unwrap_or_else(|| panic!("No components.schemas in {}", spec_path)) } +/// Load a Google Discovery REST spec and extract the top-level `schemas` map. +pub fn load_discovery_definitions(spec_path: &str) -> Map { + let content = std::fs::read_to_string(spec_path) + .unwrap_or_else(|e| panic!("Failed to read spec at {}: {}", spec_path, e)); + + let spec: Value = serde_json::from_str(&content) + .unwrap_or_else(|e| panic!("Failed to parse spec at {}: {}", spec_path, e)); + + spec.get("schemas") + .and_then(|s| s.as_object()) + .cloned() + .unwrap_or_else(|| panic!("No schemas in {}", spec_path)) +} + /// Build a strategy for a named schema from the definitions map. pub fn strategy_for_schema_name( name: &str, diff --git a/mise.toml b/mise.toml index b646d7dd..804b084f 100644 --- a/mise.toml +++ b/mise.toml @@ -1,2 +1,6 @@ +[env] +_.file = ".env" + [tools] pnpm = "10.26.2" +nodejs = "22.15.0" diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-00616f16cb8603d4.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-00616f16cb8603d4.meta.json new file mode 100644 index 00000000..e907e6f9 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-00616f16cb8603d4.meta.json @@ -0,0 +1,7 @@ +{ + "issues": [ + "chat->universal error: Conversion to universal format failed: data did not match any variant of untagged enum ChatCompletionRequestMessageContent" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-00616f16cb8603d4.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-00616f16cb8603d4.request.json new file mode 100644 index 00000000..5c6b4743 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-00616f16cb8603d4.request.json @@ -0,0 +1,74 @@ +{ + "audio": { + "format": "flac", + "voice": ".!KNF2Zkn5,f h?,Ig" + }, + "function_call": "none", + "functions": [ + { + "name": "n5 8w!1.?3,,??W,!v?O.,y?2!,8?i?tF,!..I?2? .gEt" + } + ], + "logit_bias": {}, + "logprobs": false, + "max_completion_tokens": 652, + "messages": [ + { + "content": "?..7?68e pC?pf? t", + "name": "4.Xn..a1,B", + "refusal": null, + "role": "assistant" + }, + { + "content": [ + { + "text": null, + "type": null + } + ], + "name": "lOe?mtn3?zrU?,g!,MuKSzAYuG!.6,Q,.7.,Y?T6", + "role": "user" + } + ], + "modalities": null, + "model": "gpt-5-mini-2025-08-07", + "n": -860, + "presence_penalty": 54.71461087452379, + "reasoning_effort": "minimal", + "response_format": { + "type": "json_object" + }, + "seed": 374, + "store": true, + "stream_options": { + "include_obfuscation": true + }, + "tool_choice": { + "allowed_tools": { + "mode": "required", + "tools": [ + {}, + {} + ] + }, + "type": "allowed_tools" + }, + "tools": [ + { + "function": { + "name": "D!ZHX,3v,0,IH" + }, + "type": "function" + }, + { + "custom": { + "format": { + "type": "text" + }, + "name": ".,XfS!9GnFE8" + }, + "type": "custom" + } + ], + "web_search_options": {} +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-04afc1e70d055310.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-04afc1e70d055310.meta.json new file mode 100644 index 00000000..d117b649 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-04afc1e70d055310.meta.json @@ -0,0 +1,26 @@ +{ + "issues": [ + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.seed (965 -> null)", + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: params.tools.length (3 -> 1)", + "universal(1->2): changed: params.logprobs (false -> null)", + "universal(1->2): changed: params.parallel_tool_calls (true -> null)", + "universal(1->2): changed: messages.length (2 -> 1)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: web_search_options", + "chat(final): lost: seed", + "chat(final): lost: n", + "chat(final): lost: stream_options", + "chat(final): lost: modalities", + "chat(final): lost: logprobs", + "chat(final): lost: stream", + "chat(final): lost: function_call", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: audio", + "chat(final): changed: messages.length (2 -> 1)", + "chat(final): changed: tools.length (3 -> 1)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-04afc1e70d055310.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-04afc1e70d055310.request.json new file mode 100644 index 00000000..6705ffdf --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-04afc1e70d055310.request.json @@ -0,0 +1,64 @@ +{ + "audio": { + "format": "wav", + "voice": "W!W.s60,.!qRRHBY??AI" + }, + "function_call": "none", + "logprobs": false, + "max_completion_tokens": 17, + "messages": [ + { + "content": null, + "name": "hC,2.CDLKTNfe!AUmB2oq83k.0lO?q,lf?13O6.Lk.", + "role": "function" + }, + { + "content": "9.tf..W?t4a2.5..Na.?,4.Wmo43j,", + "name": "??,,0.K3,A. e", + "role": "system" + } + ], + "modalities": null, + "model": "ns!?6,y!.9.t f", + "n": -125, + "parallel_tool_calls": true, + "reasoning_effort": "low", + "seed": 965, + "stream": true, + "stream_options": null, + "tool_choice": { + "function": { + "name": "!p.Z.Y,7" + }, + "type": "function" + }, + "tools": [ + { + "function": { + "description": "n!n8.E9!,A ,3Xs 87?.yx", + "name": "IEii ,7tON6r1.zYHzLc? 6hJ!Y.l", + "parameters": {} + }, + "type": "function" + }, + { + "custom": { + "name": "a?Y.!?,pDvp,.m?0,!k" + }, + "type": "custom" + }, + { + "custom": { + "description": "?B!L ja.S?7m", + "name": "? 8mi?9jiV,Gf,4,3X.Rn?,?fL?O.,f34,!8x,!," + }, + "type": "custom" + } + ], + "web_search_options": { + "user_location": { + "approximate": {}, + "type": "approximate" + } + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-05048e3ab95af455.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-05048e3ab95af455.meta.json new file mode 100644 index 00000000..b99df818 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-05048e3ab95af455.meta.json @@ -0,0 +1,30 @@ +{ + "issues": [ + "universal(1->2): changed: messages.length (2 -> 1)", + "universal(1->2): changed: params.seed (-465 -> null)", + "universal(1->2): changed: params.top_logprobs (3 -> null)", + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: params.parallel_tool_calls (true -> null)", + "universal(1->2): changed: params.frequency_penalty (-14.320129888587324 -> null)", + "universal(1->2): changed: params.store (true -> null)", + "universal(1->2): changed: params.logprobs (false -> null)", + "universal(1->2): changed: params.tools.length (3 -> 1)", + "chat(final): lost: n", + "chat(final): lost: modalities", + "chat(final): lost: logprobs", + "chat(final): lost: store", + "chat(final): lost: top_logprobs", + "chat(final): lost: stream", + "chat(final): lost: function_call", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: stream_options", + "chat(final): lost: frequency_penalty", + "chat(final): lost: prediction", + "chat(final): lost: seed", + "chat(final): changed: messages.length (2 -> 1)", + "chat(final): changed: tools.length (3 -> 1)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-05048e3ab95af455.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-05048e3ab95af455.request.json new file mode 100644 index 00000000..ce186577 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-05048e3ab95af455.request.json @@ -0,0 +1,69 @@ +{ + "frequency_penalty": -14.320129888587324, + "function_call": { + "name": "X,Qr??v Bx T " + }, + "logprobs": false, + "max_completion_tokens": -165, + "messages": [ + { + "content": "8!,u0e ???,0.?mVFs", + "role": "tool", + "tool_call_id": "fX2.xOiU,.Uc.4i..,,Hdg?NqD2,,llBs ,..,d" + }, + { + "content": null, + "name": ",?w?4?2,", + "role": "function" + } + ], + "modalities": [ + "audio", + "audio" + ], + "model": "gpt-4-0314", + "n": 720, + "parallel_tool_calls": true, + "prediction": { + "content": "aX,U6fU,!qHk", + "type": "content" + }, + "reasoning_effort": "medium", + "seed": -465, + "stop": [ + " .a!Kk8!.. .oHXN?y,v.HJE9. ,7", + "5?obn?uA2k.HO!.,g8EE?.3, l,,,?,R,l2su", + "ThRiC.41r.,8?lnp9I,qqSJ,?e..eow.l..yV ?dW.hu89t", + "1u,!i7 a." + ], + "store": true, + "stream": true, + "stream_options": { + "include_obfuscation": false, + "include_usage": false + }, + "tools": [ + { + "custom": { + "description": "?Yp wJ ,T,5!a30m.?L?pLk,5,d,L.MgG7,! ", + "name": "R,.!05gtHST??Nf.?v?i.M.o,M,,50C,?C33a,qR" + }, + "type": "custom" + }, + { + "function": { + "name": "? . TEFt5. .i yoR17!7ZMWro,7.x.!", + "parameters": {}, + "strict": null + }, + "type": "function" + }, + { + "custom": { + "name": "A.,j5?x.3Fm43O" + }, + "type": "custom" + } + ], + "top_logprobs": 3 +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0680230824e2f95c.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0680230824e2f95c.meta.json new file mode 100644 index 00000000..548ffd21 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0680230824e2f95c.meta.json @@ -0,0 +1,24 @@ +{ + "issues": [ + "universal(1->2): changed: params.top_logprobs (571 -> null)", + "universal(1->2): changed: params.stream (false -> null)", + "universal(1->2): changed: messages[0].content[0].output (\"\" -> {\"output\":\"\"})", + "chat(final): lost: logit_bias", + "chat(final): lost: n", + "chat(final): lost: prediction", + "chat(final): lost: stream_options", + "chat(final): lost: top_logprobs", + "chat(final): lost: stream", + "chat(final): lost: reasoning_effort", + "chat(final): lost: function_call", + "chat(final): lost: modalities", + "chat(final): lost: web_search_options", + "chat(final): lost: messages[0].name", + "chat(final): added: messages[0].tool_call_id", + "chat(final): changed: messages[0].role (\"function\" -> \"tool\")", + "chat(final): changed: messages[0].content (null -> \"{\\\"output\\\":\\\"\\\"}\")", + "chat(final): changed: stop (\"qWI.O,G YvnirG?!..6Ne?a?Y ?B?.. Xg..\" -> [\"qWI.O,G YvnirG?!..6Ne?a?Y ?B?.. Xg..\"])" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0680230824e2f95c.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0680230824e2f95c.request.json new file mode 100644 index 00000000..a9361a04 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0680230824e2f95c.request.json @@ -0,0 +1,39 @@ +{ + "function_call": "auto", + "logit_bias": {}, + "messages": [ + { + "content": null, + "name": "rE?nA ..Iaz7,", + "role": "function" + } + ], + "modalities": [ + "audio", + "text", + "audio" + ], + "model": "gpt-3.5-turbo-0125", + "n": 79, + "prediction": { + "content": "6F???!I8E3q.?l s!HCJ6r,6ipo,a1,tfnL n?GLD..iI", + "type": "content" + }, + "reasoning_effort": null, + "stop": "qWI.O,G YvnirG?!..6Ne?a?Y ?B?.. Xg..", + "stream": false, + "stream_options": { + "include_obfuscation": false + }, + "top_logprobs": 571, + "web_search_options": { + "user_location": { + "approximate": { + "city": "U1?x4.0vCuuf5758r.92.", + "country": "KHvr?1?,.", + "region": ".P R.5a,U!j.uH3!?.ZayNP5.???UnH?z.,?s.,xa2ixO.B" + }, + "type": "approximate" + } + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-08c80730b339ee0d.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-08c80730b339ee0d.meta.json new file mode 100644 index 00000000..b6f8617e --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-08c80730b339ee0d.meta.json @@ -0,0 +1,21 @@ +{ + "issues": [ + "universal(1->2): changed: params.tools.length (3 -> 2)", + "universal(1->2): changed: params.logprobs (false -> null)", + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: messages.length (1 -> 0)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: functions", + "chat(final): lost: logit_bias", + "chat(final): lost: stream", + "chat(final): lost: stream_options", + "chat(final): lost: verbosity", + "chat(final): lost: tool_choice", + "chat(final): lost: logprobs", + "chat(final): changed: stop (\"f0OdJ.T,\" -> [\"f0OdJ.T,\"])", + "chat(final): changed: tools.length (3 -> 2)", + "chat(final): changed: messages.length (1 -> 0)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-08c80730b339ee0d.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-08c80730b339ee0d.request.json new file mode 100644 index 00000000..0906e86d --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-08c80730b339ee0d.request.json @@ -0,0 +1,74 @@ +{ + "functions": [ + { + "description": ".u.OqTee1,?", + "name": "?,k.z? ?kB8v!E.?,1tIg..4xR2i?", + "parameters": {} + }, + { + "description": "v,4!S?52L?", + "name": "?Vm?0L!,14.HRw?oUn8L!19!.5o.u,. ", + "parameters": {} + }, + { + "name": "S.911sUGo..,. .DxB,c? .?2 dY8W ?5?e,Q ,s?8li ?.a?", + "parameters": {} + }, + { + "description": "?44wV1?!Mn.D92z! ?,!iq8 G,r. ,?", + "name": ",q.?FT5?", + "parameters": {} + } + ], + "logit_bias": {}, + "logprobs": false, + "messages": [ + { + "content": "4 .!3.4D,pb! 0gNe!oyk?R47.jK", + "name": ".0?.??!.??i..V.Hi6.c. eLS8v2!x,3", + "role": "system" + } + ], + "model": "u1!7B!g.d?wEVv!Pt?CVD?11 2..M", + "stop": "f0OdJ.T,", + "stream": true, + "stream_options": { + "include_obfuscation": true, + "include_usage": true + }, + "tool_choice": { + "allowed_tools": { + "mode": "auto", + "tools": [ + {}, + {} + ] + }, + "type": "allowed_tools" + }, + "tools": [ + { + "custom": { + "description": "?70,.S?.,O,?3.N43X,L? 6 f", + "name": "n,kI,A" + }, + "type": "custom" + }, + { + "function": { + "name": "GhZ?,!1.j .3?!,.?4.6", + "parameters": {} + }, + "type": "function" + }, + { + "function": { + "name": " ", + "parameters": {}, + "strict": null + }, + "type": "function" + } + ], + "verbosity": "medium" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-09fa42838b8cfd7b.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-09fa42838b8cfd7b.meta.json new file mode 100644 index 00000000..5ec098b8 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-09fa42838b8cfd7b.meta.json @@ -0,0 +1,28 @@ +{ + "issues": [ + "universal(1->2): changed: messages.length (1 -> 0)", + "universal(1->2): changed: params.top_logprobs (-747 -> null)", + "universal(1->2): changed: params.seed (-877 -> null)", + "universal(1->2): changed: params.tools.length (2 -> 1)", + "universal(1->2): changed: params.presence_penalty (49.989426326552554 -> null)", + "universal(1->2): changed: params.store (false -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: n", + "chat(final): lost: store", + "chat(final): lost: functions", + "chat(final): lost: presence_penalty", + "chat(final): lost: audio", + "chat(final): lost: modalities", + "chat(final): lost: function_call", + "chat(final): lost: logit_bias", + "chat(final): lost: seed", + "chat(final): lost: top_logprobs", + "chat(final): lost: verbosity", + "chat(final): lost: web_search_options", + "chat(final): changed: messages.length (1 -> 0)", + "chat(final): changed: stop (\"O,.1.cI.s\" -> [\"O,.1.cI.s\"])", + "chat(final): changed: tools.length (2 -> 1)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-09fa42838b8cfd7b.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-09fa42838b8cfd7b.request.json new file mode 100644 index 00000000..2f5a7332 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-09fa42838b8cfd7b.request.json @@ -0,0 +1,87 @@ +{ + "audio": { + "format": "aac", + "voice": "?PoW!1.,c?0.x!??!!?9,rF!,o??,.!n1 ,W4Sl" + }, + "function_call": { + "name": "?,M1 ..,ig??." + }, + "functions": [ + { + "description": "Bb!2G9?!.xE6V5X,C7 3! ZYLib,!. nIC.", + "name": ". ,?j.O,, C?" + }, + { + "description": ",.oB! Qq,n,?!1y,BQ,G,Ym,! uRE5?", + "name": ",7K, 8Uu,r!43?Faoz?.,.,?.Mkgc k,!Us?r?uQ ?j ,44", + "parameters": {} + }, + { + "name": "?q .?,S .u yo a.!q,?4,h6H.ShnxC?.ONI?", + "parameters": {} + }, + { + "description": ",h?7?f4gkagr.4e!,cLW3", + "name": "?!!?8pSg21?4m 2,j,9y1D" + }, + { + "description": ".Z.,?4?,VF0Z,.0,?i,.??V26?tUl9m2Lsdyk,", + "name": "ti9,lBD.dU??KubFqF.9?, mub.?u..m0,Hxw4P" + } + ], + "logit_bias": {}, + "messages": [ + { + "content": [ + { + "text": ".K.SW,!En!oHS??z", + "type": "text" + }, + { + "text": "6c!lN!?. 6X?p ?QzS ! 6I", + "type": "text" + }, + { + "text": "5.O", + "type": "text" + } + ], + "role": "developer" + } + ], + "modalities": null, + "model": "gpt-4-turbo", + "n": -841, + "presence_penalty": 49.989426326552554, + "response_format": { + "type": "json_object" + }, + "seed": -877, + "stop": "O,.1.cI.s", + "store": false, + "tools": [ + { + "function": { + "name": "E" + }, + "type": "function" + }, + { + "custom": { + "name": ",0?,0,,?Q!.9Q,9?..9W, oC!???? 6.I9M.3bHy?CI0l?ht" + }, + "type": "custom" + } + ], + "top_logprobs": -747, + "verbosity": null, + "web_search_options": { + "search_context_size": "medium", + "user_location": { + "approximate": { + "country": "vD u?,x" + }, + "type": "approximate" + } + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0af37cb5b59d868b.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0af37cb5b59d868b.meta.json new file mode 100644 index 00000000..4895b408 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0af37cb5b59d868b.meta.json @@ -0,0 +1,7 @@ +{ + "issues": [ + "chat->universal error: Conversion to universal format failed: invalid type: null, expected a string" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0af37cb5b59d868b.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0af37cb5b59d868b.request.json new file mode 100644 index 00000000..8947d006 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0af37cb5b59d868b.request.json @@ -0,0 +1,98 @@ +{ + "function_call": { + "name": "5rg1,OEx.!gn?7cxP4,0dd?Q?,.f?62 X1..6p,8" + }, + "functions": [ + { + "name": ".b.?!0.4qmSKM!!5.. .RYUUY!,64.z.u!IVO" + }, + { + "name": " t!?!,aO,88a E??,3" + }, + { + "name": " ,ceGQ?.S!.. V!,.CN0?2?m??6??3?U" + }, + { + "name": "F?KE?5KP5NK..Ue.l! ??DxLX!", + "parameters": {} + } + ], + "logit_bias": {}, + "logprobs": true, + "messages": [ + { + "audio": { + "id": "I???" + }, + "function_call": null, + "refusal": "? .4YfA?!w, Q!!?FzK!.O.h.u?qCSu", + "role": "assistant", + "tool_calls": [ + { + "function": { + "arguments": null, + "name": null + }, + "id": "h8,Z?,wnL13??x.3?w,m?m54D eU?V!4qNa?cp?", + "type": "function" + } + ] + } + ], + "model": "tDu?!8TeZ.?.!weY!?D!,..AxNjG.", + "prediction": { + "content": "X?2?.Pj!.vQ,.!J7.zgWZW7 ,RA", + "type": "content" + }, + "presence_penalty": 93.829137528793, + "reasoning_effort": "high", + "response_format": { + "type": "json_object" + }, + "seed": 794, + "stop": [ + ".HL.p?b.,b!,!v?", + "XE?5X0V6Y4i1N?qI!4?P?Y,Y. .y3,7 9v,I?1,2", + "!?4m0s.!F2.?8,,E70!", + "1I,.Eu?0r?.?." + ], + "stream": false, + "stream_options": null, + "tool_choice": { + "allowed_tools": { + "mode": "required", + "tools": [ + {}, + {}, + {} + ] + }, + "type": "allowed_tools" + }, + "tools": [ + { + "custom": { + "description": ",. AT,7.ZGQ0vu0Z.? V.MU,Q3.Ht.?M ?6t,", + "name": "49CMAK!," + }, + "type": "custom" + }, + { + "custom": { + "description": "kD e.o", + "format": { + "type": "text" + }, + "name": "??q6,HB 2k?v,?!g M" + }, + "type": "custom" + }, + { + "custom": { + "description": "229y8G4C?.Gx,34s4te7,Fn?? .q.RI", + "name": ", " + }, + "type": "custom" + } + ] +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0f6021d1f374dc8f.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f6021d1f374dc8f.meta.json new file mode 100644 index 00000000..9560e951 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f6021d1f374dc8f.meta.json @@ -0,0 +1,30 @@ +{ + "issues": [ + "universal(1->2): changed: messages.length (1 -> 0)", + "universal(1->2): changed: params.parallel_tool_calls (false -> null)", + "universal(1->2): changed: params.logprobs (true -> null)", + "universal(1->2): changed: params.store (true -> null)", + "universal(1->2): changed: params.top_logprobs (-581 -> null)", + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.seed (573 -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: n", + "chat(final): lost: logit_bias", + "chat(final): lost: store", + "chat(final): lost: seed", + "chat(final): lost: verbosity", + "chat(final): lost: logprobs", + "chat(final): lost: max_tokens", + "chat(final): lost: stream", + "chat(final): lost: modalities", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: top_logprobs", + "chat(final): lost: function_call", + "chat(final): added: max_completion_tokens", + "chat(final): changed: messages.length (1 -> 0)", + "chat(final): changed: reasoning_effort (\"minimal\" -> \"low\")" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0f6021d1f374dc8f.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f6021d1f374dc8f.request.json new file mode 100644 index 00000000..28cf1575 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f6021d1f374dc8f.request.json @@ -0,0 +1,32 @@ +{ + "function_call": { + "name": "6." + }, + "logit_bias": {}, + "logprobs": true, + "max_tokens": -780, + "messages": [ + { + "content": [ + { + "text": "FnoiN.!,?JL6?1es?5?", + "type": "text" + } + ], + "role": "developer" + } + ], + "modalities": null, + "model": "gpt-4-turbo-preview", + "n": 353, + "parallel_tool_calls": false, + "reasoning_effort": "minimal", + "response_format": { + "type": "json_object" + }, + "seed": 573, + "store": true, + "stream": true, + "top_logprobs": -581, + "verbosity": null +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0f9d6200213116a0.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f9d6200213116a0.meta.json new file mode 100644 index 00000000..2ad3f311 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f9d6200213116a0.meta.json @@ -0,0 +1,25 @@ +{ + "issues": [ + "universal(1->2): added: params.tools[1].parameters.items", + "universal(1->2): changed: messages.length (3 -> 1)", + "universal(1->2): changed: params.presence_penalty (-78.0783387945037 -> null)", + "universal(1->2): changed: params.top_logprobs (-218 -> null)", + "universal(1->2): changed: params.stream (false -> null)", + "universal(1->2): changed: params.logprobs (false -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: presence_penalty", + "chat(final): lost: logprobs", + "chat(final): lost: logit_bias", + "chat(final): lost: audio", + "chat(final): lost: stream", + "chat(final): lost: stream_options", + "chat(final): lost: verbosity", + "chat(final): lost: top_logprobs", + "chat(final): lost: tools[1].function.strict", + "chat(final): added: tools[1].function.parameters.items", + "chat(final): changed: messages.length (3 -> 1)", + "chat(final): changed: stop (\".?,O,m.zJVSm bFX,.,.Rg.OB,\" -> [\".?,O,m.zJVSm bFX,.,.Rg.OB,\"])" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-0f9d6200213116a0.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f9d6200213116a0.request.json new file mode 100644 index 00000000..49fe0c4b --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-0f9d6200213116a0.request.json @@ -0,0 +1,48 @@ +{ + "audio": { + "format": "mp3", + "voice": "cedar" + }, + "logit_bias": {}, + "logprobs": false, + "max_completion_tokens": -236, + "messages": [ + { + "content": "m u.??a.21lI4,!?", + "name": "s.Q,..Y 2 UgW9L!.6Zq368,.?quY5O.PH .s,?V.1", + "role": "developer" + }, + { + "content": "!. p .?", + "name": "z.QMa9 JWyz,m?.Wc?g,I..!! Rl?3?arL?xjkG", + "role": "system" + }, + { + "content": "?iM..Rc,h2iyR.!!,?xP72.iK!M0,t W! lCgjP!ks,!ex,!, ", + "role": "assistant" + } + ], + "model": "n?36xW.,,!!..y?.d3.1NjounY ,F.", + "presence_penalty": -78.0783387945037, + "stop": ".?,O,m.zJVSm bFX,.,.Rg.OB,", + "stream": false, + "stream_options": {}, + "tools": [ + { + "function": { + "name": "B.qb?,2x1fK!!S?,V4E!JN?hf.,dU.G.WYDk8.rxI.Y0xuH.!?" + }, + "type": "function" + }, + { + "function": { + "name": "kcG!yMV ?3qs.3 !?J6,.?LzDX1HZ", + "parameters": {}, + "strict": null + }, + "type": "function" + } + ], + "top_logprobs": -218, + "verbosity": "high" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-16f47bea5be70dfb.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-16f47bea5be70dfb.meta.json new file mode 100644 index 00000000..15f916c0 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-16f47bea5be70dfb.meta.json @@ -0,0 +1,25 @@ +{ + "issues": [ + "universal(1->2): changed: messages.length (1 -> 0)", + "universal(1->2): changed: params.response_format.format_type (\"JsonSchema\" -> \"JsonObject\")", + "universal(1->2): changed: params.seed (438 -> null)", + "universal(1->2): changed: params.store (false -> null)", + "universal(1->2): changed: params.stream (false -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: stream", + "chat(final): lost: prediction", + "chat(final): lost: web_search_options", + "chat(final): lost: modalities", + "chat(final): lost: seed", + "chat(final): lost: store", + "chat(final): lost: functions", + "chat(final): lost: logit_bias", + "chat(final): lost: audio", + "chat(final): lost: function_call", + "chat(final): lost: response_format.json_schema", + "chat(final): changed: response_format.type (\"json_schema\" -> \"json_object\")", + "chat(final): changed: messages.length (1 -> 0)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-16f47bea5be70dfb.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-16f47bea5be70dfb.request.json new file mode 100644 index 00000000..3dbd306c --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-16f47bea5be70dfb.request.json @@ -0,0 +1,79 @@ +{ + "audio": { + "format": "opus", + "voice": "ballad" + }, + "function_call": "none", + "functions": [ + { + "description": "l.iLO??Nj?Mg ?,.", + "name": "Go?.k!.dNG.,6.64Gg.9cfMP3w, fCdg??lt??,,YpIm .Y.68", + "parameters": {} + }, + { + "name": "5Zi!tj," + }, + { + "description": "XKu!? z .pA3k,0.2D5za 08a!,,hqJ.5K?,w,.j?!6", + "name": "?D,l ,5H9Wb? ", + "parameters": {} + }, + { + "description": ",!.5??w6z,9qP.pR.i,?R.?,?paRrUJcdB ?OD.?4a5K.,", + "name": "g?t,9?G Via?v,", + "parameters": {} + } + ], + "logit_bias": {}, + "messages": [ + { + "content": [ + { + "text": "b..??.d97 ???E7z,b,1.???", + "type": "text" + } + ], + "role": "developer" + } + ], + "modalities": null, + "model": "gpt-4-turbo-2024-04-09", + "prediction": { + "content": [ + { + "text": ",fL.?,.O8H?!.Cy6rWC7pK!,e?", + "type": "text" + } + ], + "type": "content" + }, + "response_format": { + "json_schema": { + "description": "bAw Vx,T8z?uT", + "name": ".5UiXyS 6ege9yt?4.64.", + "strict": null + }, + "type": "json_schema" + }, + "seed": 438, + "store": false, + "stream": false, + "tools": [ + { + "function": { + "description": "S.NGOKC.?3,", + "name": "S q!gI06 e,w,?l,s??4.SG9W?pZS3,.V?ZE,.FsiZ,.9" + }, + "type": "function" + } + ], + "web_search_options": { + "user_location": { + "approximate": { + "country": "!.6.H 9q6!?Ex??,", + "timezone": "0.," + }, + "type": "approximate" + } + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-1e0526fa83578884.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-1e0526fa83578884.meta.json new file mode 100644 index 00000000..b399dd55 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-1e0526fa83578884.meta.json @@ -0,0 +1,21 @@ +{ + "issues": [ + "universal(1->2): changed: messages.length (2 -> 1)", + "universal(1->2): changed: params.logprobs (false -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: max_tokens", + "chat(final): lost: modalities", + "chat(final): lost: audio", + "chat(final): lost: functions", + "chat(final): lost: stream_options", + "chat(final): lost: logprobs", + "chat(final): lost: function_call", + "chat(final): lost: reasoning_effort", + "chat(final): lost: web_search_options", + "chat(final): lost: prediction", + "chat(final): changed: max_completion_tokens (-256 -> -818)", + "chat(final): changed: messages.length (2 -> 1)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-1e0526fa83578884.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-1e0526fa83578884.request.json new file mode 100644 index 00000000..794e7536 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-1e0526fa83578884.request.json @@ -0,0 +1,59 @@ +{ + "audio": { + "format": "flac", + "voice": "cedar" + }, + "function_call": { + "name": "8g!O zxpcI1H,?Z!?., ?W!mhI5!!.3.a" + }, + "functions": [ + { + "name": ".3Xd" + }, + { + "name": ".49Xv,?E.3!??4,fveEgNO76?Id.IpW.,0.n.OE.?v,.Q 4.", + "parameters": {} + }, + { + "description": ",nf2,8?7.goi?l", + "name": "5 ?u.????,,,.L?.,8d?.", + "parameters": {} + }, + { + "name": "k39Zt.,kv?.sR 8!rC,", + "parameters": {} + } + ], + "logprobs": false, + "max_completion_tokens": -256, + "max_tokens": -818, + "messages": [ + { + "content": "Q.F?,w1 .U,Sr3b??7u0, .D.?i.4y", + "name": " 84g4?e?RE,sJ.f?q.8,,w3CgtUE! ?.Ee", + "role": "function" + }, + { + "content": ".t?o,.Zy.E.1Ka.,cRz ..6??9?Pj.y,CJB. is2zQ", + "role": "system" + } + ], + "modalities": [ + "audio", + "audio" + ], + "model": "2g C3i", + "prediction": { + "content": "8,!!u?d,q3K,h?,? !!., ?t?!Xbpx5.,mB4 wI1Gks.,.vZ8", + "type": "content" + }, + "reasoning_effort": null, + "stream_options": null, + "tool_choice": { + "function": { + "name": "XZ 2Xq xr,?g9H6!.?7A?ncLt,y,K??" + }, + "type": "function" + }, + "web_search_options": {} +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-260034010784dcec.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-260034010784dcec.meta.json new file mode 100644 index 00000000..218a8e7f --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-260034010784dcec.meta.json @@ -0,0 +1,32 @@ +{ + "issues": [ + "universal(1->2): added: params.response_format.json_schema.schema.items", + "universal(1->2): changed: params.presence_penalty (42.567216991235036 -> null)", + "universal(1->2): changed: params.top_logprobs (778 -> null)", + "universal(1->2): changed: params.response_format.json_schema.strict (false -> null)", + "universal(1->2): changed: params.response_format.json_schema.name (\"! !c6Z4,a. 7?vRj??.?7,.GoO09Mh\" -> \"response\")", + "universal(1->2): changed: params.tools.length (2 -> 1)", + "universal(1->2): changed: params.parallel_tool_calls (true -> null)", + "universal(1->2): changed: params.logprobs (false -> null)", + "universal(1->2): changed: params.seed (476 -> null)", + "universal(1->2): changed: messages.length (1 -> 0)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: stream_options", + "chat(final): lost: function_call", + "chat(final): lost: presence_penalty", + "chat(final): lost: logit_bias", + "chat(final): lost: top_logprobs", + "chat(final): lost: logprobs", + "chat(final): lost: audio", + "chat(final): lost: functions", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: seed", + "chat(final): lost: response_format.json_schema.strict", + "chat(final): added: response_format.json_schema.schema.items", + "chat(final): changed: response_format.json_schema.name (\"! !c6Z4,a. 7?vRj??.?7,.GoO09Mh\" -> \"response\")", + "chat(final): changed: messages.length (1 -> 0)", + "chat(final): changed: tools.length (2 -> 1)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-260034010784dcec.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-260034010784dcec.request.json new file mode 100644 index 00000000..98d2ca31 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-260034010784dcec.request.json @@ -0,0 +1,68 @@ +{ + "audio": { + "format": "mp3", + "voice": "aNp??T! .53?zm,q 0O4c!ITUF i?7J..,U.?" + }, + "function_call": { + "name": "p5,!,X.E,,! Hi.xfo.uF?!?.?fRKv. .ce!l!!!" + }, + "functions": [ + { + "name": ",.! ,A,7w,", + "parameters": {} + } + ], + "logit_bias": {}, + "logprobs": false, + "max_completion_tokens": 39, + "messages": [ + { + "content": [ + { + "text": "54MSC!,,?X?5.j,?2n2??,Hh.??.3", + "type": "text" + }, + { + "text": " p!ox7,!..UBr?6,4 ,I4nG9O0Y?hkM5I Ak2.IS n.f7Cs.", + "type": "text" + }, + { + "text": "6?.ngc!???,t.333", + "type": "text" + } + ], + "name": "NjH?7g,9,r XSfG.XHuuC.P,,.zc?3DYqwz Y56?Wk..O T6H", + "role": "developer" + } + ], + "model": "gpt-4-turbo", + "parallel_tool_calls": true, + "presence_penalty": 42.567216991235036, + "response_format": { + "json_schema": { + "name": "! !c6Z4,a. 7?vRj??.?7,.GoO09Mh", + "schema": {}, + "strict": false + }, + "type": "json_schema" + }, + "seed": 476, + "stream_options": null, + "tools": [ + { + "function": { + "name": "pdU.vXy8bF", + "parameters": {}, + "strict": null + }, + "type": "function" + }, + { + "custom": { + "name": "n4!?0P?,,t5pIF 2?h,n3UP p3l.B27.?A" + }, + "type": "custom" + } + ], + "top_logprobs": 778 +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-2ee666392a1d7d74.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-2ee666392a1d7d74.meta.json new file mode 100644 index 00000000..1c1d2c38 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-2ee666392a1d7d74.meta.json @@ -0,0 +1,25 @@ +{ + "issues": [ + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: params.logprobs (false -> null)", + "universal(1->2): changed: params.parallel_tool_calls (false -> null)", + "universal(1->2): changed: params.frequency_penalty (46.10292730424837 -> null)", + "universal(1->2): changed: params.tool_choice.disable_parallel (true -> null)", + "universal(1->2): changed: messages[0].content[0].output (\"?S?W\" -> {\"output\":\"?S?W\"})", + "chat(final): lost: verbosity", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: logprobs", + "chat(final): lost: web_search_options", + "chat(final): lost: functions", + "chat(final): lost: frequency_penalty", + "chat(final): lost: n", + "chat(final): lost: stream_options", + "chat(final): lost: logit_bias", + "chat(final): lost: stream", + "chat(final): lost: modalities", + "chat(final): changed: messages[0].content (\"?S?W\" -> \"{\\\"output\\\":\\\"?S?W\\\"}\")" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-2ee666392a1d7d74.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-2ee666392a1d7d74.request.json new file mode 100644 index 00000000..8c9f090a --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-2ee666392a1d7d74.request.json @@ -0,0 +1,37 @@ +{ + "frequency_penalty": 46.10292730424837, + "functions": [ + { + "name": "vDrB3? ", + "parameters": {} + }, + { + "description": "8oiu?.G,yt28!o?,", + "name": "WDz,7Wr0,Tqu.VRWfyE." + } + ], + "logit_bias": {}, + "logprobs": false, + "messages": [ + { + "content": "?S?W", + "role": "tool", + "tool_call_id": "4.?,W!3 z e,.?, 8a.S .3C.t" + } + ], + "modalities": null, + "model": "3!4. .5..T?!!IkJ5wDX?9 ?V.?tg?8", + "n": 913, + "parallel_tool_calls": false, + "reasoning_effort": "high", + "response_format": { + "type": "text" + }, + "stream": true, + "stream_options": null, + "tool_choice": "none", + "verbosity": "low", + "web_search_options": { + "search_context_size": "high" + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-310ed34a7ad861b4.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-310ed34a7ad861b4.meta.json new file mode 100644 index 00000000..0d3dfa58 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-310ed34a7ad861b4.meta.json @@ -0,0 +1,21 @@ +{ + "issues": [ + "universal(1->2): changed: messages[0].content (\"\" -> [{\"text\":\"\",\"type\":\"text\"}])", + "universal(1->2): changed: messages[1].content[0].output (\"\" -> {\"output\":\"\"})", + "universal(1->2): changed: params.store (false -> null)", + "chat(final): lost: reasoning_effort", + "chat(final): lost: prediction", + "chat(final): lost: function_call", + "chat(final): lost: stream_options", + "chat(final): lost: store", + "chat(final): lost: n", + "chat(final): lost: messages[0].audio", + "chat(final): lost: messages[1].name", + "chat(final): added: messages[0].content", + "chat(final): added: messages[1].tool_call_id", + "chat(final): changed: messages[1].content (null -> \"{\\\"output\\\":\\\"\\\"}\")", + "chat(final): changed: messages[1].role (\"function\" -> \"tool\")" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-310ed34a7ad861b4.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-310ed34a7ad861b4.request.json new file mode 100644 index 00000000..adf092c2 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-310ed34a7ad861b4.request.json @@ -0,0 +1,29 @@ +{ + "function_call": "auto", + "messages": [ + { + "audio": { + "id": "mjK.?,NZp..6. N0? 8.?TUh.?r cvG!o.v!8xIa1k 7MpF6q5" + }, + "role": "assistant" + }, + { + "content": null, + "name": "Q,ovM?", + "role": "function" + } + ], + "model": ",YaO?5!H?z?x.4!SH F?,.X0TDS?.Px,!8A,,p", + "n": 673, + "prediction": { + "content": "c..o,T?x7D.?.m!?6!Q ?i? ", + "type": "content" + }, + "reasoning_effort": null, + "response_format": { + "type": "json_object" + }, + "store": false, + "stream_options": null, + "tool_choice": "none" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-33d13b18d488f747.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-33d13b18d488f747.meta.json new file mode 100644 index 00000000..3ebacff3 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-33d13b18d488f747.meta.json @@ -0,0 +1,28 @@ +{ + "issues": [ + "universal(1->2): changed: messages.length (3 -> 1)", + "universal(1->2): changed: params.seed (-695 -> null)", + "universal(1->2): changed: params.tools ([{\"description\":\" .?!BQ.,1S!83?.vy4\",\"kind\":\"custom\",\"name\":\"V3.Zj.hB?nc.L?Ql.,c4?f?x1uPsO.,,,.?ya?,4?e\"},{\"format\":{\"grammar\":{\"definition\":\"6.2xx?Wt,!c?A?f,2Za?O, L J3Jy.8,yp.\",\"syntax\":\"lark\"},\"type\":\"grammar\"},\"kind\":\"custom\",\"name\":\"264!F?j8F\"}] -> null)", + "universal(1->2): changed: params.top_logprobs (328 -> null)", + "universal(1->2): changed: params.parallel_tool_calls (true -> null)", + "universal(1->2): changed: params.presence_penalty (-17.30499605635964 -> null)", + "universal(1->2): changed: params.frequency_penalty (8.40294665126246 -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: top_logprobs", + "chat(final): lost: prediction", + "chat(final): lost: n", + "chat(final): lost: frequency_penalty", + "chat(final): lost: presence_penalty", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: seed", + "chat(final): lost: web_search_options", + "chat(final): lost: max_tokens", + "chat(final): lost: tools", + "chat(final): lost: functions", + "chat(final): lost: audio", + "chat(final): changed: messages.length (3 -> 1)", + "chat(final): changed: max_completion_tokens (-902 -> 586)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-33d13b18d488f747.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-33d13b18d488f747.request.json new file mode 100644 index 00000000..c4da079d --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-33d13b18d488f747.request.json @@ -0,0 +1,98 @@ +{ + "audio": { + "format": "opus", + "voice": "?,R.l..YBo?,j.!1a.CT!3es.P?W893?,Mjli" + }, + "frequency_penalty": 8.40294665126246, + "functions": [ + { + "description": "?e4W,DB?.?4VO??T?4e.EZ6.?D,5?5.v5j.D,pG5.uX ", + "name": "N,O?.,9,.d,.4 75fh,wl?04!.9 4e ..?4A", + "parameters": {} + }, + { + "name": "Dbjw??" + }, + { + "name": "!O,??", + "parameters": {} + }, + { + "name": "NXf.?S, 1OO2??R.o!vC,x.7?bS ku,,R?EigRM ?yUx,Y?SJz", + "parameters": {} + } + ], + "max_completion_tokens": -902, + "max_tokens": 586, + "messages": [ + { + "function_call": null, + "role": "assistant" + }, + { + "content": [ + { + "text": ",S!?6!s,AyB.Wn3T,us.g.3", + "type": "text" + } + ], + "name": "?S.3Y.?3?55Q.71Ot5RoF1,P60?.?bjE.?8??XM4?P?m,,?", + "role": "developer" + }, + { + "audio": null, + "content": ".A4.1.a,,4L.ni,8.ve,?qnsu6?P", + "name": "b5,!O!k l.HQJI!Bi0c!!,", + "role": "assistant" + } + ], + "model": "a,1?0?.7,BTr57g?W.N7 ,h3IJr S4 nc", + "n": 159, + "parallel_tool_calls": true, + "prediction": { + "content": [ + { + "text": ",6?Q ", + "type": "text" + }, + { + "text": "8x?x?9..N. !..Lm.? ?4.,??LuO.A5Uj", + "type": "text" + }, + { + "text": "DZBe .7 .?,J6Q?Qcy!.il52., ,!", + "type": "text" + } + ], + "type": "content" + }, + "presence_penalty": -17.30499605635964, + "seed": -695, + "tool_choice": "required", + "tools": [ + { + "custom": { + "description": " .?!BQ.,1S!83?.vy4", + "name": "V3.Zj.hB?nc.L?Ql.,c4?f?x1uPsO.,,,.?ya?,4?e" + }, + "type": "custom" + }, + { + "custom": { + "format": { + "grammar": { + "definition": "6.2xx?Wt,!c?A?f,2Za?O, L J3Jy.8,yp.", + "syntax": "lark" + }, + "type": "grammar" + }, + "name": "264!F?j8F" + }, + "type": "custom" + } + ], + "top_logprobs": 328, + "web_search_options": { + "search_context_size": "medium" + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-34ba485a1406f69c.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-34ba485a1406f69c.meta.json new file mode 100644 index 00000000..81f807b8 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-34ba485a1406f69c.meta.json @@ -0,0 +1,28 @@ +{ + "issues": [ + "universal(1->2): added: params.tools[0].parameters.items", + "universal(1->2): changed: messages[0].content[0].output (\"\" -> {\"output\":\"\"})", + "universal(1->2): changed: messages[1].content (\"F.sO.G!g?ba?8bDF F,Dd.Dg,.,Z?0,xA,5i?!!?..9oZecMc\" -> [{\"text\":\"F.sO.G!g?ba?8bDF F,Dd.Dg,.,Z?0,xA,5i?!!?..9oZecMc\",\"type\":\"text\"}])", + "universal(1->2): changed: params.presence_penalty (17.299183099790024 -> null)", + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.top_logprobs (672 -> null)", + "universal(1->2): changed: params.parallel_tool_calls (true -> null)", + "chat(final): lost: parallel_tool_calls", + "chat(final): lost: n", + "chat(final): lost: top_logprobs", + "chat(final): lost: web_search_options", + "chat(final): lost: prediction", + "chat(final): lost: presence_penalty", + "chat(final): lost: messages[0].name", + "chat(final): lost: messages[1].audio", + "chat(final): lost: messages[1].function_call", + "chat(final): lost: messages[1].refusal", + "chat(final): added: tools[0].function.parameters.items", + "chat(final): added: messages[0].tool_call_id", + "chat(final): changed: stop (\"!D.k?R?p L!N,qnWi,s 8.5,l L. k!h\" -> [\"!D.k?R?p L!N,qnWi,s 8.5,l L. k!h\"])", + "chat(final): changed: messages[0].content (null -> \"{\\\"output\\\":\\\"\\\"}\")", + "chat(final): changed: messages[0].role (\"function\" -> \"tool\")" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-34ba485a1406f69c.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-34ba485a1406f69c.request.json new file mode 100644 index 00000000..a4a10759 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-34ba485a1406f69c.request.json @@ -0,0 +1,60 @@ +{ + "messages": [ + { + "content": null, + "name": "??.p m,!6?00w.?..CQ8.!...uZ...s?g.A", + "role": "function" + }, + { + "audio": null, + "content": "F.sO.G!g?ba?8bDF F,Dd.Dg,.,Z?0,xA,5i?!!?..9oZecMc", + "function_call": { + "arguments": "D,e uLG3v A6XQ,5m", + "name": ",Pm?,,,BAg!N.khza.XW.jV?,5.Rp51,." + }, + "refusal": null, + "role": "assistant" + } + ], + "model": "7oKl,XCq8,fE,35,VV!,..?s.2 3kn.!I.Un.gWoI1", + "n": 905, + "parallel_tool_calls": true, + "prediction": { + "content": [ + { + "text": " vIfvB.l0.U?i", + "type": "text" + }, + { + "text": "..?l.!20eC..?Om0q!9?b.51lt,??m,.. .F,e,a", + "type": "text" + } + ], + "type": "content" + }, + "presence_penalty": 17.299183099790024, + "reasoning_effort": "low", + "response_format": { + "type": "json_object" + }, + "stop": "!D.k?R?p L!N,qnWi,s 8.5,l L. k!h", + "tool_choice": { + "function": { + "name": "5,?F,2x?Frh1 .Uq5!.m" + }, + "type": "function" + }, + "tools": [ + { + "function": { + "name": "CNl.bI.?D?WW,.af,?!QY.?.? ?.z85Pp?! oZ", + "parameters": {} + }, + "type": "function" + } + ], + "top_logprobs": 672, + "web_search_options": { + "search_context_size": "high" + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-4ec638697bbccbe3.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-4ec638697bbccbe3.meta.json new file mode 100644 index 00000000..a2a2ac47 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-4ec638697bbccbe3.meta.json @@ -0,0 +1,23 @@ +{ + "issues": [ + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: params.store (true -> null)", + "universal(1->2): changed: params.logprobs (true -> null)", + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.reasoning.effort (\"medium\" -> \"low\")", + "universal(1->2): changed: params.presence_penalty (90.809479429592 -> null)", + "universal(1->2): changed: messages.length (1 -> 0)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: presence_penalty", + "chat(final): lost: stream", + "chat(final): lost: functions", + "chat(final): lost: web_search_options", + "chat(final): lost: store", + "chat(final): lost: n", + "chat(final): lost: logprobs", + "chat(final): changed: reasoning_effort (\"medium\" -> \"low\")", + "chat(final): changed: messages.length (1 -> 0)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-4ec638697bbccbe3.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-4ec638697bbccbe3.request.json new file mode 100644 index 00000000..86176579 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-4ec638697bbccbe3.request.json @@ -0,0 +1,56 @@ +{ + "functions": [ + { + "name": ".?Hw.?,!..w", + "parameters": {} + }, + { + "description": "fv 0gAo?Ez6,Hr,.W?,M ZtY, 7n21nS?fH.W?,c", + "name": "? L?g?,E?!.?.A?80R8.dqY,UScyT8134?.Z8W!?.,,?8" + }, + { + "description": "bB ,, .9.l.,Y?P.???e5", + "name": "iv?!" + } + ], + "logprobs": true, + "max_completion_tokens": 160, + "messages": [ + { + "content": [ + { + "text": "3Oqp,..", + "type": "text" + }, + { + "text": "??cGz!,5y8", + "type": "text" + }, + { + "text": "BSs E?k6?6,..?!fC?5A,", + "type": "text" + } + ], + "name": "!u,?Nl?,O2 0 ag!6. rw..!?,LMp!L?!VWf,.av!", + "role": "developer" + } + ], + "model": "RJ", + "n": -396, + "presence_penalty": 90.809479429592, + "reasoning_effort": "medium", + "response_format": { + "type": "json_object" + }, + "store": true, + "stream": true, + "web_search_options": { + "search_context_size": "low", + "user_location": { + "approximate": { + "region": "1?x?zH7z,.,27Doe?C." + }, + "type": "approximate" + } + } +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-64011eae99c883f4.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-64011eae99c883f4.meta.json new file mode 100644 index 00000000..10c4129d --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-64011eae99c883f4.meta.json @@ -0,0 +1,37 @@ +{ + "issues": [ + "universal(1->2): added: params.response_format.json_schema.schema.items", + "universal(1->2): changed: messages.length (3 -> 1)", + "universal(1->2): changed: params.tools ([{\"description\":\"Owd5Y!XouwrA4x..qh11U !?74 m ?VFbY,k ce!.k eE\",\"kind\":\"custom\",\"name\":\".XDT!ckr9,,?wXL?.,.x u !2,.1? 5b!9o!HAF.?7\"},{\"format\":{\"type\":\"text\"},\"kind\":\"custom\",\"name\":\"Y,,o.uPG6,cEg!f.fA83B4.6umV?N ?uZJ!,vi\"}] -> null)", + "universal(1->2): changed: params.reasoning.effort (\"high\" -> \"low\")", + "universal(1->2): changed: params.reasoning.canonical (\"effort\" -> \"budget_tokens\")", + "universal(1->2): changed: params.store (false -> null)", + "universal(1->2): changed: params.top_logprobs (-941 -> null)", + "universal(1->2): changed: params.response_format.json_schema.name (\"i?.Bn.S?eF.K!.9v rPD,?,.1R.LY92i,,?..OY8,?,!W.\" -> \"response\")", + "universal(1->2): changed: params.response_format.json_schema.description (\",?N !u3d?Z0?F \" -> null)", + "universal(1->2): changed: params.seed (-566 -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: seed", + "chat(final): lost: functions", + "chat(final): lost: verbosity", + "chat(final): lost: function_call", + "chat(final): lost: tools", + "chat(final): lost: top_logprobs", + "chat(final): lost: max_tokens", + "chat(final): lost: logit_bias", + "chat(final): lost: audio", + "chat(final): lost: prediction", + "chat(final): lost: store", + "chat(final): lost: stream_options", + "chat(final): lost: response_format.json_schema.strict", + "chat(final): lost: response_format.json_schema.description", + "chat(final): added: response_format.json_schema.schema.items", + "chat(final): changed: messages.length (3 -> 1)", + "chat(final): changed: stop (\"7QY,1D c,.9K.n,,,k!,1?fcO\" -> [\"7QY,1D c,.9K.n,,,k!,1?fcO\"])", + "chat(final): changed: max_completion_tokens (416 -> 357)", + "chat(final): changed: response_format.json_schema.name (\"i?.Bn.S?eF.K!.9v rPD,?,.1R.LY92i,,?..OY8,?,!W.\" -> \"response\")", + "chat(final): changed: reasoning_effort (\"high\" -> \"low\")" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-64011eae99c883f4.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-64011eae99c883f4.request.json new file mode 100644 index 00000000..db7bb721 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-64011eae99c883f4.request.json @@ -0,0 +1,99 @@ +{ + "audio": { + "format": "wav", + "voice": "w?!L" + }, + "function_call": "auto", + "functions": [ + { + "description": ".Zw?E", + "name": "d", + "parameters": {} + }, + { + "description": "7Qb?,!As ? 4JdK !!!2! D,?xo l", + "name": "NJW.N.z0u9!aP,lI4,k.2,M2U0K" + }, + { + "description": ". ,nGTl.d..yZBuF.,b5lgE .EIkP.L", + "name": "!" + } + ], + "logit_bias": {}, + "max_completion_tokens": 416, + "max_tokens": 357, + "messages": [ + { + "content": null, + "name": "B,.?!N7iTxUU9P,v,!q!75? E.TB 0!j.GVvD.C2", + "role": "function" + }, + { + "content": "MJ1.a,kg6!?.HDr,e0,.s.Kx02", + "role": "system" + }, + { + "content": ",V.i7x,rD,..a?p5 0?Z", + "role": "tool", + "tool_call_id": "BW " + } + ], + "model": "gpt-4-turbo-preview", + "prediction": { + "content": [ + { + "text": "h?92fv9z3G, ?,?c..?P,,0,Y.R,?.NEK,OGe?", + "type": "text" + }, + { + "text": " .?gAD.gXaD?.r,GJ?.", + "type": "text" + }, + { + "text": ".!.!.6?J1,.7gO?j3v? W.s,,?2O,.? QG,b4m?o.", + "type": "text" + } + ], + "type": "content" + }, + "reasoning_effort": "high", + "response_format": { + "json_schema": { + "description": ",?N !u3d?Z0?F ", + "name": "i?.Bn.S?eF.K!.9v rPD,?,.1R.LY92i,,?..OY8,?,!W.", + "schema": {}, + "strict": null + }, + "type": "json_schema" + }, + "seed": -566, + "stop": "7QY,1D c,.9K.n,,,k!,1?fcO", + "store": false, + "stream_options": null, + "tool_choice": { + "function": { + "name": "9d.o!.GQZ,? ??A O?s," + }, + "type": "function" + }, + "tools": [ + { + "custom": { + "description": "Owd5Y!XouwrA4x..qh11U !?74 m ?VFbY,k ce!.k eE", + "name": ".XDT!ckr9,,?wXL?.,.x u !2,.1? 5b!9o!HAF.?7" + }, + "type": "custom" + }, + { + "custom": { + "format": { + "type": "text" + }, + "name": "Y,,o.uPG6,cEg!f.fA83B4.6umV?N ?uZJ!,vi" + }, + "type": "custom" + } + ], + "top_logprobs": -941, + "verbosity": null +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-a3ec1907e0d629d5.meta.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-a3ec1907e0d629d5.meta.json new file mode 100644 index 00000000..776c55da --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-a3ec1907e0d629d5.meta.json @@ -0,0 +1,28 @@ +{ + "issues": [ + "universal(1->2): lost: params.tools[0].strict", + "universal(1->2): changed: messages.length (3 -> 0)", + "universal(1->2): changed: params.logprobs (true -> null)", + "universal(1->2): changed: params.frequency_penalty (-70.06246855099464 -> null)", + "universal(1->2): changed: params.stream (true -> null)", + "universal(1->2): changed: params.store (false -> null)", + "google(1->2): lost: systemInstruction", + "chat(final): lost: store", + "chat(final): lost: reasoning_effort", + "chat(final): lost: audio", + "chat(final): lost: modalities", + "chat(final): lost: prediction", + "chat(final): lost: stream", + "chat(final): lost: frequency_penalty", + "chat(final): lost: web_search_options", + "chat(final): lost: function_call", + "chat(final): lost: stream_options", + "chat(final): lost: functions", + "chat(final): lost: logprobs", + "chat(final): lost: tools[0].function.strict", + "chat(final): changed: stop (\".9.h?,!3X cix?R7?b.mP7x4p6H??6!?7pC,?k?U\" -> [\".9.h?,!3X cix?R7?b.mP7x4p6H??6!?7pC,?k?U\"])", + "chat(final): changed: messages.length (3 -> 0)" + ], + "kind": "chat-google-two-arm", + "provider": "chat-completions" +} diff --git a/payloads/fuzz-snapshots/chat-google-two-arm/case-a3ec1907e0d629d5.request.json b/payloads/fuzz-snapshots/chat-google-two-arm/case-a3ec1907e0d629d5.request.json new file mode 100644 index 00000000..f5261471 --- /dev/null +++ b/payloads/fuzz-snapshots/chat-google-two-arm/case-a3ec1907e0d629d5.request.json @@ -0,0 +1,80 @@ +{ + "audio": { + "format": "pcm16", + "voice": "ash" + }, + "frequency_penalty": -70.06246855099464, + "function_call": "auto", + "functions": [ + { + "name": "E 1qQ KmZ? " + } + ], + "logprobs": true, + "max_completion_tokens": 443, + "messages": [ + { + "content": [ + { + "text": ",f .wX ?", + "type": "text" + } + ], + "role": "developer" + }, + { + "content": " 5xPcy?qc ,Q?.,?!.?10KMn.,F?V,h?9?Y", + "name": " Lof..WP56s i!9Xw", + "role": "system" + }, + { + "content": [ + { + "text": "8yt!w", + "type": "text" + }, + { + "text": ".!wX?", + "type": "text" + } + ], + "name": "A,8.d.p., ??k..2h8 9 HI.t!u8.1s?t,OV!?H! !?E5W!y", + "role": "developer" + } + ], + "modalities": null, + "model": "o3-2025-04-16", + "prediction": { + "content": [ + { + "text": "??j.,.E1 f,.?d.50QswHW.!5S.2?h?oLnJi?z5.T,tg.", + "type": "text" + }, + { + "text": ".?n?6,N.t8I!?!qy?hu.A88!?vU2vj.t.2?AxUG ,XhPh", + "type": "text" + } + ], + "type": "content" + }, + "reasoning_effort": null, + "stop": ".9.h?,!3X cix?R7?b.mP7x4p6H??6!?7pC,?k?U", + "store": false, + "stream": true, + "stream_options": { + "include_obfuscation": false + }, + "tools": [ + { + "function": { + "description": "r.7.?J!3x", + "name": "?,Z", + "strict": true + }, + "type": "function" + } + ], + "web_search_options": { + "search_context_size": "medium" + } +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-00015db5103bca8c.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-00015db5103bca8c.meta.json new file mode 100644 index 00000000..c77444fe --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-00015db5103bca8c.meta.json @@ -0,0 +1,7 @@ +{ + "issues": [ + "request_to_universal error: Conversion to universal format failed: Missing required field: parts" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-00015db5103bca8c.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-00015db5103bca8c.request.json new file mode 100644 index 00000000..8a5b052d --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-00015db5103bca8c.request.json @@ -0,0 +1,53 @@ +{ + "cachedContent": "Zx.yEkOd0", + "contents": [ + { + "role": ".,i?0" + }, + { + "role": "?kV.k ,,LMM.3f,1?,mlxb6I3IG" + } + ], + "generationConfig": { + "_responseJsonSchema": null, + "frequencyPenalty": -60.42419908836965, + "imageConfig": {}, + "logprobs": -825, + "maxOutputTokens": -266, + "presencePenalty": 62.34146184198899, + "responseMimeType": "S", + "responseModalities": [ + "IMAGE" + ], + "speechConfig": { + "multiSpeakerVoiceConfig": { + "speakerVoiceConfigs": [ + { + "voiceConfig": { + "prebuiltVoiceConfig": null + } + } + ] + } + }, + "stopSequences": [ + "zA!,2.xB,TLcC. jfvuE.,k!xy S.?5I?2c.o" + ], + "thinkingConfig": { + "includeThoughts": true, + "thinkingBudget": -573, + "thinkingLevel": "MINIMAL" + } + }, + "safetySettings": [ + {}, + {} + ], + "systemInstruction": {}, + "toolConfig": { + "functionCallingConfig": { + "mode": "AUTO" + }, + "retrievalConfig": {} + } +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-0005d480caf84338.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-0005d480caf84338.meta.json new file mode 100644 index 00000000..033e4af0 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-0005d480caf84338.meta.json @@ -0,0 +1,15 @@ +{ + "issues": [ + "lost: systemInstruction", + "lost: toolConfig", + "lost: contents[0].parts[0].thoughtSignature", + "lost: contents[0].parts[0].videoMetadata", + "lost: contents[0].parts[0].partMetadata", + "lost: contents[0].parts[0].functionResponse", + "lost: contents[0].parts[0].thought", + "lost: contents[0].parts[0].functionCall", + "added: contents[0].role" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-0005d480caf84338.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-0005d480caf84338.request.json new file mode 100644 index 00000000..92e9f4d2 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-0005d480caf84338.request.json @@ -0,0 +1,97 @@ +{ + "contents": [ + { + "parts": [ + { + "functionCall": { + "id": "xJ9iB" + }, + "functionResponse": { + "id": ".qM?", + "name": ",W?zH.p??d 3!", + "willContinue": true + }, + "partMetadata": {}, + "text": "52, .X?,!Z 1L s!,GH!!!aFdv.mKl,l?,.?Dq", + "thought": true, + "thoughtSignature": " Kl,6?", + "videoMetadata": { + "endOffset": "Bpv?? !,,Jr,giuk!n. EZe66,1!buc?", + "fps": 11.271011959752407, + "startOffset": "5P3Q,w,n,3e,x" + } + } + ] + } + ], + "systemInstruction": { + "parts": [ + { + "codeExecutionResult": { + "output": "?I.,?jJ!f. .DuF1374!.Ahv2q7,9j22T3zHbO.Rn4?A" + }, + "executableCode": { + "code": "!?B?D.N!U??us1?.x??,,S4f2b6.L", + "language": "LANGUAGE_UNSPECIFIED" + }, + "fileData": { + "fileUri": "?!.jj?!,PC8. 6.??,x8,?s?pw33A,R?L L", + "mimeType": ",,.?.!w1w0 3i.9!?...yQ.x,FVL!.." + }, + "functionCall": { + "args": {}, + "id": "7?. 5.sjWl9 2Qc 5,! z. ??Qo H.q47. OPvJuB.,!iNi v", + "name": ".3A?.?4g?IO!?Ymdx!a??s!,p?.fOGE?.e,Vg?" + }, + "functionResponse": { + "id": "!,cd,fy.. hA?82S9Fhp 4m.h?!DJ!u8?j.?N9g1,", + "parts": [ + { + "inlineData": { + "mimeType": null + } + }, + {}, + { + "inlineData": { + "data": null + } + } + ] + }, + "partMetadata": {}, + "text": "S" + }, + { + "codeExecutionResult": { + "outcome": "OUTCOME_DEADLINE_EXCEEDED", + "output": "a.MGjQ4" + }, + "fileData": { + "mimeType": "7.Q8 NZ..D..Q?yNAc?KVNg,?p8.??," + }, + "functionResponse": { + "parts": [ + { + "inlineData": {} + }, + { + "inlineData": { + "data": null + } + } + ], + "response": {}, + "scheduling": "INTERRUPT" + }, + "inlineData": {}, + "videoMetadata": { + "endOffset": " .,.?g9.,j,Ly2O,47.XqVw.p?Oe?0?ZA", + "fps": 69.29815115247787, + "startOffset": "i ?x?71He,si?1.?.IW,B?!I!0" + } + } + ] + }, + "toolConfig": {} +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-007bd4500a682a93.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-007bd4500a682a93.meta.json new file mode 100644 index 00000000..6432821c --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-007bd4500a682a93.meta.json @@ -0,0 +1,9 @@ +{ + "issues": [ + "changed: contents[0].role (\"a\" -> \"user\")", + "changed: contents[0].parts.length (1 -> 0)", + "changed: tools.length (1 -> 2)" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-007bd4500a682a93.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-007bd4500a682a93.request.json new file mode 100644 index 00000000..e0ea4523 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-007bd4500a682a93.request.json @@ -0,0 +1,27 @@ +{ + "contents": [ + { + "parts": [ + { + "inlineData": {} + } + ], + "role": "a" + } + ], + "tools": [ + { + "computerUse": { + "excludedPredefinedFunctions": [ + "?.bi.?,p" + ] + }, + "googleSearch": { + "timeRangeFilter": { + "startTime": "QDBDMGS.?lxG,,?.X?9?j.k?TA.!x.R,?9," + } + }, + "googleSearchRetrieval": {} + } + ] +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-051ebd07ca42b0d6.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-051ebd07ca42b0d6.meta.json new file mode 100644 index 00000000..0bc93dfa --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-051ebd07ca42b0d6.meta.json @@ -0,0 +1,10 @@ +{ + "issues": [ + "lost: toolConfig", + "lost: contents[0].parts[0].inlineData", + "lost: contents[0].parts[0].videoMetadata", + "changed: contents[0].role (\"j.,.kD8c,v! ?\" -> \"user\")" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-051ebd07ca42b0d6.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-051ebd07ca42b0d6.request.json new file mode 100644 index 00000000..abd0ac60 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-051ebd07ca42b0d6.request.json @@ -0,0 +1,23 @@ +{ + "contents": [ + { + "parts": [ + { + "inlineData": {}, + "text": "?lsKj.a? ??R6?,uO3q?p2j?? .h ", + "videoMetadata": { + "fps": 70.04902652071755 + } + } + ], + "role": "j.,.kD8c,v! ?" + } + ], + "toolConfig": { + "retrievalConfig": { + "latLng": { + "longitude": 42.35899714596116 + } + } + } +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-066263d9b371b047.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-066263d9b371b047.meta.json new file mode 100644 index 00000000..1ecb131c --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-066263d9b371b047.meta.json @@ -0,0 +1,13 @@ +{ + "issues": [ + "lost: toolConfig", + "lost: systemInstruction", + "lost: contents[0].parts[0].videoMetadata", + "lost: contents[0].parts[0].mediaResolution", + "lost: contents[0].parts[0].thoughtSignature", + "added: contents[0].role", + "added: contents[0].parts[0].inlineData.mimeType" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-066263d9b371b047.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-066263d9b371b047.request.json new file mode 100644 index 00000000..642d91b6 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-066263d9b371b047.request.json @@ -0,0 +1,50 @@ +{ + "contents": [ + { + "parts": [ + { + "inlineData": { + "data": ".vW1 v?,Nx6mq?a?A?8,5E" + }, + "mediaResolution": {}, + "thoughtSignature": "AVJlL,u.?Y?PN Tsr", + "videoMetadata": { + "fps": 65.50903686895178, + "startOffset": "?Z?R,.Fu d. ," + } + } + ] + } + ], + "model": "D ", + "safetySettings": [ + {} + ], + "systemInstruction": { + "parts": [ + { + "codeExecutionResult": { + "outcome": "OUTCOME_UNSPECIFIED" + }, + "executableCode": { + "code": " 9.!?..t3VA.2?", + "language": "LANGUAGE_UNSPECIFIED" + }, + "functionResponse": { + "response": {}, + "willContinue": false + }, + "text": "z,f 7,IZlg, N!W,u?a f5,4U!.2!,d,.", + "thought": true, + "thoughtSignature": ",KheB??KQqCP,M1!.c.e.,2gzDKYJ.?,FBt,Pk8" + } + ] + }, + "toolConfig": { + "retrievalConfig": { + "latLng": { + "longitude": -49.83756195325235 + } + } + } +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-08e697e9ba45fe3e.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-08e697e9ba45fe3e.meta.json new file mode 100644 index 00000000..b6e22213 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-08e697e9ba45fe3e.meta.json @@ -0,0 +1,14 @@ +{ + "issues": [ + "lost: contents[0].parts[0].partMetadata", + "lost: contents[0].parts[0].thought", + "lost: contents[0].parts[0].mediaResolution", + "lost: contents[0].parts[0].videoMetadata", + "lost: contents[0].parts[0].codeExecutionResult", + "lost: contents[0].parts[0].functionCall", + "lost: contents[0].parts[0].fileData", + "changed: contents[0].role (\"L,???b3M?!vZ!y,.3Q..FL0!.5 e.,?\" -> \"user\")" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-08e697e9ba45fe3e.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-08e697e9ba45fe3e.request.json new file mode 100644 index 00000000..ac7ef8f6 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-08e697e9ba45fe3e.request.json @@ -0,0 +1,28 @@ +{ + "cachedContent": "W02F.q!.GGcS.qm.,3..M!mn!8H? .9bSyHSf?UVER", + "contents": [ + { + "parts": [ + { + "codeExecutionResult": { + "outcome": "OUTCOME_OK", + "output": "32iaPm,psYi,u1JR" + }, + "fileData": { + "fileUri": "G?.ua?tx64Ri?,??z.3Zk5T?Y8????I,Pd79sa.?XX?, ," + }, + "functionCall": {}, + "mediaResolution": {}, + "partMetadata": {}, + "text": "GnQ,!2?,Zx2.4 Z.? ,H 0a0Qd?,8Z2M.3u.!.L.dm?pN", + "thought": false, + "videoMetadata": { + "endOffset": "MOKAT ,a,V,v,?ng.i KP.17ZK,,,k,i.o?kQ?,,4xWx,.p.3t", + "startOffset": "77?!d. ?!j " + } + } + ], + "role": "L,???b3M?!vZ!y,.3Q..FL0!.5 e.,?" + } + ] +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-0bde07b217c86337.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-0bde07b217c86337.meta.json new file mode 100644 index 00000000..0c95c2ae --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-0bde07b217c86337.meta.json @@ -0,0 +1,14 @@ +{ + "issues": [ + "lost: systemInstruction", + "lost: toolConfig.functionCallingConfig.allowedFunctionNames", + "lost: generationConfig.responseMimeType", + "lost: generationConfig.speechConfig.multiSpeakerVoiceConfig.speakerVoiceConfigs[0].voiceConfig.prebuiltVoiceConfig", + "added: generationConfig.responseSchema", + "changed: toolConfig.functionCallingConfig.mode (\"VALIDATED\" -> \"AUTO\")", + "changed: contents[0].parts.length (1 -> 0)", + "changed: contents[0].role (\"092,?JS.,02,N4 ..xQ3X..d6!p8u?l.aq73k,FK!tG?5I\" -> \"user\")" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-0bde07b217c86337.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-0bde07b217c86337.request.json new file mode 100644 index 00000000..12a39020 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-0bde07b217c86337.request.json @@ -0,0 +1,94 @@ +{ + "cachedContent": "1FfW7eP9!!!wV!c?4?758Z2i?8a1.B?8b,6X.M.?5?,", + "contents": [ + { + "parts": [ + { + "codeExecutionResult": { + "output": "ql.H,?Ewf973 g.I,XZ?bqw7,nZ? kGi !S?1ia?k" + }, + "executableCode": { + "code": ",A.. Q.J?TD", + "language": "LANGUAGE_UNSPECIFIED" + }, + "fileData": { + "mimeType": "?. 2?77o.3,krV00 am6,z" + }, + "mediaResolution": { + "level": "MEDIA_RESOLUTION_HIGH" + }, + "partMetadata": {}, + "thought": true, + "videoMetadata": {} + } + ], + "role": "092,?JS.,02,N4 ..xQ3X..d6!p8u?l.aq73k,FK!tG?5I" + } + ], + "generationConfig": { + "candidateCount": -720, + "frequencyPenalty": -2.548798129137723, + "logprobs": -608, + "mediaResolution": "MEDIA_RESOLUTION_HIGH", + "presencePenalty": -81.12346234355222, + "responseJsonSchema": " 4c09s BvUzow4y6G3UV7w1 qqs2", + "responseLogprobs": true, + "responseMimeType": "M,Q.,NU.,N", + "responseModalities": [ + "AUDIO", + "MODALITY_UNSPECIFIED", + "IMAGE" + ], + "seed": 562, + "speechConfig": { + "languageCode": "!VH,", + "multiSpeakerVoiceConfig": { + "speakerVoiceConfigs": [ + { + "voiceConfig": { + "prebuiltVoiceConfig": null + } + }, + { + "voiceConfig": {} + }, + { + "speaker": "N,?K0wu.R!o.,E hVT,!,O,38v 5" + } + ] + } + }, + "topP": 30.900514366320234 + }, + "model": "IerC,???XA,Iy7N1n4,..y! ?.KO ", + "systemInstruction": { + "parts": [ + { + "executableCode": { + "code": "e!CfUzN.i? .... 55r.Z3LQf41", + "language": "PYTHON" + }, + "functionCall": { + "args": {} + }, + "functionResponse": { + "id": ". .X", + "name": "oat09o.n3RQ4.E ?3ZEJp?t!E9,P ,wms", + "response": {} + }, + "mediaResolution": {}, + "thought": false, + "thoughtSignature": "L9p5" + } + ], + "role": "Z,?WUq?M,Dm9MY 2.4mA8,0 ,XD7M?,, 1.7fE6!! XNb9" + }, + "toolConfig": { + "functionCallingConfig": { + "allowedFunctionNames": [ + ",??0X.7x?MJ.8XD!,T27t?hQJ" + ], + "mode": "VALIDATED" + } + } +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-117472f2bf6ae259.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-117472f2bf6ae259.meta.json new file mode 100644 index 00000000..2da9bb91 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-117472f2bf6ae259.meta.json @@ -0,0 +1,9 @@ +{ + "issues": [ + "lost: toolConfig", + "lost: systemInstruction", + "changed: contents.length (3 -> 1)" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-117472f2bf6ae259.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-117472f2bf6ae259.request.json new file mode 100644 index 00000000..218004eb --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-117472f2bf6ae259.request.json @@ -0,0 +1,157 @@ +{ + "cachedContent": "?j.JUM04?.k3rAjkY ,?R! 25J.?,8M3MkbN7?6, ,a.ZHe", + "contents": [ + { + "parts": [ + { + "codeExecutionResult": { + "outcome": "OUTCOME_UNSPECIFIED" + }, + "fileData": {}, + "functionResponse": { + "id": "Zi aFh?a6aG2H?.pMlo,P.m,Py .gj", + "parts": [ + { + "inlineData": null + } + ], + "scheduling": "INTERRUPT", + "willContinue": true + }, + "mediaResolution": { + "level": "MEDIA_RESOLUTION_HIGH" + }, + "partMetadata": {}, + "text": "e?T.3z0P,7a8 ,m8!.e.? ..,N.n,zaN2Ez!hoydi", + "thought": true + }, + { + "functionCall": { + "args": {}, + "id": ". .?2T jy!..yx.Sy1? .6P" + }, + "functionResponse": { + "id": "wC?0 ,ha jvdb!,.,n,9?E!.O fn.wOgAc,26", + "response": {}, + "willContinue": true + }, + "inlineData": {}, + "mediaResolution": { + "level": "MEDIA_RESOLUTION_UNSPECIFIED" + }, + "partMetadata": {}, + "text": "yu?9MLHSL", + "thought": true + } + ] + }, + { + "parts": [ + { + "executableCode": { + "code": "Wt, 1?OVQqtM0V2?,fd1IX?.o,?.144uE", + "language": "LANGUAGE_UNSPECIFIED" + }, + "fileData": { + "mimeType": "SrZB3J,.!" + }, + "functionCall": { + "args": {}, + "name": "DyN!?,,,.Li8?d,Al9u7M,,L5,xXm M" + }, + "functionResponse": { + "id": ",??,ec. j4WkMARA?8y,!.?gXTqo..3s?H jnR,.er8?g!,", + "name": ".L!.URw.,en!", + "response": {}, + "scheduling": "WHEN_IDLE" + }, + "inlineData": { + "data": "4.?7,Z?Z!R.,,3A .v2,rQ,?,!.lo.?!1i,Mm G2w eI0j0" + }, + "text": "?2.I!1QnE,q?9u.E.S.!W.T,4..26.!3?p.z..gO", + "thought": false, + "thoughtSignature": "OAl!w8R L?, e0 !?v.w? 6", + "videoMetadata": { + "endOffset": "?.?dimZDh.?6VgY.!D?7S.. Q.A3a.,?!n", + "fps": 31.80004873880053, + "startOffset": ".56.1I.,," + } + }, + { + "codeExecutionResult": { + "outcome": "OUTCOME_FAILED", + "output": "ov2." + }, + "executableCode": { + "language": "LANGUAGE_UNSPECIFIED" + }, + "fileData": { + "fileUri": ".8Kp oG.P.8?u Uc", + "mimeType": "?t?0.r?Y.." + }, + "inlineData": { + "mimeType": " Z .?l.,.V8UOK" + }, + "mediaResolution": { + "level": "MEDIA_RESOLUTION_LOW" + }, + "text": " nQ7UhFf9,cJ5dO3,8?2??1b", + "thought": true, + "videoMetadata": { + "fps": -87.21323262709994, + "startOffset": "5Ps.Q,s!??Y84.,bh??G??!! " + } + } + ] + }, + { + "parts": [ + { + "functionCall": { + "args": {} + }, + "inlineData": { + "data": "?ijMBl4H,,w..?.vW1 v?,Nx6mq?a?A?8,5E" + }, + "mediaResolution": {}, + "thoughtSignature": "AVJlL,u.?Y?PN Tsr", + "videoMetadata": { + "fps": 65.50903686895178, + "startOffset": "?Z?R,.Fu d. ," + } + } + ] + } + ], + "model": "D ", + "safetySettings": [ + {} + ], + "systemInstruction": { + "parts": [ + { + "codeExecutionResult": { + "outcome": "OUTCOME_UNSPECIFIED" + }, + "executableCode": { + "code": " 9.!?..t3VA.2?", + "language": "LANGUAGE_UNSPECIFIED" + }, + "functionResponse": { + "response": {}, + "willContinue": false + }, + "text": "z,f 7,IZlg, N!W,u?a f5,4U!.2!,d,.", + "thought": true, + "thoughtSignature": ",KheB??KQqCP,M1!.c.e.,2gzDKYJ.?,FBt,Pk8" + } + ] + }, + "toolConfig": { + "retrievalConfig": { + "latLng": { + "longitude": -49.83756195325235 + } + } + } +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-138766d1b5d34371.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-138766d1b5d34371.meta.json new file mode 100644 index 00000000..c62ede6e --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-138766d1b5d34371.meta.json @@ -0,0 +1,21 @@ +{ + "issues": [ + "lost: contents[0].parts[0].inlineData", + "lost: contents[0].parts[0].codeExecutionResult", + "lost: contents[0].parts[0].thoughtSignature", + "lost: contents[0].parts[0].executableCode", + "lost: contents[0].parts[1].videoMetadata", + "lost: contents[0].parts[1].mediaResolution", + "lost: contents[0].parts[1].executableCode", + "lost: contents[0].parts[2].codeExecutionResult", + "lost: contents[0].parts[2].functionCall", + "lost: contents[0].parts[2].inlineData", + "lost: contents[0].parts[2].videoMetadata", + "lost: generationConfig.responseJsonSchema", + "added: generationConfig.responseSchema", + "changed: contents[0].role (\"7oF,.m6,L?OK,7?0JW\" -> \"user\")", + "changed: tools.length (1 -> 3)" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-138766d1b5d34371.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-138766d1b5d34371.request.json new file mode 100644 index 00000000..8fd35f37 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-138766d1b5d34371.request.json @@ -0,0 +1,69 @@ +{ + "cachedContent": " xQ.PP?", + "contents": [ + { + "parts": [ + { + "codeExecutionResult": {}, + "executableCode": {}, + "inlineData": { + "mimeType": ".e?9rOs40B!,F,.JEv,Q6j9!,,?Ad" + }, + "text": " nI?G m,sy?a.?JB..!,,8rnc4R7DmvKT,,K", + "thoughtSignature": "z1.Tf ,4fQ..,o.Tf4w?2 F9?!N 3" + }, + { + "executableCode": { + "code": "Ph.G Qw5n4r.8,Z5,.xWd!?.. o?z.a!?7B6 Aj" + }, + "mediaResolution": {}, + "text": "N.,n6a,94?II.?X?6bn.zE F R.?,c??X1 BKf2J0U 6,..", + "videoMetadata": { + "fps": -90.9113865558719 + } + }, + { + "codeExecutionResult": {}, + "functionCall": {}, + "inlineData": { + "data": "31A.,", + "mimeType": "2k.s05,Z,.e 81.?5r.bsC." + }, + "text": ".,7.??JU9,.,y?!F.Dr?0,Mb,Tyc,REg,.r ,hy!PR2FhB", + "videoMetadata": { + "endOffset": "? J!?w!w,.n ?jg.9?", + "startOffset": ",8C.MP!," + } + } + ], + "role": "7oF,.m6,L?OK,7?0JW" + } + ], + "generationConfig": { + "_responseJsonSchema": "NF ", + "candidateCount": 861, + "mediaResolution": "MEDIA_RESOLUTION_HIGH", + "presencePenalty": -24.74147361495701, + "responseJsonSchema": null, + "seed": 764 + }, + "model": ",!Ji,1X.,2R5I2ME!P r7?OuIO", + "safetySettings": [ + { + "threshold": "BLOCK_ONLY_HIGH" + }, + {} + ], + "tools": [ + { + "codeExecution": {}, + "googleSearch": {}, + "googleSearchRetrieval": { + "dynamicRetrievalConfig": { + "mode": "MODE_DYNAMIC" + } + }, + "urlContext": {} + } + ] +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-375c9df694c3d0a3.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-375c9df694c3d0a3.meta.json new file mode 100644 index 00000000..25df2d8a --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-375c9df694c3d0a3.meta.json @@ -0,0 +1,16 @@ +{ + "issues": [ + "lost: toolConfig", + "lost: generationConfig.responseMimeType", + "lost: generationConfig.responseJsonSchema", + "lost: contents[0].parts[0].executableCode", + "lost: contents[0].parts[0].mediaResolution", + "lost: contents[0].parts[0].videoMetadata", + "lost: contents[0].parts[0].thoughtSignature", + "added: generationConfig.thinkingConfig.includeThoughts", + "changed: generationConfig.responseSchema ({\"default\":\"u U712X\",\"description\":\"1!?mw!9,T??NU.9E321?92.ySf 1UXL5.!,Lf.b?l87ZJ3V.D,\",\"enum\":[\"4N,?c?.??p?.EJ5vq..UOc4,,?s1H,ZHt.D3,? \",\".dROe b.53\",\"a D?1vG.vQ.,,Kk?Pq.A8.R,\"],\"format\":\",p..E , .Zv\",\"items\":{\"description\":\",j,.!,Ng!c.chY\",\"example\":null,\"format\":\"7i8KLWM!,r6.J !j?Q0.t???,N?.!?.!9?B,v6z6,?MN!Qt\",\"maxLength\":\" \",\"minItems\":\"?? ,?HzCp8,G?..? qr Fwj\",\"minProperties\":\",..sN.U O VN?LjP,,Z?? dhpK.d1v7.Z1,l\",\"nullable\":true,\"properties\":{},\"propertyOrdering\":[\"K !.?N,!y!!,29yTXWe?,,21 ?,.\",\"t 69\",\". z,t??2\"],\"type\":\"NULL\"},\"maxLength\":\"bpL!zlx Lbu?z,Wym.g3P!,. UE,RGT!c!64y7p?hOpTOh.\",\"maxProperties\":\"4 .,\",\"maximum\":19.61703789371374,\"minProperties\":\"!,Y.!s.?UW o,Q??!.f6!?o i\",\"required\":[\"?,? ?G,? nW.mg2e?02 R5M a6E0IP?!.,035M,,86?.\",\"PFq?L\",\".k,,?.BB6j6J\"],\"title\":\"0..QG7D,u.P?pJqM?f1?x?D,?xu0SKP,\",\"type\":\"ARRAY\"} -> null)", + "changed: contents[0].role (\"? ,gbww6.kgJ7Pi.6P7z.K?,J8!2?f.N.!0?\" -> \"user\")" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-375c9df694c3d0a3.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-375c9df694c3d0a3.request.json new file mode 100644 index 00000000..4976c63d --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-375c9df694c3d0a3.request.json @@ -0,0 +1,95 @@ +{ + "cachedContent": "MIL?l v.,! Y??m?j?a.,.?vzX,c3K m.DGjy.!", + "contents": [ + { + "parts": [ + { + "executableCode": { + "language": "LANGUAGE_UNSPECIFIED" + }, + "inlineData": { + "data": "i?9O,2.,.H0rl7.,5.?Q,f?,n67aVy?dP", + "mimeType": "99bn9.?jx9?5Xu?F.6t.Q,q.V3.7ER." + }, + "mediaResolution": { + "level": "MEDIA_RESOLUTION_ULTRA_HIGH" + }, + "thoughtSignature": "?u!eIug..9C? soB0.8sTxo.q.k?65W??,p4 g.i3mX6 u7??", + "videoMetadata": { + "fps": 51.698216934714644 + } + } + ], + "role": "? ,gbww6.kgJ7Pi.6P7z.K?,J8!2?f.N.!0?" + } + ], + "generationConfig": { + "_responseJsonSchema": "Z aX5ge66 6D 59xXsGnF", + "frequencyPenalty": 2.6697296059110456, + "imageConfig": { + "imageSize": "BZw,!JpI?T?CHAOT4?w .8!?P MMuh ?2S,?." + }, + "logprobs": -41, + "maxOutputTokens": -889, + "mediaResolution": "MEDIA_RESOLUTION_UNSPECIFIED", + "responseJsonSchema": null, + "responseMimeType": "yH? .rM!6 .", + "responseModalities": [ + "AUDIO", + "AUDIO" + ], + "responseSchema": { + "default": "u U712X", + "description": "1!?mw!9,T??NU.9E321?92.ySf 1UXL5.!,Lf.b?l87ZJ3V.D,", + "enum": [ + "4N,?c?.??p?.EJ5vq..UOc4,,?s1H,ZHt.D3,? ", + ".dROe b.53", + "a D?1vG.vQ.,,Kk?Pq.A8.R," + ], + "format": ",p..E , .Zv", + "items": { + "description": ",j,.!,Ng!c.chY", + "example": null, + "format": "7i8KLWM!,r6.J !j?Q0.t???,N?.!?.!9?B,v6z6,?MN!Qt", + "maxLength": " ", + "minItems": "?? ,?HzCp8,G?..? qr Fwj", + "minProperties": ",..sN.U O VN?LjP,,Z?? dhpK.d1v7.Z1,l", + "nullable": true, + "properties": {}, + "propertyOrdering": [ + "K !.?N,!y!!,29yTXWe?,,21 ?,.", + "t 69", + ". z,t??2" + ], + "type": "NULL" + }, + "maxLength": "bpL!zlx Lbu?z,Wym.g3P!,. UE,RGT!c!64y7p?hOpTOh.", + "maxProperties": "4 .,", + "maximum": 19.61703789371374, + "minProperties": "!,Y.!s.?UW o,Q??!.f6!?o i", + "required": [ + "?,? ?G,? nW.mg2e?02 R5M a6E0IP?!.,035M,,86?.", + "PFq?L", + ".k,,?.BB6j6J" + ], + "title": "0..QG7D,u.P?pJqM?f1?x?D,?xu0SKP,", + "type": "ARRAY" + }, + "speechConfig": { + "languageCode": "9avd?RZ?9hs.,n?6?!HX?.", + "voiceConfig": { + "prebuiltVoiceConfig": {} + } + }, + "stopSequences": [ + ",9BK hokEj Y9zD?,k!,??KLXuP?7l,,5y" + ], + "thinkingConfig": { + "thinkingBudget": 763 + }, + "topK": 207, + "topP": -3.3063070170643147 + }, + "model": "4 .,,9I4xTh,1 w?,.3rYaJMR!?? Vq.H.!.kiKyp0,?!h?D9", + "toolConfig": {} +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-65163e2b3d0cab80.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-65163e2b3d0cab80.meta.json new file mode 100644 index 00000000..b8e47420 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-65163e2b3d0cab80.meta.json @@ -0,0 +1,9 @@ +{ + "issues": [ + "lost: tools", + "changed: contents[0].parts.length (1 -> 0)", + "changed: contents[0].role (\"a\" -> \"user\")" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-65163e2b3d0cab80.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-65163e2b3d0cab80.request.json new file mode 100644 index 00000000..17c74e9e --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-65163e2b3d0cab80.request.json @@ -0,0 +1,15 @@ +{ + "contents": [ + { + "parts": [ + { + "inlineData": {} + } + ], + "role": "a" + } + ], + "tools": [ + {} + ] +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-97dcb376a3bad1c9.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-97dcb376a3bad1c9.meta.json new file mode 100644 index 00000000..fa0d81b8 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-97dcb376a3bad1c9.meta.json @@ -0,0 +1,14 @@ +{ + "issues": [ + "lost: systemInstruction", + "lost: contents[0].parts[0].thoughtSignature", + "lost: contents[0].parts[0].thought", + "lost: contents[0].parts[0].fileData", + "lost: contents[0].parts[0].videoMetadata", + "lost: contents[0].parts[0].functionCall", + "added: contents[0].parts[0].inlineData", + "changed: contents[0].role (\"N,L770L K,H,?,.l4n ,\" -> \"user\")" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-97dcb376a3bad1c9.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-97dcb376a3bad1c9.request.json new file mode 100644 index 00000000..70069d2d --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-97dcb376a3bad1c9.request.json @@ -0,0 +1,24 @@ +{ + "contents": [ + { + "parts": [ + { + "fileData": { + "fileUri": "f,T?W.46R?.?!i.?", + "mimeType": "!?SN.U?oo8.u,.!LJ!?,,??5nY? P?e,IN57N.,4Hw,9 " + }, + "functionCall": { + "args": {} + }, + "thought": true, + "thoughtSignature": ",?U!bl.87n?e.q34Um,? ", + "videoMetadata": { + "startOffset": "Hfb5..I4?6iQ.bN1.se23,,4,J5,7?, ." + } + } + ], + "role": "N,L770L K,H,?,.l4n ," + } + ], + "systemInstruction": {} +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-a2d3e2321403c3b4.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-a2d3e2321403c3b4.meta.json new file mode 100644 index 00000000..6f2af3d2 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-a2d3e2321403c3b4.meta.json @@ -0,0 +1,16 @@ +{ + "issues": [ + "lost: toolConfig", + "lost: contents[0].parts[0].partMetadata", + "lost: contents[0].parts[0].thoughtSignature", + "lost: contents[0].parts[0].codeExecutionResult", + "lost: contents[0].parts[0].executableCode", + "lost: contents[0].parts[0].functionCall", + "added: generationConfig.responseSchema", + "added: generationConfig.thinkingConfig.thinkingBudget", + "changed: contents[0].role (\"!,B?.p,.J9H\" -> \"user\")", + "changed: generationConfig.thinkingConfig.includeThoughts (false -> true)" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-a2d3e2321403c3b4.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-a2d3e2321403c3b4.request.json new file mode 100644 index 00000000..73f6e249 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-a2d3e2321403c3b4.request.json @@ -0,0 +1,50 @@ +{ + "cachedContent": "..? !?3?!?U4,F!H7.B,!,?,t,?zP !F,.Z?,?XGJa", + "contents": [ + { + "parts": [ + { + "codeExecutionResult": { + "outcome": "OUTCOME_DEADLINE_EXCEEDED", + "output": "NSO?6?r,T !.8Y kfN.!" + }, + "executableCode": {}, + "functionCall": { + "args": {}, + "id": "b!V!qCx?Ov?Qm?9FKKY,t,?8", + "name": "?AX4??PUX.m.9ZJ.w.o,o7XVJJ.?y?.!Bt 4!.2.0,?,l ,2," + }, + "inlineData": { + "data": "ARCQ?3tWv!?Ss.aA!1XUZ!6G?,6e.2D??,D?!5TA", + "mimeType": "p.b,28!m?k,Zz20td.83n,.WiazGM?lB!,.I2GIy," + }, + "partMetadata": {}, + "thoughtSignature": "y.Rb?? .,??V?7...,O.!!?pXEh,b?.?,?Pg!?!!F91.c ,xd." + } + ], + "role": "!,B?.p,.J9H" + } + ], + "generationConfig": { + "_responseJsonSchema": "o8 2Wo hA3MC q 8v A0nfE", + "enableEnhancedCivicAnswers": false, + "imageConfig": {}, + "logprobs": 397, + "maxOutputTokens": 650, + "presencePenalty": -24.12705797021374, + "responseModalities": [ + "TEXT", + "MODALITY_UNSPECIFIED" + ], + "speechConfig": { + "voiceConfig": {} + }, + "temperature": -85.88575612520403, + "thinkingConfig": { + "includeThoughts": false + }, + "topP": -20.02183526273265 + }, + "model": "!PB!YL,lai...XDJ,x??e,L0.3?3..w?C905?", + "toolConfig": {} +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-c18e28e85b79bba8.meta.json b/payloads/fuzz-snapshots/google-roundtrip/case-c18e28e85b79bba8.meta.json new file mode 100644 index 00000000..f7ca4327 --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-c18e28e85b79bba8.meta.json @@ -0,0 +1,13 @@ +{ + "issues": [ + "lost: systemInstruction", + "lost: toolConfig", + "lost: tools[0].urlContext", + "lost: tools[0].fileSearch", + "lost: tools[0].computerUse", + "lost: tools[0].googleMaps", + "changed: contents.length (3 -> 1)" + ], + "kind": "request-roundtrip", + "provider": "google" +} diff --git a/payloads/fuzz-snapshots/google-roundtrip/case-c18e28e85b79bba8.request.json b/payloads/fuzz-snapshots/google-roundtrip/case-c18e28e85b79bba8.request.json new file mode 100644 index 00000000..f29d206e --- /dev/null +++ b/payloads/fuzz-snapshots/google-roundtrip/case-c18e28e85b79bba8.request.json @@ -0,0 +1,116 @@ +{ + "cachedContent": " , oQC,?,,q,, p5?.94nKlh?1X?,S,W", + "contents": [ + { + "parts": [ + { + "functionCall": { + "args": {} + }, + "functionResponse": { + "id": "XZo.5,Co. P", + "scheduling": "SCHEDULING_UNSPECIFIED" + }, + "mediaResolution": { + "level": "MEDIA_RESOLUTION_LOW" + }, + "partMetadata": {}, + "videoMetadata": { + "endOffset": "Z?!g!rE,53E,2Z?9YAmEOJK.Mx.31?V,4 !3 !Z," + } + } + ], + "role": ",B.?!8??,y!?" + }, + { + "parts": [ + { + "executableCode": { + "code": ",q3gj8!T,.Dg.?!k8d?C,F!J7?q?!Pwb.." + }, + "text": "!,? ? ! ", + "thoughtSignature": "O.Vla, !" + }, + { + "codeExecutionResult": {}, + "fileData": { + "fileUri": "Bbv..,!gr6c3.,,,,!6.p!Hkg.Cx11 ,.Fx" + }, + "inlineData": { + "data": ".4??,Kyu 4?S?c?.,9HXyV,,1F.RN.,8gx,,P?", + "mimeType": "?G?,.!?,?? Vg.s7Y,gb,hV?1.?Tz0Vg2" + }, + "mediaResolution": {}, + "partMetadata": {}, + "videoMetadata": { + "endOffset": "A.,!H5.3?TuvHw!lM" + } + }, + { + "inlineData": {}, + "mediaResolution": {}, + "partMetadata": {}, + "text": "8U?,Xlzb?.,I!6R?.Q9Zi???Uf0E", + "thoughtSignature": "?b7?hP,7?Zau F!.Pxw.b", + "videoMetadata": { + "startOffset": "!?B..S ,e.,..?.ox50o.?4.,.?Tf!Ig A" + } + } + ], + "role": ",,,!" + }, + { + "parts": [ + { + "codeExecutionResult": { + "outcome": "OUTCOME_UNSPECIFIED", + "output": "F?.?D2" + }, + "partMetadata": {}, + "text": ",,w?,aq2pq", + "thought": true, + "thoughtSignature": "5?0doC" + } + ], + "role": "?!?z..FPc.6 !??en?1!T?.,sq9zWp" + } + ], + "systemInstruction": { + "parts": [ + { + "codeExecutionResult": { + "output": "o?oKV,X.gyZ?.?C?7..rJG Ci?o?,QIO9N51M8,IjEF" + }, + "fileData": { + "mimeType": "K1.A .?w8!" + }, + "functionResponse": { + "parts": [ + {} + ], + "scheduling": "WHEN_IDLE", + "willContinue": true + }, + "partMetadata": {}, + "text": ".,. ,??68F?wZtr WCfmVZ.!.,F0z,!zA.v" + } + ] + }, + "toolConfig": {}, + "tools": [ + { + "computerUse": { + "environment": "ENVIRONMENT_BROWSER" + }, + "fileSearch": { + "metadataFilter": ",4W L.ApfF?a,3m! ? ?fb. 4SIgm8", + "topK": 382 + }, + "googleMaps": { + "enableWidget": false + }, + "googleSearch": {}, + "urlContext": {} + } + ] +} diff --git a/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap b/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap index e5309657..fba6ae81 100644 --- a/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap +++ b/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap @@ -2499,7 +2499,6 @@ exports[`chat-completions → google > complexReasoningRequest > request 1`] = ` ], "generationConfig": { "maxOutputTokens": 20000, - "responseSchema": null, }, "model": "gemini-2.5-flash", } @@ -2616,6 +2615,9 @@ exports[`chat-completions → google > frequencyPenaltyParam > request 1`] = ` "role": "user", }, ], + "generationConfig": { + "frequencyPenalty": 0.5, + }, "model": "gemini-2.5-flash", } `; @@ -2798,7 +2800,6 @@ exports[`chat-completions → google > maxCompletionTokensParam > request 1`] = ], "generationConfig": { "maxOutputTokens": 500, - "responseSchema": null, }, "model": "gemini-2.5-flash", } @@ -2895,7 +2896,6 @@ exports[`chat-completions → google > multimodalRequest > request 1`] = ` ], "generationConfig": { "maxOutputTokens": 300, - "responseSchema": null, }, "model": "gemini-2.5-flash", } @@ -3279,6 +3279,9 @@ exports[`chat-completions → google > presencePenaltyParam > request 1`] = ` "role": "user", }, ], + "generationConfig": { + "presencePenalty": 0.5, + }, "model": "gemini-2.5-flash", } `; @@ -3368,7 +3371,6 @@ exports[`chat-completions → google > reasoningEffortLowParam > request 1`] = ` }, ], "generationConfig": { - "responseSchema": null, "thinkingConfig": { "includeThoughts": true, "thinkingBudget": 1024, @@ -3500,7 +3502,6 @@ exports[`chat-completions → google > reasoningRequestTruncated > request 1`] = ], "generationConfig": { "maxOutputTokens": 100, - "responseSchema": null, }, "model": "gemini-2.5-flash", } @@ -3547,7 +3548,6 @@ exports[`chat-completions → google > reasoningSummaryParam > request 1`] = ` }, ], "generationConfig": { - "responseSchema": null, "thinkingConfig": { "includeThoughts": true, "thinkingBudget": 2048, @@ -3704,6 +3704,9 @@ exports[`chat-completions → google > seedParam > request 1`] = ` "role": "user", }, ], + "generationConfig": { + "seed": 12345, + }, "model": "gemini-2.5-flash", } `; @@ -3793,7 +3796,6 @@ exports[`chat-completions → google > simpleRequest > request 1`] = ` }, ], "generationConfig": { - "responseSchema": null, "thinkingConfig": { "includeThoughts": true, "thinkingBudget": 1024, @@ -3849,7 +3851,6 @@ exports[`chat-completions → google > stopSequencesParam > request 1`] = ` }, ], "generationConfig": { - "responseSchema": null, "stopSequences": [ "10", "ten", @@ -3945,7 +3946,6 @@ exports[`chat-completions → google > systemMessageArrayContent > request 1`] = ], "generationConfig": { "maxOutputTokens": 300, - "responseSchema": null, }, "model": "gemini-2.5-flash", "systemInstruction": { @@ -4017,7 +4017,6 @@ exports[`chat-completions → google > temperatureParam > request 1`] = ` }, ], "generationConfig": { - "responseSchema": null, "temperature": 0.7, }, "model": "gemini-2.5-flash", @@ -4066,7 +4065,6 @@ exports[`chat-completions → google > textFormatJsonObjectParam > request 1`] = ], "generationConfig": { "responseMimeType": "application/json", - "responseSchema": null, }, "model": "gemini-2.5-flash", } @@ -4244,7 +4242,6 @@ exports[`chat-completions → google > textFormatTextParam > request 1`] = ` ], "generationConfig": { "responseMimeType": "text/plain", - "responseSchema": null, }, "model": "gemini-2.5-flash", } @@ -4459,7 +4456,6 @@ exports[`chat-completions → google > topPParam > request 1`] = ` }, ], "generationConfig": { - "responseSchema": null, "topP": 0.9, }, "model": "gemini-2.5-flash", diff --git a/scripts/validate_google_payload.py b/scripts/validate_google_payload.py new file mode 100755 index 00000000..44ac4e5a --- /dev/null +++ b/scripts/validate_google_payload.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Send a Google Gemini payload to the real API and report whether it's accepted. + +Usage: + # Validate the minimal fuzz case: + python scripts/validate_google_payload.py '{"contents": [{}]}' + + # Validate from a snapshot file: + python scripts/validate_google_payload.py payloads/fuzz-snapshots/google-roundtrip/case-XYZ.request.json + + # Validate all snapshots in a directory: + python scripts/validate_google_payload.py payloads/fuzz-snapshots/google-roundtrip/ + +Requires GEMINI_API_KEY (or GOOGLE_API_KEY) in the environment. +""" + +import json +import os +import sys +from pathlib import Path +from urllib.error import HTTPError +from urllib.request import Request, urlopen + +MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.0-flash") +API_KEY = os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY") +BASE_URL = "https://generativelanguage.googleapis.com/v1beta" + + +def validate_payload(payload: dict, label: str = "") -> bool: + url = f"{BASE_URL}/models/{MODEL}:generateContent?key={API_KEY}" + data = json.dumps(payload).encode() + req = Request(url, data=data, headers={"Content-Type": "application/json"}) + + prefix = f"[{label}] " if label else "" + try: + resp = urlopen(req) + body = json.loads(resp.read()) + print(f"{prefix}ACCEPTED (200) -- API processed the payload") + if "candidates" in body: + text = ( + body["candidates"][0] + .get("content", {}) + .get("parts", [{}])[0] + .get("text", "")[:80] + ) + if text: + print(f" response: {text!r}") + return True + except HTTPError as e: + body = e.read().decode() + try: + err = json.loads(body) + msg = err.get("error", {}).get("message", body[:200]) + status = err.get("error", {}).get("status", e.code) + except json.JSONDecodeError: + msg = body[:200] + status = e.code + print(f"{prefix}REJECTED ({status}) -- {msg}") + return False + + +def load_payload(arg: str) -> list[tuple[str, dict]]: + """Return list of (label, payload) from a JSON string, file, or directory.""" + path = Path(arg) + + # Directory: load all .request.json files + if path.is_dir(): + results = [] + for f in sorted(path.glob("*.request.json")): + with open(f) as fh: + results.append((f.name, json.load(fh))) + return results + + # File + if path.is_file(): + with open(path) as fh: + return [(path.name, json.load(fh))] + + # Inline JSON string + try: + return [("inline", json.loads(arg))] + except json.JSONDecodeError: + print(f"Error: not a valid JSON string, file, or directory: {arg}", file=sys.stderr) + sys.exit(1) + + +def main(): + if not API_KEY: + print("Set GEMINI_API_KEY or GOOGLE_API_KEY environment variable.", file=sys.stderr) + sys.exit(1) + + if len(sys.argv) < 2: + print(__doc__.strip()) + sys.exit(1) + + payloads = load_payload(sys.argv[1]) + if not payloads: + print("No payloads found.", file=sys.stderr) + sys.exit(1) + + accepted = 0 + rejected = 0 + for label, payload in payloads: + if validate_payload(payload, label): + accepted += 1 + else: + rejected += 1 + + if len(payloads) > 1: + print(f"\n--- {accepted} accepted, {rejected} rejected (of {len(payloads)}) ---") + + sys.exit(0 if rejected == 0 else 1) + + +if __name__ == "__main__": + main()