diff --git a/rstructor_derive/src/generators/enum_schema.rs b/rstructor_derive/src/generators/enum_schema.rs index 89b5470..815f03f 100644 --- a/rstructor_derive/src/generators/enum_schema.rs +++ b/rstructor_derive/src/generators/enum_schema.rs @@ -400,7 +400,7 @@ fn generate_externally_tagged_enum_schema( ]; let mut schema_obj = ::serde_json::json!({ - "oneOf": variant_schemas, + "anyOf": variant_schemas, "title": stringify!(#name) }); @@ -754,7 +754,7 @@ fn generate_internally_tagged_enum_schema( ]; let mut schema_obj = ::serde_json::json!({ - "oneOf": variant_schemas, + "anyOf": variant_schemas, "title": stringify!(#name) }); @@ -1045,7 +1045,7 @@ fn generate_adjacently_tagged_enum_schema( ]; let mut schema_obj = ::serde_json::json!({ - "oneOf": variant_schemas, + "anyOf": variant_schemas, "title": stringify!(#name) }); @@ -1202,7 +1202,7 @@ fn generate_untagged_enum_schema( ]; let mut schema_obj = ::serde_json::json!({ - "oneOf": variant_schemas, + "anyOf": variant_schemas, "title": stringify!(#name) }); diff --git a/rstructor_derive/tests/enum_with_data_tests.rs b/rstructor_derive/tests/enum_with_data_tests.rs index 7d4f1d0..1102355 100644 --- a/rstructor_derive/tests/enum_with_data_tests.rs +++ b/rstructor_derive/tests/enum_with_data_tests.rs @@ -23,13 +23,13 @@ fn test_enum_with_data_schema() { let schema_obj = UserStatus::schema(); let schema = schema_obj.to_json(); - // Check that we're using oneOf for complex enums + // Check that we're using anyOf for complex enums assert!( - schema.get("oneOf").is_some(), - "Schema should use oneOf for enums with associated data" + schema.get("anyOf").is_some(), + "Schema should use anyOf for enums with associated data" ); - if let Some(Value::Array(variants)) = schema.get("oneOf") { + if let Some(Value::Array(variants)) = schema.get("anyOf") { // Should have 4 variants assert_eq!(variants.len(), 4, "Should have 4 variants"); } diff --git a/src/backend/gemini.rs b/src/backend/gemini.rs index 655d323..fd8bf5d 100644 --- a/src/backend/gemini.rs +++ b/src/backend/gemini.rs @@ -263,7 +263,7 @@ impl GeminiClient { /// # Ok(()) /// # } /// ``` - #[instrument(name = "gemini_client_new", skip(api_key), fields(model = ?Model::Gemini25Flash))] + #[instrument(name = "gemini_client_new", skip(api_key), fields(model = ?Model::Gemini3FlashPreview))] pub fn new(api_key: impl Into) -> Result { let api_key = api_key.into(); if api_key.is_empty() { diff --git a/src/backend/utils.rs b/src/backend/utils.rs index 12f6fe6..96cbab9 100644 --- a/src/backend/utils.rs +++ b/src/backend/utils.rs @@ -175,28 +175,12 @@ pub struct AdjacentlyTaggedEnumInfo { /// Extract adjacently tagged enum info from a schema (before Gemini transformation) /// Searches recursively through the schema tree pub fn extract_adjacently_tagged_info(schema: &Value) -> Option { - // First check if this level has oneOf - if let Some(one_of) = schema.get("oneOf").and_then(|v| v.as_array()) { - let mut tag_key = None; - let mut content_key = None; - let mut tag_values = Vec::new(); - - for variant in one_of { - if let Some((t, c, v)) = detect_adjacently_tagged_variant(variant) { - if tag_key.is_none() { - tag_key = Some(t); - content_key = Some(c); - } - tag_values.push(v); - } - } - - if let (Some(tag), Some(content)) = (tag_key, content_key) { - return Some(AdjacentlyTaggedEnumInfo { - tag_key: tag, - content_key: content, - tag_values, - }); + // First check if this level has enum disjunction variants + for key in ["oneOf", "anyOf"] { + if let Some(variants) = schema.get(key).and_then(|v| v.as_array()) + && let Some(info) = extract_adjacently_tagged_info_from_variants(variants) + { + return Some(info); } } @@ -217,7 +201,7 @@ pub fn extract_adjacently_tagged_info(schema: &Value) -> Option Option Option { + let mut tag_key = None; + let mut content_key = None; + let mut tag_values = Vec::new(); + + for variant in variants { + if let Some((t, c, v)) = detect_adjacently_tagged_variant(variant) { + if tag_key.is_none() { + tag_key = Some(t); + content_key = Some(c); + } + tag_values.push(v); + } + } + + if let (Some(tag), Some(content)) = (tag_key, content_key) { + Some(AdjacentlyTaggedEnumInfo { + tag_key: tag, + content_key: content, + tag_values, + }) + } else { + None + } +} + /// Transform internally tagged JSON back to adjacently tagged format pub fn transform_internally_to_adjacently_tagged( json: &mut Value, @@ -551,6 +563,55 @@ fn transform_adjacently_tagged_to_internally_tagged( Value::Object(obj) } +fn normalize_adjacently_tagged_variants(variants: &mut Vec) { + // First, check if this looks like an adjacently tagged enum. + // All variants should have the same tag/content keys. + let mut adjacently_tagged_info: Option<(String, String)> = None; + let mut all_adjacently_tagged = true; + + for variant in variants.iter() { + if let Some((tag_key, content_key, _tag_value)) = detect_adjacently_tagged_variant(variant) + { + if let Some((ref existing_tag, ref existing_content)) = adjacently_tagged_info { + if tag_key != *existing_tag || content_key != *existing_content { + all_adjacently_tagged = false; + break; + } + } else { + adjacently_tagged_info = Some((tag_key, content_key)); + } + } else { + // Unit variant (only tag, no content) is still okay. + if let Some(variant_obj) = variant.as_object() + && let Some(props) = variant_obj.get("properties").and_then(|p| p.as_object()) + && props.len() == 1 + && variant_obj + .get("required") + .and_then(|r| r.as_array()) + .map(|a| a.len()) + == Some(1) + { + continue; + } + all_adjacently_tagged = false; + break; + } + } + + if all_adjacently_tagged && adjacently_tagged_info.is_some() { + *variants = variants + .iter() + .map(|variant| { + if let Some((t, c, v)) = detect_adjacently_tagged_variant(variant) { + transform_adjacently_tagged_to_internally_tagged(variant, &t, &c, &v) + } else { + variant.clone() + } + }) + .collect(); + } +} + /// Internal function that strips unsupported keywords after refs are resolved. fn strip_gemini_unsupported_keywords_recursive(schema: &mut Value) { if let Some(obj) = schema.as_object_mut() { @@ -732,78 +793,16 @@ fn strip_gemini_unsupported_keywords_recursive(schema: &mut Value) { } } - // Process 'anyOf' array - if let Some(any_of) = obj.get_mut("anyOf") - && let Some(arr) = any_of.as_array_mut() - { - for item in arr.iter_mut() { - strip_gemini_unsupported_keywords_recursive(item); - } - } - - // Process 'oneOf' array - if let Some(one_of) = obj.get_mut("oneOf") - && let Some(arr) = one_of.as_array_mut() - { - // First, check if this looks like an adjacently tagged enum - // All variants should have the same tag/content keys - let mut adjacently_tagged_info: Option<(String, String)> = None; - let mut all_adjacently_tagged = true; - - for item in arr.iter() { - if let Some((tag_key, content_key, _tag_value)) = - detect_adjacently_tagged_variant(item) - { - if let Some((ref existing_tag, ref existing_content)) = adjacently_tagged_info { - // Check if keys match - if tag_key != *existing_tag || content_key != *existing_content { - all_adjacently_tagged = false; - break; - } - } else { - adjacently_tagged_info = Some((tag_key, content_key)); - } - } else { - // Unit variant (only tag, no content) is still okay - // Check if it has just one required field with enum - if let Some(variant_obj) = item.as_object() - && let Some(props) = - variant_obj.get("properties").and_then(|p| p.as_object()) - && props.len() == 1 - && variant_obj - .get("required") - .and_then(|r| r.as_array()) - .map(|a| a.len()) - == Some(1) - { - // This is a unit variant, keep checking - continue; - } - all_adjacently_tagged = false; - break; + // Process enum disjunction arrays and normalize adjacently tagged variants. + for key in ["anyOf", "oneOf"] { + if let Some(disjunction) = obj.get_mut(key) + && let Some(variants) = disjunction.as_array_mut() + { + normalize_adjacently_tagged_variants(variants); + for variant in variants.iter_mut() { + strip_gemini_unsupported_keywords_recursive(variant); } } - - // If all variants are adjacently tagged, transform them - if all_adjacently_tagged && adjacently_tagged_info.is_some() { - // Transform each variant - *arr = arr - .iter() - .map(|item| { - if let Some((t, c, v)) = detect_adjacently_tagged_variant(item) { - transform_adjacently_tagged_to_internally_tagged(item, &t, &c, &v) - } else { - // Unit variant - leave as is - item.clone() - } - }) - .collect(); - } - - // Now recursively process all variants - for item in arr.iter_mut() { - strip_gemini_unsupported_keywords_recursive(item); - } } // Handle additionalProperties if it's a schema object (for maps) - recurse into it @@ -1902,6 +1901,103 @@ mod tests { ); } + #[test] + fn test_extract_adjacently_tagged_info_anyof() { + let schema = serde_json::json!({ + "anyOf": [ + { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["Success"] }, + "data": { + "type": "object", + "properties": { + "output": { "type": "string" } + }, + "required": ["output"] + } + }, + "required": ["status", "data"] + }, + { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["Failure"] }, + "data": { + "type": "object", + "properties": { + "reason": { "type": "string" } + }, + "required": ["reason"] + } + }, + "required": ["status", "data"] + } + ] + }); + + let info = extract_adjacently_tagged_info(&schema).expect("should detect anyOf enum info"); + assert_eq!(info.tag_key, "status"); + assert_eq!(info.content_key, "data"); + assert_eq!(info.tag_values.len(), 2); + assert!(info.tag_values.contains(&"Success".to_string())); + assert!(info.tag_values.contains(&"Failure".to_string())); + } + + #[test] + fn test_gemini_anyof_adjacently_tagged_variants_are_flattened() { + let mut schema = serde_json::json!({ + "anyOf": [ + { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["Success"] }, + "data": { + "type": "object", + "properties": { + "output": { "type": "string" } + }, + "required": ["output"] + } + }, + "required": ["status", "data"], + "description": "Success variant" + }, + { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["Failure"] }, + "data": { + "type": "object", + "properties": { + "reason": { "type": "string" } + }, + "required": ["reason"] + } + }, + "required": ["status", "data"], + "description": "Failure variant" + } + ] + }); + + strip_gemini_unsupported_keywords_recursive(&mut schema); + + let first_props = schema["anyOf"][0]["properties"] + .as_object() + .expect("properties should be object"); + assert!(first_props.contains_key("status")); + assert!(first_props.contains_key("output")); + assert!(!first_props.contains_key("data")); + + let first_required = schema["anyOf"][0]["required"] + .as_array() + .expect("required should be array"); + assert!(first_required.contains(&serde_json::json!("status"))); + assert!(first_required.contains(&serde_json::json!("output"))); + assert!(!first_required.contains(&serde_json::json!("data"))); + } + #[test] fn test_gemini_map_with_x_enum_keys() { let mut schema = serde_json::json!({ diff --git a/tests/complex_enum_integration_tests.rs b/tests/complex_enum_integration_tests.rs index 0956c89..7cd8755 100644 --- a/tests/complex_enum_integration_tests.rs +++ b/tests/complex_enum_integration_tests.rs @@ -1,7 +1,7 @@ // tests/complex_enum_integration_tests.rs #[cfg(test)] mod complex_enum_tests { - use rstructor::{GeminiClient, Instructor, LLMClient}; + use rstructor::{GeminiClient, GeminiModel, Instructor, LLMClient}; use serde::{Deserialize, Serialize}; use std::env; @@ -27,6 +27,7 @@ mod complex_enum_tests { let api_key = env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set"); let client = GeminiClient::new(api_key) .unwrap() + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0) .no_retries(); diff --git a/tests/gemini_multimodal_tests.rs b/tests/gemini_multimodal_tests.rs index 9c745e0..37b25a6 100644 --- a/tests/gemini_multimodal_tests.rs +++ b/tests/gemini_multimodal_tests.rs @@ -10,7 +10,7 @@ #[cfg(test)] mod gemini_multimodal_tests { #[cfg(feature = "gemini")] - use rstructor::GeminiClient; + use rstructor::{GeminiClient, GeminiModel}; use rstructor::{Instructor, LLMClient, MediaFile}; use serde::{Deserialize, Serialize}; @@ -43,6 +43,7 @@ mod gemini_multimodal_tests { let client = GeminiClient::from_env() .expect("GEMINI_API_KEY must be set for this test") + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0); let result: ImageDescription = client diff --git a/tests/hashmap_integration_tests.rs b/tests/hashmap_integration_tests.rs index cb473cc..10689d6 100644 --- a/tests/hashmap_integration_tests.rs +++ b/tests/hashmap_integration_tests.rs @@ -1,7 +1,7 @@ // tests/hashmap_integration_tests.rs #[cfg(test)] mod hashmap_tests { - use rstructor::{GeminiClient, Instructor, LLMClient}; + use rstructor::{GeminiClient, GeminiModel, Instructor, LLMClient}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::env; @@ -32,6 +32,7 @@ mod hashmap_tests { let api_key = env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set"); let client = GeminiClient::new(api_key) .unwrap() + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0) .no_retries(); @@ -63,6 +64,7 @@ mod hashmap_tests { let api_key = env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set"); let client = GeminiClient::new(api_key) .unwrap() + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0) .no_retries(); diff --git a/tests/llm_integration_tests.rs b/tests/llm_integration_tests.rs index 4050300..367b997 100644 --- a/tests/llm_integration_tests.rs +++ b/tests/llm_integration_tests.rs @@ -12,10 +12,10 @@ #[cfg(test)] mod llm_integration_tests { - #[cfg(feature = "gemini")] - use rstructor::GeminiClient; #[cfg(feature = "anthropic")] use rstructor::{AnthropicClient, AnthropicModel}; + #[cfg(feature = "gemini")] + use rstructor::{GeminiClient, GeminiModel}; #[cfg(feature = "grok")] use rstructor::{GrokClient, GrokModel}; use rstructor::{Instructor, LLMClient, SchemaType}; @@ -154,9 +154,9 @@ mod llm_integration_tests { #[tokio::test] async fn test_gemini_materialize() { // Read from GEMINI_API_KEY env var - // Uses default model (Gemini 3 Flash Preview with Low thinking) let client = GeminiClient::from_env() .expect("GEMINI_API_KEY must be set for this test") + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0); let prompt = "Provide information about the movie Inception"; diff --git a/tests/nested_enum_tests.rs b/tests/nested_enum_tests.rs index 28d5f12..40a490c 100644 --- a/tests/nested_enum_tests.rs +++ b/tests/nested_enum_tests.rs @@ -96,9 +96,9 @@ mod nested_enum_tests { let schema = TaskState::schema(); let schema_json = schema.to_json(); - // Verify it's a oneOf schema (complex enum) - assert!(schema_json["oneOf"].is_array()); - let variants = schema_json["oneOf"].as_array().unwrap(); + // Verify it's an anyOf schema (complex enum) + assert!(schema_json["anyOf"].is_array()); + let variants = schema_json["anyOf"].as_array().unwrap(); assert_eq!(variants.len(), 3); // Verify Pending variant has priority field @@ -160,8 +160,8 @@ mod nested_enum_tests { // Verify items are objects (since TaskState is a complex enum) let items = &tasks_prop["items"]; assert!(items.is_object()); - // Complex enums should have oneOf schema - assert!(items.get("oneOf").is_some() || items.get("type").is_some()); + // Complex enums should have anyOf schema + assert!(items.get("anyOf").is_some() || items.get("type").is_some()); } #[test] @@ -248,9 +248,9 @@ mod nested_enum_tests { let schema = DeepNestedEnum::schema(); let schema_json = schema.to_json(); - // Verify it's a oneOf schema - assert!(schema_json["oneOf"].is_array()); - let variants = schema_json["oneOf"].as_array().unwrap(); + // Verify it's an anyOf schema + assert!(schema_json["anyOf"].is_array()); + let variants = schema_json["anyOf"].as_array().unwrap(); assert_eq!(variants.len(), 1); // Verify Level1 variant has both status and priority fields diff --git a/tests/openai_enum_anyof_test.rs b/tests/openai_enum_anyof_test.rs new file mode 100644 index 0000000..66a647e --- /dev/null +++ b/tests/openai_enum_anyof_test.rs @@ -0,0 +1,89 @@ +//! Regression test for OpenAI `oneOf` rejection bug. +//! +//! OpenAI's structured outputs API rejects `oneOf` in JSON schemas — it only +//! supports `anyOf`. The `rstructor` derive macro must emit `anyOf` for all +//! complex enum schemas so that OpenAI (and other providers) accept them. +//! +//! Run with: +//! ```bash +//! cargo test --test openai_enum_anyof_test +//! ``` + +#[cfg(test)] +mod openai_enum_anyof_tests { + use rstructor::Instructor; + use serde::{Deserialize, Serialize}; + + // ── Complex enum: externally tagged (default serde representation) ── + + #[derive(Instructor, Serialize, Deserialize, Debug, PartialEq)] + #[llm(description = "A type of animal")] + enum Animal { + #[llm(description = "A dog")] + Dog { + #[llm(description = "The breed of the dog")] + breed: String, + }, + #[llm(description = "A cat")] + Cat { + #[llm(description = "Whether the cat is indoor-only")] + indoor: bool, + }, + #[llm(description = "A mouse")] + Mouse { + #[llm(description = "The colour of the mouse")] + colour: String, + }, + } + + #[derive(Instructor, Serialize, Deserialize, Debug)] + #[llm(description = "Identification of a fictional character")] + struct CharacterId { + #[llm(description = "Name of the character")] + name: String, + + #[llm(description = "What kind of animal the character is")] + animal: Animal, + } + + // ==================================================================== + // Live OpenAI integration test — requires OPENAI_API_KEY + // ==================================================================== + + /// Send a struct containing a complex enum to OpenAI and confirm the API + /// accepts the schema and returns valid data. + /// + /// Before the fix this would fail with: + /// > Invalid schema for response_format '...': 'oneOf' is not permitted. + #[cfg(feature = "openai")] + #[tokio::test] + async fn test_openai_accepts_complex_enum_schema() { + use rstructor::{LLMClient, OpenAIClient, OpenAIModel}; + use std::env; + + let api_key = env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY must be set"); + + let client = OpenAIClient::new(api_key) + .expect("Failed to create OpenAI client") + .model(OpenAIModel::Gpt4OMini) + .temperature(0.0); + + let result = client + .materialize::("What kind of animal is Scooby-Doo?") + .await; + + assert!( + result.is_ok(), + "OpenAI should accept the schema with `anyOf`. Error: {:?}", + result.err() + ); + + let character = result.unwrap(); + assert_eq!(character.name, "Scooby-Doo"); + assert!( + matches!(character.animal, Animal::Dog { .. }), + "Scooby-Doo should be identified as a Dog, got: {:?}", + character.animal + ); + } +} diff --git a/tests/recursive_structures_tests.rs b/tests/recursive_structures_tests.rs index 78841da..f54376a 100644 --- a/tests/recursive_structures_tests.rs +++ b/tests/recursive_structures_tests.rs @@ -1,7 +1,7 @@ // tests/recursive_structures_tests.rs #[cfg(test)] mod recursive_tests { - use rstructor::{GeminiClient, Instructor, LLMClient}; + use rstructor::{GeminiClient, GeminiModel, Instructor, LLMClient}; use serde::{Deserialize, Serialize}; use std::env; @@ -23,6 +23,7 @@ mod recursive_tests { let api_key = env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set"); let client = GeminiClient::new(api_key) .unwrap() + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0) .no_retries(); diff --git a/tests/serde_rename_tests.rs b/tests/serde_rename_tests.rs index 97af9dc..e94858c 100644 --- a/tests/serde_rename_tests.rs +++ b/tests/serde_rename_tests.rs @@ -301,8 +301,8 @@ mod serde_rename_tests { let schema = PaymentMethod::schema(); let schema_json = schema.to_json(); - // Complex enums use oneOf - let one_of = schema_json["oneOf"].as_array().unwrap(); + // Complex enums use anyOf + let one_of = schema_json["anyOf"].as_array().unwrap(); assert_eq!(one_of.len(), 3); // Check that variant names are properly renamed diff --git a/tests/timeout_tests.rs b/tests/timeout_tests.rs index 600d78f..8042dfd 100644 --- a/tests/timeout_tests.rs +++ b/tests/timeout_tests.rs @@ -5,10 +5,10 @@ #[cfg(test)] mod timeout_tests { - #[cfg(feature = "gemini")] - use rstructor::GeminiClient; #[cfg(feature = "anthropic")] use rstructor::{AnthropicClient, AnthropicModel}; + #[cfg(feature = "gemini")] + use rstructor::{GeminiClient, GeminiModel}; #[cfg(feature = "grok")] use rstructor::{GrokClient, GrokModel}; use rstructor::{Instructor, LLMClient, RStructorError}; @@ -200,9 +200,9 @@ mod timeout_tests { #[tokio::test] async fn test_gemini_timeout_configuration() { // Test that timeout can be set via builder pattern - // Uses default model (Gemini 3 Flash Preview with Low thinking) let client = GeminiClient::from_env() .expect("GEMINI_API_KEY must be set for this test") + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0) .timeout(Duration::from_millis(1)); // 1ms timeout - should timeout @@ -224,9 +224,9 @@ mod timeout_tests { #[tokio::test] async fn test_gemini_timeout_chaining() { // Test that timeout can be chained with other configuration methods - // Uses default model (Gemini 3 Flash Preview with Low thinking) let _client = GeminiClient::from_env() .expect("GEMINI_API_KEY must be set for this test") + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.5) .max_tokens(100) .timeout(Duration::from_secs(2)); // 2 second timeout for unit tests @@ -239,9 +239,10 @@ mod timeout_tests { #[cfg(feature = "gemini")] #[tokio::test] async fn test_gemini_no_timeout_default() { - // Test that default client has no timeout - // Test with empty string to use GEMINI_API_KEY env var - let _client = GeminiClient::from_env().expect("GEMINI_API_KEY must be set for this test"); + // Test that client can be created with explicit Gemini 3 Flash Preview and no timeout + let _client = GeminiClient::from_env() + .expect("GEMINI_API_KEY must be set for this test") + .model(GeminiModel::Gemini3FlashPreview); // Verify that client was created successfully without timeout // (We can't access config directly, but default behavior means no timeout) diff --git a/tests/weird_types_tests.rs b/tests/weird_types_tests.rs index 6b0314c..791689b 100644 --- a/tests/weird_types_tests.rs +++ b/tests/weird_types_tests.rs @@ -1,7 +1,7 @@ // tests/weird_types_tests.rs #[cfg(test)] mod weird_types_tests { - use rstructor::{GeminiClient, Instructor, LLMClient}; + use rstructor::{GeminiClient, GeminiModel, Instructor, LLMClient}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::env; @@ -34,6 +34,7 @@ mod weird_types_tests { let api_key = env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set"); let client = GeminiClient::new(api_key) .unwrap() + .model(GeminiModel::Gemini3FlashPreview) .temperature(0.0) .no_retries();