diff --git a/crates/generate-types/src/main.rs b/crates/generate-types/src/main.rs index 567ca239..a69c8c66 100644 --- a/crates/generate-types/src/main.rs +++ b/crates/generate-types/src/main.rs @@ -1689,6 +1689,11 @@ fn post_process_quicktype_output_for_google(quicktype_output: &str) -> String { // "STRING" (Google native) and "string" (OpenAI JSON Schema) during deserialization processed = add_type_enum_lowercase_aliases(&processed); + // Google's Discovery spec represents Schema int64 fields as protobuf-JSON + // strings. Accept numeric JSON Schema input too, while preserving string + // serialization in the generated Google type. + processed = add_google_schema_int64_deserializers(&processed); + processed } @@ -1750,3 +1755,75 @@ fn add_type_enum_lowercase_aliases(content: &str) -> String { result_lines.join("\n") } + +fn add_google_schema_int64_deserializers(content: &str) -> String { + const INT64_SCHEMA_FIELDS: &[&str] = &[ + "max_items", + "min_items", + "min_properties", + "max_properties", + "min_length", + "max_length", + ]; + + let mut processed = content.to_string(); + let helper = r#" +fn deserialize_optional_i64_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum I64String { + String(String), + I64(i64), + U64(u64), + } + + Ok(Option::::deserialize(deserializer)?.map(|value| match value { + I64String::String(value) => value, + I64String::I64(value) => value.to_string(), + I64String::U64(value) => value.to_string(), + })) +} +"#; + + if let Some(insert_at) = processed.find("#[derive(") { + processed.insert_str(insert_at, helper); + } + + let lines: Vec<&str> = processed.lines().collect(); + let mut result_lines = Vec::new(); + let mut in_schema = false; + + for line in lines { + let trimmed = line.trim(); + + if trimmed == "pub struct Schema {" || trimmed.ends_with("pub struct Schema {") { + in_schema = true; + } else if in_schema && trimmed == "}" { + in_schema = false; + } + + if in_schema + && INT64_SCHEMA_FIELDS + .iter() + .any(|field| trimmed == format!("pub {field}: Option,")) + { + let already_has_attr = result_lines + .last() + .is_some_and(|prev: &String| prev.contains("deserialize_optional_i64_string")); + if !already_has_attr { + let indent = line.len() - line.trim_start().len(); + result_lines.push(format!( + "{}#[serde(default, deserialize_with = \"deserialize_optional_i64_string\")]", + " ".repeat(indent) + )); + } + } + + result_lines.push(line.to_string()); + } + + result_lines.join("\n") +} diff --git a/crates/lingua/src/providers/google/convert.rs b/crates/lingua/src/providers/google/convert.rs index dec4cf2f..52e74023 100644 --- a/crates/lingua/src/providers/google/convert.rs +++ b/crates/lingua/src/providers/google/convert.rs @@ -792,11 +792,15 @@ impl From<&FunctionDeclaration> for UniversalTool { fn normalize_google_schema_types(mut value: Value) -> Value { match &mut value { Value::Object(map) => { - if let Some(Value::String(t)) = map.get_mut("type") { - *t = t.to_lowercase(); - } map.retain(|_, v| !v.is_null()); - for v in map.values_mut() { + for (key, v) in map.iter_mut() { + if key == "type" { + if let Value::String(t) = v { + *t = t.to_lowercase(); + } + } else if is_google_schema_int64_keyword(key) { + normalize_google_schema_int64_value(v); + } *v = normalize_google_schema_types(std::mem::take(v)); } } @@ -810,6 +814,25 @@ fn normalize_google_schema_types(mut value: Value) -> Value { value } +fn is_google_schema_int64_keyword(key: &str) -> bool { + matches!( + key, + "minLength" | "maxLength" | "minItems" | "maxItems" | "minProperties" | "maxProperties" + ) +} + +fn normalize_google_schema_int64_value(value: &mut Value) { + let Value::String(s) = value else { + return; + }; + + let Ok(parsed) = s.parse::() else { + return; + }; + + *value = Value::Number(parsed.into()); +} + /// Recursively strip `exclusiveMinimum` fields from a JSON schema value in-place. /// /// Google's `Schema` proto does not recognise `exclusiveMinimum` (a JSON Schema Draft 6+ @@ -1567,6 +1590,53 @@ mod tests { assert!(tool.is_function()); } + #[test] + fn test_function_declaration_schema_int64_fields_normalize_to_json_numbers() { + let decl: FunctionDeclaration = serde_json::from_value(json!({ + "name": "demo", + "description": "demo tool", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": "9" + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1, + "maxItems": "3" + } + } + } + })) + .unwrap(); + + let tool = UniversalTool::from(&decl); + + assert_eq!( + tool.parameters, + Some(json!({ + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 9 + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1, + "maxItems": 3 + } + } + })) + ); + } + #[test] fn test_universal_tool_to_function_declaration() { let tool = UniversalTool::function( diff --git a/crates/lingua/src/providers/google/detect.rs b/crates/lingua/src/providers/google/detect.rs index 415e401c..a226886b 100644 --- a/crates/lingua/src/providers/google/detect.rs +++ b/crates/lingua/src/providers/google/detect.rs @@ -77,6 +77,61 @@ mod tests { assert!(try_parse_google(&payload).is_ok()); } + #[test] + fn test_try_parse_google_accepts_numeric_schema_int64_fields() { + let payload = json!({ + "contents": [{"role": "user", "parts": [{"text": "Hello"}]}], + "tools": [{ + "functionDeclarations": [{ + "name": "demo", + "description": "demo tool", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 9 + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1, + "maxItems": 3 + } + } + } + }] + }] + }); + + assert!(try_parse_google(&payload).is_ok()); + } + + #[test] + fn test_try_parse_google_accepts_string_schema_int64_fields() { + let payload = json!({ + "contents": [{"role": "user", "parts": [{"text": "Hello"}]}], + "tools": [{ + "functionDeclarations": [{ + "name": "demo", + "description": "demo tool", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": "1" + } + } + } + }] + }] + }); + + assert!(try_parse_google(&payload).is_ok()); + } + #[test] fn test_try_parse_google_with_model_role() { let payload = json!({ diff --git a/crates/lingua/src/providers/google/generated.rs b/crates/lingua/src/providers/google/generated.rs index 3be5562e..797aeb77 100644 --- a/crates/lingua/src/providers/google/generated.rs +++ b/crates/lingua/src/providers/google/generated.rs @@ -21,6 +21,26 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use ts_rs::TS; +fn deserialize_optional_i64_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum I64String { + String(String), + I64(i64), + U64(u64), + } + + Ok( + Option::::deserialize(deserializer)?.map(|value| match value { + I64String::String(value) => value, + I64String::I64(value) => value.to_string(), + I64String::U64(value) => value.to_string(), + }), + ) +} #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, TS)] #[ts(export_to = "google/")] pub struct GoogleSchemas { @@ -940,12 +960,15 @@ pub struct Schema { pub maximum: Option, /// Optional. Maximum number of the elements for Type.ARRAY. #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, deserialize_with = "deserialize_optional_i64_string")] pub max_items: Option, /// Optional. Maximum length of the Type.STRING #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, deserialize_with = "deserialize_optional_i64_string")] pub max_length: Option, /// Optional. Maximum number of the properties for Type.OBJECT. #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, deserialize_with = "deserialize_optional_i64_string")] pub max_properties: Option, /// Optional. SCHEMA FIELDS FOR TYPE INTEGER and NUMBER Minimum value of the Type.INTEGER and /// Type.NUMBER @@ -953,12 +976,15 @@ pub struct Schema { pub minimum: Option, /// Optional. Minimum number of the elements for Type.ARRAY. #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, deserialize_with = "deserialize_optional_i64_string")] pub min_items: Option, /// Optional. SCHEMA FIELDS FOR TYPE STRING Minimum length of the Type.STRING #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, deserialize_with = "deserialize_optional_i64_string")] pub min_length: Option, /// Optional. Minimum number of the properties for Type.OBJECT. #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, deserialize_with = "deserialize_optional_i64_string")] pub min_properties: Option, /// Optional. Indicates if the value may be null. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/payloads/cases/params.ts b/payloads/cases/params.ts index 17628752..35965f4c 100644 --- a/payloads/cases/params.ts +++ b/payloads/cases/params.ts @@ -5,7 +5,7 @@ import { Modality, MediaResolution, } from "@google/genai"; -import { TestCaseCollection } from "./types"; +import { TestCase, TestCaseCollection } from "./types"; import { OPENAI_CHAT_COMPLETIONS_MODEL, OPENAI_RESPONSES_MODEL, @@ -2249,6 +2249,52 @@ export const paramsCases: TestCaseCollection = { bedrock: null, }, + googleToolSchemaNumericInt64Param: (() => { + const indexNameSchema: Record = { + type: Type.STRING, + minLength: 1, + maxLength: 128, + }; + const tagsSchema: Record = { + type: Type.ARRAY, + items: { type: Type.STRING }, + minItems: 1, + maxItems: 3, + }; + + const testCase: TestCase = { + "chat-completions": null, + responses: null, + anthropic: null, + google: { + model: GOOGLE_MODEL, + contents: [ + { role: "user", parts: [{ text: "Validate tool schema bounds." }] }, + ], + tools: [ + { + functionDeclarations: [ + { + name: "validate_bounds", + description: "Validate bounded string and array inputs.", + parameters: { + type: Type.OBJECT, + properties: { + index_name: indexNameSchema, + tags: tagsSchema, + }, + required: ["index_name", "tags"], + }, + }, + ], + }, + ], + }, + bedrock: null, + }; + return testCase; + })(), + exclusiveMinimumToolParam: { "chat-completions": { model: OPENAI_CHAT_COMPLETIONS_MODEL, diff --git a/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap b/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap index 46243f10..4a61f908 100644 --- a/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap +++ b/payloads/scripts/transforms/__snapshots__/transforms.test.ts.snap @@ -18334,6 +18334,124 @@ exports[`google → anthropic > googleResponseSchemaPropertyOrderingParam > resp } `; +exports[`google → anthropic > googleToolSchemaNumericInt64Param > request 1`] = ` +{ + "max_tokens": 4096, + "messages": [ + { + "content": "Validate tool schema bounds.", + "role": "user", + }, + ], + "model": "claude-sonnet-4-5-20250929", + "tools": [ + { + "description": "Validate bounded string and array inputs.", + "input_schema": { + "properties": { + "index_name": { + "maxLength": 128, + "minLength": 1, + "type": "string", + }, + "tags": { + "items": { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + }, + "required": [ + "index_name", + "tags", + ], + "type": "object", + }, + "name": "validate_bounds", + }, + ], +} +`; + +exports[`google → anthropic > googleToolSchemaNumericInt64Param > response 1`] = ` +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "I'll help you validate the bounds of the tool schema by testing various scenarios. Let me make several calls to test different boundary conditions:", + }, + { + "functionCall": { + "args": { + "index_name": "a", + "tags": [ + "tag1", + ], + }, + "id": "toolu_01PMw5ePG7oTsYwA1GRVmrY6", + "name": "validate_bounds", + }, + }, + { + "functionCall": { + "args": { + "index_name": "this_is_exactly_128_characters_long_string_padding_with_more_text_to_reach_the_limit_and_ensure_we_hit_maximum_boundary_check_X", + "tags": [ + "tag1", + "tag2", + "tag3", + ], + }, + "id": "toolu_012eTSUrPPrFSCmsY8mpptbb", + "name": "validate_bounds", + }, + }, + { + "functionCall": { + "args": { + "index_name": "valid_index_name", + "tags": [ + "minimum", + ], + }, + "id": "toolu_015YDkvKcTjt4atdyf8B3VWY", + "name": "validate_bounds", + }, + }, + { + "functionCall": { + "args": { + "index_name": "another_valid_index", + "tags": [ + "tag1", + "tag2", + ], + }, + "id": "toolu_01FTQMSoXB3Vqqmtz3AizfoV", + "name": "validate_bounds", + }, + }, + ], + "role": "model", + }, + "finishReason": "STOP", + "index": 0, + }, + ], + "modelVersion": "claude-sonnet-4-5-20250929", + "usageMetadata": { + "cachedContentTokenCount": 0, + "candidatesTokenCount": 340, + "promptTokenCount": 619, + "totalTokenCount": 959, + }, +} +`; + exports[`google → anthropic > imageConfigParam > request 1`] = ` { "max_tokens": 4096, @@ -20743,6 +20861,85 @@ exports[`google → chat-completions > googleResponseSchemaPropertyOrderingParam } `; +exports[`google → chat-completions > googleToolSchemaNumericInt64Param > request 1`] = ` +{ + "messages": [ + { + "content": "Validate tool schema bounds.", + "role": "user", + }, + ], + "model": "gpt-5-nano", + "tools": [ + { + "function": { + "description": "Validate bounded string and array inputs.", + "name": "validate_bounds", + "parameters": { + "properties": { + "index_name": { + "maxLength": 128, + "minLength": 1, + "type": "string", + }, + "tags": { + "items": { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + }, + "required": [ + "index_name", + "tags", + ], + "type": "object", + }, + }, + "type": "function", + }, + ], +} +`; + +exports[`google → chat-completions > googleToolSchemaNumericInt64Param > response 1`] = ` +{ + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "args": { + "index_name": "example", + "tags": [ + "tag1", + "tag2", + ], + }, + "name": "validate_bounds", + }, + }, + ], + "role": "model", + }, + "finishReason": "STOP", + "index": 0, + }, + ], + "modelVersion": "gpt-5-nano-2025-08-07", + "usageMetadata": { + "cachedContentTokenCount": 0, + "candidatesTokenCount": 33, + "promptTokenCount": 162, + "thoughtsTokenCount": 832, + "totalTokenCount": 1027, + }, +} +`; + exports[`google → chat-completions > imageConfigParam > request 1`] = ` { "messages": [ @@ -23040,6 +23237,103 @@ exports[`google → responses > googleResponseSchemaPropertyOrderingParam > resp } `; +exports[`google → responses > googleToolSchemaNumericInt64Param > request 1`] = ` +{ + "input": [ + { + "content": "Validate tool schema bounds.", + "role": "user", + }, + ], + "model": "gpt-5-nano", + "tools": [ + { + "description": "Validate bounded string and array inputs.", + "name": "validate_bounds", + "parameters": { + "properties": { + "index_name": { + "maxLength": 128, + "minLength": 1, + "type": "string", + }, + "tags": { + "items": { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + }, + "required": [ + "index_name", + "tags", + ], + "type": "object", + }, + "type": "function", + }, + ], +} +`; + +exports[`google → responses > googleToolSchemaNumericInt64Param > response 1`] = ` +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "", + "thought": true, + }, + ], + "role": "model", + }, + "finishReason": "STOP", + "index": 0, + }, + { + "content": { + "parts": [ + { + "text": "Here are the bounds the tool schema enforces: + +- index_name: string with length between 1 and 128 (inclusive) +- tags: array of strings with length between 1 and 3 (inclusive); each item is a string + +If you’d like, I can validate specific inputs. Examples: + +- Valid: + - index_name = "users" + - tags = ["admin"] +- Invalid: + - index_name = "" (violates minLength 1) + - index_name = "a" repeated 129 times (violates maxLength 128) + - tags = [] (violates minItems 1) + - tags = ["t1", "t2", "t3", "t4"] (violates maxItems 3) + +Please provide values for index_name and tags (an array of strings), and I’ll run the validation. Or tell me to run a few test cases for you.", + }, + ], + "role": "model", + }, + "finishReason": "STOP", + "index": 1, + }, + ], + "modelVersion": "gpt-5-nano-2025-08-07", + "usageMetadata": { + "cachedContentTokenCount": 0, + "candidatesTokenCount": 238, + "promptTokenCount": 80, + "thoughtsTokenCount": 960, + "totalTokenCount": 1278, + }, +} +`; + exports[`google → responses > imageConfigParam > request 1`] = ` { "input": [ diff --git a/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-request.json b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-request.json new file mode 100644 index 00000000..0e11b688 --- /dev/null +++ b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-request.json @@ -0,0 +1,62 @@ +{ + "model": "gemini-2.5-flash", + "contents": [ + { + "role": "user", + "parts": [ + { + "text": "Validate tool schema bounds." + } + ] + }, + { + "role": "model", + "parts": [ + { + "text": "I can do that. What is the `index_name` and `tags` (list of strings) you want to validate?", + "thoughtSignature": "Cu0BAQw51seMqrFmFnucSlGqnmQJOgD0sb+XT9VWZcJLZ3E8yb1dLaBSoXvI1JQGhBr8OxNdYx0AB639dy6/LaPvlh8AYFDrBj70SaX8AckSASuG/yvubgNZquVxt1Ujc7QKayINMrsEkbOKztYyRg1k3Bw7ByLfJpbVWCkVGclUtSSG9UxWe6oymeOohAfTB8wFet8EH3fP85c/uXgegWV9KqH3LmWqRKvfJ4wIJ20TEtVYSdvIJp2K5cp0jK7B0F64N6oH9mU8EN5uhfG4Y/wOtPsyeTVh8NqJsHMyPriiBijUoYnxbxC6wmbghGe3" + } + ] + }, + { + "role": "user", + "parts": [ + { + "text": "What should I do next?" + } + ] + } + ], + "tools": [ + { + "functionDeclarations": [ + { + "name": "validate_bounds", + "description": "Validate bounded string and array inputs.", + "parameters": { + "type": "OBJECT", + "properties": { + "index_name": { + "type": "STRING", + "minLength": 1, + "maxLength": 128 + }, + "tags": { + "type": "ARRAY", + "items": { + "type": "STRING" + }, + "minItems": 1, + "maxItems": 3 + } + }, + "required": [ + "index_name", + "tags" + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-response-streaming.json b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-response-streaming.json new file mode 100644 index 00000000..b3ad9571 --- /dev/null +++ b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-response-streaming.json @@ -0,0 +1,64 @@ +[ + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Please provide the `index_name` and `tags` (as a list of strings) so I can validate the", + "thoughtSignature": "CmQBDDnWx9OXW813/tt8IX/WCiMMogtmjdkMyDj4YuARMzZFKtEahM06bYSwaVgF2mEcD9JZVmMLdcMOYCIPhHXCNZKQOZTF0YOTcIw7pF8k3BfNnohMDcjpBXSHxlfj8J4iCIPgCoUBAQw51sex+TbhFiplxsIy+CEkbt2ldSHA1aDmoWCAJzOvPiGvSPjbuIic04OQ85jD1YXtppmcDUaULUkJgEAfvHOCd5KTYKCayDMILMbE47m2TXsf/YGETOCmPxM0ocTp0d6Y3jarb9igzT/PrfJK/5ooLZJ5ec4qZFHhHqpbkCo6FleVRA==" + } + ], + "role": "model" + }, + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 97, + "candidatesTokenCount": 24, + "totalTokenCount": 161, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 97 + } + ], + "thoughtsTokenCount": 40, + "serviceTier": "standard" + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "Hq4ZaoCiO6vi_uMP6KW32Q4" + }, + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": " tool schema bounds." + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 97, + "candidatesTokenCount": 28, + "totalTokenCount": 165, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 97 + } + ], + "thoughtsTokenCount": 40, + "serviceTier": "standard" + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "Hq4ZaoCiO6vi_uMP6KW32Q4" + } +] \ No newline at end of file diff --git a/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-response.json b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-response.json new file mode 100644 index 00000000..ce71f6bc --- /dev/null +++ b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/followup-response.json @@ -0,0 +1,30 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Please provide the `index_name` and `tags` so I can validate the tool schema bounds. \n" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 97, + "candidatesTokenCount": 21, + "totalTokenCount": 118, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 97 + } + ], + "serviceTier": "standard" + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "Hq4ZatyWO8Wk_uMPzKrsuQw" +} \ No newline at end of file diff --git a/payloads/snapshots/googleToolSchemaNumericInt64Param/google/request.json b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/request.json new file mode 100644 index 00000000..2fc82283 --- /dev/null +++ b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/request.json @@ -0,0 +1,45 @@ +{ + "model": "gemini-2.5-flash", + "contents": [ + { + "role": "user", + "parts": [ + { + "text": "Validate tool schema bounds." + } + ] + } + ], + "tools": [ + { + "functionDeclarations": [ + { + "name": "validate_bounds", + "description": "Validate bounded string and array inputs.", + "parameters": { + "type": "OBJECT", + "properties": { + "index_name": { + "type": "STRING", + "minLength": 1, + "maxLength": 128 + }, + "tags": { + "type": "ARRAY", + "items": { + "type": "STRING" + }, + "minItems": 1, + "maxItems": 3 + } + }, + "required": [ + "index_name", + "tags" + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/payloads/snapshots/googleToolSchemaNumericInt64Param/google/response-streaming.json b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/response-streaming.json new file mode 100644 index 00000000..ed9d99a1 --- /dev/null +++ b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/response-streaming.json @@ -0,0 +1,64 @@ +[ + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "I", + "thoughtSignature": "CiQBDDnWx1pziXrm4EnTxx04NfY/KGP3llYj9EW9B9sO6qMv+jcKdwEMOdbHXaBrAJYSLf/94T7c0lSjmrC6QcJ4asbFn7ZTvsExmQ2qLXcQAf9ZMjhZGikSRZWPXmFXgmL84/ChbL4CmIws/qYYK8fq0jlrktoifvGvmM5mXW96BlzUapvhjGB15oDy0iPWRI0wApUGgbuMGwyUgbbaCvIBAQw51sd4G6tXg6iI5pHK5zR0nh6qq7tdgqYOATPJyi6F1tC1f+5UxlsxP61KrfyHWh2Wsgq3KteGmE8BupeFlRTy44205vlOBEei/IoAprAMXzEsYl9gHlOzTGEqxjTtJoi6dI/1vdAFpI/vJTc1pkugY1H4khYQ8a3lHo9T0bORVh3Z48iMNZKXWhwrAMUALSZ+zqWZcr7yVa0JNPzZFZAiNFehCyclca6utBpRUpAsOJGOCa8gAWcTR9I4TLobRYIRZPzSMVY1n9vBjlcdhHNzHQFAINo64ncZ5mfr5AuzZIrK/uiAdFaL6o8382/qhok=" + } + ], + "role": "model" + }, + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 62, + "candidatesTokenCount": 1, + "totalTokenCount": 129, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 62 + } + ], + "thoughtsTokenCount": 66, + "serviceTier": "standard" + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "Hq4ZasLPAqTK_uMPr6b2qQw" + }, + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": " need more information to validate the tool schema bounds. Could you please provide the `index_name` and `tags`?" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 62, + "candidatesTokenCount": 26, + "totalTokenCount": 154, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 62 + } + ], + "thoughtsTokenCount": 66, + "serviceTier": "standard" + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "Hq4ZasLPAqTK_uMPr6b2qQw" + } +] \ No newline at end of file diff --git a/payloads/snapshots/googleToolSchemaNumericInt64Param/google/response.json b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/response.json new file mode 100644 index 00000000..9bdca6d6 --- /dev/null +++ b/payloads/snapshots/googleToolSchemaNumericInt64Param/google/response.json @@ -0,0 +1,32 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "I can do that. What is the `index_name` and `tags` (list of strings) you want to validate?", + "thoughtSignature": "Cu0BAQw51seMqrFmFnucSlGqnmQJOgD0sb+XT9VWZcJLZ3E8yb1dLaBSoXvI1JQGhBr8OxNdYx0AB639dy6/LaPvlh8AYFDrBj70SaX8AckSASuG/yvubgNZquVxt1Ujc7QKayINMrsEkbOKztYyRg1k3Bw7ByLfJpbVWCkVGclUtSSG9UxWe6oymeOohAfTB8wFet8EH3fP85c/uXgegWV9KqH3LmWqRKvfJ4wIJ20TEtVYSdvIJp2K5cp0jK7B0F64N6oH9mU8EN5uhfG4Y/wOtPsyeTVh8NqJsHMyPriiBijUoYnxbxC6wmbghGe3" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 62, + "candidatesTokenCount": 27, + "totalTokenCount": 139, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 62 + } + ], + "thoughtsTokenCount": 50, + "serviceTier": "standard" + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "Hq4ZaonDAaHO_uMPzevviQc" +} \ No newline at end of file diff --git a/payloads/transforms/google_to_anthropic/googleToolSchemaNumericInt64Param.json b/payloads/transforms/google_to_anthropic/googleToolSchemaNumericInt64Param.json new file mode 100644 index 00000000..9fc80cb2 --- /dev/null +++ b/payloads/transforms/google_to_anthropic/googleToolSchemaNumericInt64Param.json @@ -0,0 +1,86 @@ +{ + "model": "claude-sonnet-4-5-20250929", + "id": "msg_01HyqMJ9csHXdUiikvBW6xqv", + "type": "message", + "role": "assistant", + "content": [ + { + "type": "text", + "text": "I'll help you validate the bounds of the tool schema by testing various scenarios. Let me make several calls to test different boundary conditions:" + }, + { + "type": "tool_use", + "id": "toolu_01PMw5ePG7oTsYwA1GRVmrY6", + "name": "validate_bounds", + "input": { + "index_name": "a", + "tags": [ + "tag1" + ] + }, + "caller": { + "type": "direct" + } + }, + { + "type": "tool_use", + "id": "toolu_012eTSUrPPrFSCmsY8mpptbb", + "name": "validate_bounds", + "input": { + "index_name": "this_is_exactly_128_characters_long_string_padding_with_more_text_to_reach_the_limit_and_ensure_we_hit_maximum_boundary_check_X", + "tags": [ + "tag1", + "tag2", + "tag3" + ] + }, + "caller": { + "type": "direct" + } + }, + { + "type": "tool_use", + "id": "toolu_015YDkvKcTjt4atdyf8B3VWY", + "name": "validate_bounds", + "input": { + "index_name": "valid_index_name", + "tags": [ + "minimum" + ] + }, + "caller": { + "type": "direct" + } + }, + { + "type": "tool_use", + "id": "toolu_01FTQMSoXB3Vqqmtz3AizfoV", + "name": "validate_bounds", + "input": { + "index_name": "another_valid_index", + "tags": [ + "tag1", + "tag2" + ] + }, + "caller": { + "type": "direct" + } + } + ], + "stop_reason": "tool_use", + "stop_sequence": null, + "stop_details": null, + "usage": { + "input_tokens": 619, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": { + "ephemeral_5m_input_tokens": 0, + "ephemeral_1h_input_tokens": 0 + }, + "output_tokens": 340, + "service_tier": "standard", + "inference_geo": "not_available" + } +} \ No newline at end of file diff --git a/payloads/transforms/google_to_chat-completions/googleToolSchemaNumericInt64Param.json b/payloads/transforms/google_to_chat-completions/googleToolSchemaNumericInt64Param.json new file mode 100644 index 00000000..83d61864 --- /dev/null +++ b/payloads/transforms/google_to_chat-completions/googleToolSchemaNumericInt64Param.json @@ -0,0 +1,45 @@ +{ + "id": "chatcmpl-DktG4XxU3g0Xm26Lsv8rehNP9pIwO", + "object": "chat.completion", + "created": 1780067872, + "model": "gpt-5-nano-2025-08-07", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_f7tYZBLU4YRktUuvFPodBpLP", + "type": "function", + "function": { + "name": "validate_bounds", + "arguments": "{\"index_name\":\"example\",\"tags\":[\"tag1\",\"tag2\"]}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 162, + "completion_tokens": 865, + "total_tokens": 1027, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 832, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": null +} \ No newline at end of file diff --git a/payloads/transforms/google_to_responses/googleToolSchemaNumericInt64Param.json b/payloads/transforms/google_to_responses/googleToolSchemaNumericInt64Param.json new file mode 100644 index 00000000..8e187e01 --- /dev/null +++ b/payloads/transforms/google_to_responses/googleToolSchemaNumericInt64Param.json @@ -0,0 +1,109 @@ +{ + "id": "resp_091fe5272963963f006a19ae2080f48193887ab188885883b5", + "object": "response", + "created_at": 1780067872, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1780067880, + "error": null, + "frequency_penalty": 0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-5-nano-2025-08-07", + "moderation": null, + "output": [ + { + "id": "rs_091fe5272963963f006a19ae20d6e481938c6bd86545fdea52", + "type": "reasoning", + "summary": [] + }, + { + "id": "msg_091fe5272963963f006a19ae2708d481938c20610ded0a3bd8", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "Here are the bounds the tool schema enforces:\n\n- index_name: string with length between 1 and 128 (inclusive)\n- tags: array of strings with length between 1 and 3 (inclusive); each item is a string\n\nIf you’d like, I can validate specific inputs. Examples:\n\n- Valid:\n - index_name = \"users\"\n - tags = [\"admin\"]\n- Invalid:\n - index_name = \"\" (violates minLength 1)\n - index_name = \"a\" repeated 129 times (violates maxLength 128)\n - tags = [] (violates minItems 1)\n - tags = [\"t1\", \"t2\", \"t3\", \"t4\"] (violates maxItems 3)\n\nPlease provide values for index_name and tags (an array of strings), and I’ll run the validation. Or tell me to run a few test cases for you." + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": "current_turn", + "effort": "medium", + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": true, + "temperature": 1, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tools": [ + { + "type": "function", + "description": "Validate bounded string and array inputs.", + "name": "validate_bounds", + "parameters": { + "properties": { + "index_name": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "maxItems": 3, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "index_name", + "tags" + ], + "type": "object", + "additionalProperties": false + }, + "strict": true + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 1198, + "output_tokens_details": { + "reasoning_tokens": 960 + }, + "total_tokens": 1278 + }, + "user": null, + "metadata": {}, + "output_text": "Here are the bounds the tool schema enforces:\n\n- index_name: string with length between 1 and 128 (inclusive)\n- tags: array of strings with length between 1 and 3 (inclusive); each item is a string\n\nIf you’d like, I can validate specific inputs. Examples:\n\n- Valid:\n - index_name = \"users\"\n - tags = [\"admin\"]\n- Invalid:\n - index_name = \"\" (violates minLength 1)\n - index_name = \"a\" repeated 129 times (violates maxLength 128)\n - tags = [] (violates minItems 1)\n - tags = [\"t1\", \"t2\", \"t3\", \"t4\"] (violates maxItems 3)\n\nPlease provide values for index_name and tags (an array of strings), and I’ll run the validation. Or tell me to run a few test cases for you." +} \ No newline at end of file