diff --git a/litellm-rust/crates/core/src/audio_transcription/tests.rs b/litellm-rust/crates/core/src/audio_transcription/tests.rs index 263d63337b0..6693ba2a3cf 100644 --- a/litellm-rust/crates/core/src/audio_transcription/tests.rs +++ b/litellm-rust/crates/core/src/audio_transcription/tests.rs @@ -40,7 +40,7 @@ async fn bedrock_request_is_signed_and_contains_audio() { api_base: Some(&api_base), custom_llm_provider: Some("bedrock"), extra_headers: None, - optional_params, + optional_params: optional_params.into(), timeout: None, }) .await diff --git a/litellm-rust/crates/core/src/audio_transcription/transformation.rs b/litellm-rust/crates/core/src/audio_transcription/transformation.rs index 16a28fbcac0..b15064880fd 100644 --- a/litellm-rust/crates/core/src/audio_transcription/transformation.rs +++ b/litellm-rust/crates/core/src/audio_transcription/transformation.rs @@ -1,7 +1,8 @@ use crate::Error; -use serde_json::{Map, Value}; +use serde_json::Value; use super::types::{AudioTranscriptionRequestData, AudioTranscriptionResponseData}; +use crate::params::OpaqueParams; #[derive(Clone, Debug, PartialEq, Eq)] pub enum AudioTranscriptionAuth { @@ -15,22 +16,16 @@ pub enum AudioTranscriptionAuth { pub trait AudioTranscriptionProviderConfig: Sync { fn supported_transcription_params(&self) -> &'static [&'static str]; - fn map_transcription_params(&self, params: &Map) -> Map { - params - .iter() - .filter(|(key, _)| { - self.supported_transcription_params() - .contains(&key.as_str()) - }) - .map(|(key, value)| (key.clone(), value.clone())) - .collect() + #[tracing::instrument(target = "litellm::function_trace", level = "trace", skip_all)] + fn map_transcription_params(&self, params: &OpaqueParams) -> OpaqueParams { + params.retain_supported(self.supported_transcription_params()) } fn transform_transcription_request( &self, model: &str, audio: Value, - optional_params: Map, + optional_params: OpaqueParams, ) -> Result; fn transform_transcription_response( @@ -43,14 +38,14 @@ pub trait AudioTranscriptionProviderConfig: Sync { &self, api_base: Option<&str>, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result; fn auth_strategy( &self, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result; } diff --git a/litellm-rust/crates/core/src/audio_transcription/types.rs b/litellm-rust/crates/core/src/audio_transcription/types.rs index 559d7837027..1723537277c 100644 --- a/litellm-rust/crates/core/src/audio_transcription/types.rs +++ b/litellm-rust/crates/core/src/audio_transcription/types.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use super::transformation::{AudioTranscriptionAuth, AudioTranscriptionProviderConfig}; +use crate::params::OpaqueParams; pub struct AudioTranscriptionRequest<'a> { pub model: &'a str, @@ -12,7 +13,7 @@ pub struct AudioTranscriptionRequest<'a> { pub api_base: Option<&'a str>, pub custom_llm_provider: Option<&'a str>, pub extra_headers: Option>, - pub optional_params: Map, + pub optional_params: OpaqueParams, pub timeout: Option, } @@ -26,7 +27,7 @@ pub struct ProviderAudioTranscriptionRequest { pub(super) upstream_headers: Vec<(String, String)>, pub(super) auth: AudioTranscriptionAuth, #[cfg(feature = "bedrock-auth")] - pub(super) optional_params: Map, + pub(super) optional_params: OpaqueParams, pub(super) timeout: Option, } diff --git a/litellm-rust/crates/core/src/chat_completions/mod.rs b/litellm-rust/crates/core/src/chat_completions/mod.rs index 8117663a23b..3c20c2cd287 100644 --- a/litellm-rust/crates/core/src/chat_completions/mod.rs +++ b/litellm-rust/crates/core/src/chat_completions/mod.rs @@ -16,8 +16,9 @@ pub mod response_utils; pub mod transformation; pub mod types; -use serde_json::{Map, Value}; +use serde_json::Value; +use crate::params::OpaqueParams; use handler::execute_chat_completions_provider_call; use prepare::{parse_messages, resolve_provider_config, resolve_request}; use types::{ChatCompletionsRequest, ChatCompletionsResponse}; @@ -38,7 +39,7 @@ pub fn chat_completions_decline_reason( model: &str, custom_llm_provider: Option<&str>, messages: Value, - optional_params: &Map, + optional_params: &OpaqueParams, ) -> Option<&'static str> { let Ok((_, config)) = resolve_provider_config(model, custom_llm_provider) else { return Some("provider is not on the rust chat completions path"); diff --git a/litellm-rust/crates/core/src/chat_completions/tests.rs b/litellm-rust/crates/core/src/chat_completions/tests.rs index f8594dee447..5824313b4ee 100644 --- a/litellm-rust/crates/core/src/chat_completions/tests.rs +++ b/litellm-rust/crates/core/src/chat_completions/tests.rs @@ -22,7 +22,7 @@ fn request<'a>( model, messages, optional_params: match optional_params { - Value::Object(map) => map, + Value::Object(map) => map.into(), other => panic!("params must be an object, got {other}"), }, api_key: Some("sk-test"), @@ -489,7 +489,7 @@ fn decline_reason( params: Value, ) -> Option<&'static str> { let params = match params { - Value::Object(map) => map, + Value::Object(map) => map.into(), other => panic!("params must be an object, got {other}"), }; super::chat_completions_decline_reason(model, provider, messages, ¶ms) @@ -663,7 +663,7 @@ mod round_trip { model: "anthropic/claude-sonnet-4-5", messages, optional_params: match params { - Value::Object(map) => map, + Value::Object(map) => map.into(), other => panic!("params must be an object, got {other}"), }, api_key: Some("sk-test"), diff --git a/litellm-rust/crates/core/src/chat_completions/transformation.rs b/litellm-rust/crates/core/src/chat_completions/transformation.rs index d7b9704c46c..eb1a54886d4 100644 --- a/litellm-rust/crates/core/src/chat_completions/transformation.rs +++ b/litellm-rust/crates/core/src/chat_completions/transformation.rs @@ -1,10 +1,11 @@ use crate::Error; -use serde_json::{Map, Value}; +use serde_json::Value; use super::types::{ ChatCompletionsResponse, ChatMessage, ChatMessageContent, ProviderChatRequestData, ProviderChatResponseData, }; +use crate::params::OpaqueParams; /// How the upstream call is authenticated. API-key strategies are resolved in /// `prepare`; SigV4 needs the serialized body, so the handler signs it. @@ -36,7 +37,7 @@ pub trait ChatCompletionsProviderConfig: Sync { &self, api_base: Option<&str>, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result; @@ -44,7 +45,7 @@ pub trait ChatCompletionsProviderConfig: Sync { &self, api_key: Option<&str>, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result; @@ -74,7 +75,7 @@ pub trait ChatCompletionsProviderConfig: Sync { fn unsupported_reason( &self, messages: &[ChatMessage], - optional_params: &Map, + optional_params: &OpaqueParams, ) -> Option { unsupported_param( self.supported_openai_params(), @@ -88,7 +89,7 @@ pub trait ChatCompletionsProviderConfig: Sync { &self, model: &str, messages: Vec, - optional_params: Map, + optional_params: OpaqueParams, ) -> Result; fn transform_response( @@ -101,7 +102,7 @@ pub trait ChatCompletionsProviderConfig: Sync { pub fn unsupported_param( supported: &'static [(&'static str, &'static str)], config: &'static [&'static str], - optional_params: &Map, + optional_params: &OpaqueParams, ) -> Option { if optional_params .get(STREAM_PARAM) diff --git a/litellm-rust/crates/core/src/chat_completions/types.rs b/litellm-rust/crates/core/src/chat_completions/types.rs index 3238d09b6b5..93afcc9e524 100644 --- a/litellm-rust/crates/core/src/chat_completions/types.rs +++ b/litellm-rust/crates/core/src/chat_completions/types.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use super::transformation::{ChatCompletionsAuth, ChatCompletionsProviderConfig}; +use crate::params::OpaqueParams; /// A `/chat/completions` call as it crosses into the core. /// @@ -14,7 +15,7 @@ use super::transformation::{ChatCompletionsAuth, ChatCompletionsProviderConfig}; pub struct ChatCompletionsRequest<'a> { pub model: &'a str, pub messages: Value, - pub optional_params: Map, + pub optional_params: OpaqueParams, pub api_key: Option<&'a str>, pub api_base: Option<&'a str>, pub custom_llm_provider: Option<&'a str>, @@ -26,7 +27,7 @@ pub(super) struct ResolvedChatCompletionsRequest<'a> { pub(super) model: String, pub(super) config: &'static dyn ChatCompletionsProviderConfig, pub(super) messages: Vec, - pub(super) optional_params: Map, + pub(super) optional_params: OpaqueParams, pub(super) api_key: Option<&'a str>, pub(super) api_base: Option<&'a str>, pub(super) extra_headers: Option>, @@ -41,7 +42,7 @@ pub(super) struct ProviderChatCompletionsRequest { pub(super) upstream_headers: Vec<(String, String)>, pub(super) auth: ChatCompletionsAuth, #[cfg_attr(not(feature = "bedrock-auth"), allow(dead_code))] - pub(super) optional_params: Map, + pub(super) optional_params: OpaqueParams, pub(super) timeout: Option, } diff --git a/litellm-rust/crates/core/src/lib.rs b/litellm-rust/crates/core/src/lib.rs index 5f4bc84b4a8..89c80b4551a 100644 --- a/litellm-rust/crates/core/src/lib.rs +++ b/litellm-rust/crates/core/src/lib.rs @@ -10,6 +10,7 @@ pub(crate) mod llms; mod media; pub mod messages; pub mod ocr; +pub mod params; pub mod providers; pub mod responses; mod url_utils; diff --git a/litellm-rust/crates/core/src/llms/azure_ai/ocr/cohere_parse_transformation.rs b/litellm-rust/crates/core/src/llms/azure_ai/ocr/cohere_parse_transformation.rs index 2a3b10b64d4..7481ce18e00 100644 --- a/litellm-rust/crates/core/src/llms/azure_ai/ocr/cohere_parse_transformation.rs +++ b/litellm-rust/crates/core/src/llms/azure_ai/ocr/cohere_parse_transformation.rs @@ -18,13 +18,17 @@ pub(crate) struct AzureAICohereParseConfig; impl BaseOcrConfig for AzureAICohereParseConfig { type ProviderResponse = CohereResponse; + fn get_supported_ocr_params(&self, model: &str) -> &'static [&'static str] { + CohereParseConfig.get_supported_ocr_params(model) + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, client: &OcrClient, ) -> Result { let params = crate::ocr::wire::decode_request_value::( - serde_json::Value::Object(request.optional_params.clone()), + serde_json::Value::Object(request.optional_params.clone().into()), "optional_params", )?; let config = AzureAuthInputs { diff --git a/litellm-rust/crates/core/src/llms/azure_ai/ocr/document_intelligence/transformation.rs b/litellm-rust/crates/core/src/llms/azure_ai/ocr/document_intelligence/transformation.rs index 1be4a3934a4..a987de3f842 100644 --- a/litellm-rust/crates/core/src/llms/azure_ai/ocr/document_intelligence/transformation.rs +++ b/litellm-rust/crates/core/src/llms/azure_ai/ocr/document_intelligence/transformation.rs @@ -629,6 +629,10 @@ pub(crate) struct AzureDocumentIntelligenceOCRConfig; impl BaseOcrConfig for AzureDocumentIntelligenceOCRConfig { type ProviderResponse = AzureDocumentIntelligenceOperation; + fn get_supported_ocr_params(&self, _model: &str) -> &'static [&'static str] { + &["pages", "features"] + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, @@ -686,7 +690,8 @@ impl BaseOcrConfig for AzureDocumentIntelligenceOCRConfig { fn map_ocr_params( request: &LiteLLMOcrRequest, ) -> Result { - let params = params::decode_input_params(request.optional_params.clone(), "optional_params")?; + let params = + params::decode_input_params(request.optional_params.clone().into(), "optional_params")?; let crate::ocr::prepare::ParsedProviderParams { known: params, extra_params: _extra_params, diff --git a/litellm-rust/crates/core/src/llms/azure_ai/ocr/transformation.rs b/litellm-rust/crates/core/src/llms/azure_ai/ocr/transformation.rs index f3f43de48e2..47481905946 100644 --- a/litellm-rust/crates/core/src/llms/azure_ai/ocr/transformation.rs +++ b/litellm-rust/crates/core/src/llms/azure_ai/ocr/transformation.rs @@ -2,14 +2,12 @@ use crate::Error; use crate::auth::{InputSource, Sourced}; use crate::constants::AZURE_AI_OCR_PATH; use crate::llms::base_llm::ocr::transformation::BaseOcrConfig; +use crate::llms::mistral::ocr::MistralOcrResponse; use crate::llms::mistral::ocr::transformation::MistralOCRConfig; -use crate::llms::mistral::ocr::{MistralOcrParams, MistralOcrResponse}; use crate::ocr::OcrClient; use crate::ocr::document::{inline_remote_document, validate_inline_document}; use crate::ocr::error::{OcrError, OcrRequestError, OcrResponseError}; -use crate::ocr::prepare::{ - _prepare_ocr_request, ParsedProviderParams, credential_env, transform_request_body, -}; +use crate::ocr::prepare::{credential_env, transform_request_body}; use crate::ocr::types::{LiteLLMOcrRequest, LiteLLMOcrResponse, OcrConnection}; use crate::providers::azure_ai::auth::AzureAuthInputs; use crate::url_utils::ApiUrl; @@ -23,15 +21,16 @@ pub(crate) struct AzureAIOCRConfig; impl BaseOcrConfig for AzureAIOCRConfig { type ProviderResponse = MistralOcrResponse; + fn get_supported_ocr_params(&self, model: &str) -> &'static [&'static str] { + MistralOCRConfig.get_supported_ocr_params(model) + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, client: &OcrClient, ) -> Result { - let ParsedProviderParams { - known: params, - extra_params: _extra_params, - } = _prepare_ocr_request::(request)?; + let params = self.map_ocr_params(&request.model, &request.optional_params); let config = AzureAuthInputs { azure_ad_token_provider: request.azure_ad_token_provider.clone(), ..AzureAuthInputs::from_sourced_optional_params( diff --git a/litellm-rust/crates/core/src/llms/base_llm/ocr/transformation.rs b/litellm-rust/crates/core/src/llms/base_llm/ocr/transformation.rs index 66c5c5133ae..3f69bb539c1 100644 --- a/litellm-rust/crates/core/src/llms/base_llm/ocr/transformation.rs +++ b/litellm-rust/crates/core/src/llms/base_llm/ocr/transformation.rs @@ -6,10 +6,17 @@ use crate::ocr::OcrClient; use crate::ocr::error::{OcrError, OcrResponseError}; use crate::ocr::types::{LiteLLMOcrRequest, LiteLLMOcrResponse, OcrResponseFormat}; use crate::ocr::wire::DecodedOcrResponse; +use crate::params::OpaqueParams; pub(crate) trait BaseOcrConfig: Send + Sync + Sized + 'static { type ProviderResponse: DeserializeOwned + Send; + fn get_supported_ocr_params(&self, model: &str) -> &'static [&'static str]; + + fn map_ocr_params(&self, model: &str, params: &OpaqueParams) -> OpaqueParams { + params.retain_supported(self.get_supported_ocr_params(model)) + } + fn prepare_request( &self, request: &LiteLLMOcrRequest, diff --git a/litellm-rust/crates/core/src/llms/cohere/ocr/transformation.rs b/litellm-rust/crates/core/src/llms/cohere/ocr/transformation.rs index 29a50de0d9d..1e822c216e2 100644 --- a/litellm-rust/crates/core/src/llms/cohere/ocr/transformation.rs +++ b/litellm-rust/crates/core/src/llms/cohere/ocr/transformation.rs @@ -290,13 +290,17 @@ impl CohereParseConfig { impl BaseOcrConfig for CohereParseConfig { type ProviderResponse = CohereResponse; + fn get_supported_ocr_params(&self, _model: &str) -> &'static [&'static str] { + &["output_format"] + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, client: &OcrClient, ) -> Result { let params = crate::ocr::wire::decode_request_value::( - serde_json::Value::Object(request.optional_params.clone()), + serde_json::Value::Object(request.optional_params.clone().into()), "optional_params", )?; let headers = validate_environment(&request.connection, &credential_env)?; diff --git a/litellm-rust/crates/core/src/llms/mistral/ocr/mod.rs b/litellm-rust/crates/core/src/llms/mistral/ocr/mod.rs index 546ac9eb50a..086afd5737f 100644 --- a/litellm-rust/crates/core/src/llms/mistral/ocr/mod.rs +++ b/litellm-rust/crates/core/src/llms/mistral/ocr/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod transformation; -pub(crate) use transformation::{MistralOcrParams, MistralOcrResponse}; +pub(crate) use transformation::MistralOcrResponse; diff --git a/litellm-rust/crates/core/src/llms/mistral/ocr/transformation.rs b/litellm-rust/crates/core/src/llms/mistral/ocr/transformation.rs index 233687727dc..37442885149 100644 --- a/litellm-rust/crates/core/src/llms/mistral/ocr/transformation.rs +++ b/litellm-rust/crates/core/src/llms/mistral/ocr/transformation.rs @@ -3,50 +3,14 @@ use serde_json::{Map, Value}; use crate::ocr::error::{OcrRequestError, OcrResponseError}; use crate::ocr::types::{LiteLLMOcrResponse, OcrDocument}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub(crate) enum MistralOcrPages { - Range(String), - Indices(Vec), -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub(crate) struct MistralOcrParams { - #[serde(skip_serializing_if = "Option::is_none")] - pub pages: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_image_base64: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub image_limit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub image_min_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub bbox_annotation_format: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub document_annotation_format: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub document_annotation_prompt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub extract_header: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub extract_footer: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub table_format: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub confidence_scores_granularity: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_blocks: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, -} +use crate::params::OpaqueParams; #[derive(Clone, Debug, Serialize, Deserialize)] pub(crate) struct MistralOcrRequest { pub model: String, pub document: OcrDocument, #[serde(flatten)] - pub params: MistralOcrParams, + pub params: OpaqueParams, } #[derive(Clone, Debug, Default, Deserialize)] @@ -63,7 +27,7 @@ pub(crate) struct MistralOcrResponse { pub(crate) fn transform_ocr_request( model: &str, document: OcrDocument, - params: &MistralOcrParams, + params: &OpaqueParams, ) -> Result { Ok(MistralOcrRequest { model: model.to_string(), @@ -94,7 +58,8 @@ mod mapping_tests { use serde_json::{Value, json}; fn mapped_params(value: Value) -> Value { - serde_json::to_value(serde_json::from_value::(value).unwrap()).unwrap() + let params = serde_json::from_value::(value).unwrap(); + serde_json::to_value(MistralOCRConfig.map_ocr_params("model", ¶ms)).unwrap() } fn document() -> OcrDocument { @@ -168,6 +133,16 @@ mod mapping_tests { assert!(mapped.get("unsupported_param").is_none()); } + #[rstest] + fn map_ocr_params_preserves_unvalidated_values_and_explicit_null() { + let mapped = mapped_params(json!({ + "pages":{"future":"shape"}, + "include_image_base64":null + })); + assert_eq!(mapped["pages"], json!({"future":"shape"})); + assert!(mapped.get("include_image_base64").unwrap().is_null()); + } + #[rstest] #[case("table_format", json!("html"))] #[case("confidence_scores_granularity", json!("word"))] @@ -205,8 +180,7 @@ mod mapping_tests { #[case("include_blocks", json!(true))] #[case("id", json!("req-123"))] fn request_mapping_matches_python(#[case] name: &str, #[case] value: Value) { - let params: MistralOcrParams = - serde_json::from_value(json!({name: value.clone()})).unwrap(); + let params: OpaqueParams = serde_json::from_value(json!({name: value.clone()})).unwrap(); let result = serde_json::to_value(transform_ocr_request("model", document(), ¶ms).unwrap()) .unwrap(); @@ -226,7 +200,7 @@ mod mapping_tests { #[case] name: &str, #[case] value: Value, ) { - let params: MistralOcrParams = serde_json::from_value(json!({name:value.clone()})).unwrap(); + let params: OpaqueParams = serde_json::from_value(json!({name:value.clone()})).unwrap(); let result = serde_json::to_value( transform_ocr_request("mistral-ocr-latest", document(), ¶ms).unwrap(), ) @@ -237,7 +211,7 @@ mod mapping_tests { #[rstest] fn transform_ocr_request_includes_multiple_new_params() { - let params: MistralOcrParams = serde_json::from_value(json!({ + let params: OpaqueParams = serde_json::from_value(json!({ "table_format":"html", "confidence_scores_granularity":"page", "extract_header":true @@ -312,9 +286,7 @@ use crate::constants::MISTRAL_OCR_API_BASE; use crate::llms::base_llm::ocr::transformation::BaseOcrConfig; use crate::ocr::OcrClient; use crate::ocr::error::OcrError; -use crate::ocr::prepare::{ - _prepare_ocr_request, ParsedProviderParams, credential_env, transform_request_body, -}; +use crate::ocr::prepare::{credential_env, transform_request_body}; use crate::ocr::types::{LiteLLMOcrRequest, OcrConnection}; use crate::url_utils::ApiUrl; @@ -328,7 +300,7 @@ impl MistralOCRConfig { &self, model: &str, document: crate::ocr::types::OcrDocument, - params: &MistralOcrParams, + params: &OpaqueParams, ) -> Result { transform_ocr_request(model, document, params) } @@ -337,15 +309,30 @@ impl MistralOCRConfig { impl BaseOcrConfig for MistralOCRConfig { type ProviderResponse = MistralOcrResponse; + fn get_supported_ocr_params(&self, _model: &str) -> &'static [&'static str] { + &[ + "pages", + "include_image_base64", + "image_limit", + "image_min_size", + "bbox_annotation_format", + "document_annotation_format", + "document_annotation_prompt", + "extract_header", + "extract_footer", + "table_format", + "confidence_scores_granularity", + "include_blocks", + "id", + ] + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, client: &OcrClient, ) -> Result { - let ParsedProviderParams { - known: params, - extra_params: _extra_params, - } = _prepare_ocr_request::(request)?; + let params = self.map_ocr_params(&request.model, &request.optional_params); let headers = validate_environment(&request.connection, &credential_env)?; let url = get_complete_url(request.connection.api_base.as_deref())?; let body = self.transform_ocr_request(&request.model, request.document.clone(), ¶ms)?; diff --git a/litellm-rust/crates/core/src/llms/reducto/ocr/transformation.rs b/litellm-rust/crates/core/src/llms/reducto/ocr/transformation.rs index 2053e9e9ee3..daa9a61e781 100644 --- a/litellm-rust/crates/core/src/llms/reducto/ocr/transformation.rs +++ b/litellm-rust/crates/core/src/llms/reducto/ocr/transformation.rs @@ -446,6 +446,10 @@ mod legacy { impl BaseOcrConfig for ReductoParseLegacyConfig { type ProviderResponse = ReductoResponse; + fn get_supported_ocr_params(&self, _model: &str) -> &'static [&'static str] { + &["enhance"] + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, @@ -499,6 +503,10 @@ mod v3 { impl BaseOcrConfig for ReductoParseV3Config { type ProviderResponse = ReductoResponse; + fn get_supported_ocr_params(&self, _model: &str) -> &'static [&'static str] { + &["formatting", "retrieval", "settings"] + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, diff --git a/litellm-rust/crates/core/src/llms/vertex_ai/ocr/deepseek_transformation.rs b/litellm-rust/crates/core/src/llms/vertex_ai/ocr/deepseek_transformation.rs index 566b9780b9c..5f7747168b5 100644 --- a/litellm-rust/crates/core/src/llms/vertex_ai/ocr/deepseek_transformation.rs +++ b/litellm-rust/crates/core/src/llms/vertex_ai/ocr/deepseek_transformation.rs @@ -229,6 +229,10 @@ pub(crate) struct VertexAIDeepSeekOCRConfig; impl BaseOcrConfig for VertexAIDeepSeekOCRConfig { type ProviderResponse = DeepSeekOcrResponse; + fn get_supported_ocr_params(&self, _model: &str) -> &'static [&'static str] { + &["stream", "temperature", "max_tokens", "top_p", "n", "stop"] + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, diff --git a/litellm-rust/crates/core/src/llms/vertex_ai/ocr/transformation.rs b/litellm-rust/crates/core/src/llms/vertex_ai/ocr/transformation.rs index 35a48ceb69a..8487eb1daa7 100644 --- a/litellm-rust/crates/core/src/llms/vertex_ai/ocr/transformation.rs +++ b/litellm-rust/crates/core/src/llms/vertex_ai/ocr/transformation.rs @@ -2,14 +2,12 @@ use super::common_utils::validate_destination; use crate::Error; use crate::auth::vertex::{self, VertexConfig}; use crate::llms::base_llm::ocr::transformation::BaseOcrConfig; +use crate::llms::mistral::ocr::MistralOcrResponse; use crate::llms::mistral::ocr::transformation::MistralOCRConfig; -use crate::llms::mistral::ocr::{MistralOcrParams, MistralOcrResponse}; use crate::ocr::OcrClient; use crate::ocr::document::{inline_remote_document, validate_inline_document}; use crate::ocr::error::{OcrError, OcrRequestError, OcrResponseError}; -use crate::ocr::prepare::{ - _prepare_ocr_request, ParsedProviderParams, credential_env, transform_request_body, -}; +use crate::ocr::prepare::{credential_env, transform_request_body}; use crate::ocr::types::{LiteLLMOcrRequest, LiteLLMOcrResponse}; use crate::url_utils::ApiUrl; const DEFAULT_LOCATION: &str = "us-central1"; @@ -20,16 +18,17 @@ pub(crate) struct VertexAIOCRConfig; impl BaseOcrConfig for VertexAIOCRConfig { type ProviderResponse = MistralOcrResponse; + fn get_supported_ocr_params(&self, model: &str) -> &'static [&'static str] { + MistralOCRConfig.get_supported_ocr_params(model) + } + async fn prepare_request( &self, request: &LiteLLMOcrRequest, client: &OcrClient, ) -> Result { validate_destination(&request.connection)?; - let ParsedProviderParams { - known: params, - extra_params: _extra_params, - } = _prepare_ocr_request::(request)?; + let params = self.map_ocr_params(&request.model, &request.optional_params); let config = VertexConfig::from_sourced_optional_params( &request.optional_params, &request.input_sources, diff --git a/litellm-rust/crates/core/src/ocr/hooks.rs b/litellm-rust/crates/core/src/ocr/hooks.rs index 4d4a387e553..cbafbae042e 100644 --- a/litellm-rust/crates/core/src/ocr/hooks.rs +++ b/litellm-rust/crates/core/src/ocr/hooks.rs @@ -100,7 +100,7 @@ impl CallLifecycleHooks( request: &LiteLLMOcrRequest, ) -> Result, OcrRequestError> { super::wire::decode_request_value( - Value::Object(request.optional_params.clone()), + Value::Object(request.optional_params.clone().into()), "optional_params", ) } diff --git a/litellm-rust/crates/core/src/ocr/provider_config.rs b/litellm-rust/crates/core/src/ocr/provider_config.rs index f361ca49b0c..bc43f38826b 100644 --- a/litellm-rust/crates/core/src/ocr/provider_config.rs +++ b/litellm-rust/crates/core/src/ocr/provider_config.rs @@ -1,4 +1,13 @@ use crate::Error; +use crate::llms::azure_ai::ocr::cohere_parse_transformation::AzureAICohereParseConfig; +use crate::llms::azure_ai::ocr::document_intelligence::transformation::AzureDocumentIntelligenceOCRConfig; +use crate::llms::azure_ai::ocr::transformation::AzureAIOCRConfig; +use crate::llms::base_llm::ocr::transformation::BaseOcrConfig; +use crate::llms::cohere::ocr::transformation::CohereParseConfig; +use crate::llms::mistral::ocr::transformation::MistralOCRConfig; +use crate::llms::reducto::ocr::transformation::{ReductoParseLegacyConfig, ReductoParseV3Config}; +use crate::llms::vertex_ai::ocr::deepseek_transformation::VertexAIDeepSeekOCRConfig; +use crate::llms::vertex_ai::ocr::transformation::VertexAIOCRConfig; use crate::routing_utils::provider::{CustomLlmProvider, get_custom_llm_provider}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -26,6 +35,24 @@ impl OcrConfigKind { Self::VertexAi | Self::VertexDeepSeek => OcrProvider::VertexAi, } } + + pub(crate) fn get_supported_ocr_params(self, model: &str) -> &'static [&'static str] { + match self { + Self::Cohere => CohereParseConfig.get_supported_ocr_params(model), + Self::Mistral => MistralOCRConfig.get_supported_ocr_params(model), + Self::AzureAi => AzureAIOCRConfig::default().get_supported_ocr_params(model), + Self::AzureCohere => { + AzureAICohereParseConfig::default().get_supported_ocr_params(model) + } + Self::AzureDocumentIntelligence => { + AzureDocumentIntelligenceOCRConfig.get_supported_ocr_params(model) + } + Self::ReductoLegacy => ReductoParseLegacyConfig.get_supported_ocr_params(model), + Self::ReductoV3 => ReductoParseV3Config.get_supported_ocr_params(model), + Self::VertexAi => VertexAIOCRConfig::default().get_supported_ocr_params(model), + Self::VertexDeepSeek => VertexAIDeepSeekOCRConfig.get_supported_ocr_params(model), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/litellm-rust/crates/core/src/ocr/types.rs b/litellm-rust/crates/core/src/ocr/types.rs index 544f46fb797..27d172a49f0 100644 --- a/litellm-rust/crates/core/src/ocr/types.rs +++ b/litellm-rust/crates/core/src/ocr/types.rs @@ -10,6 +10,7 @@ use super::provider_config::{OcrConfigKind, resolve_provider_config}; use crate::Error; use crate::auth::{InputSource, TokenProviderHandle}; use crate::constants::OCR_HTTP_TIMEOUT_SECS; +use crate::params::OpaqueParams; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] @@ -95,7 +96,7 @@ pub struct LiteLLMOcrRequest { pub connection: OcrConnection, pub hooks: Arc, pub litellm_call_id: Option, - pub optional_params: Map, + pub optional_params: OpaqueParams, pub input_sources: BTreeMap, pub azure_ad_token_provider: Option, pub(crate) config: OcrConfigKind, @@ -106,7 +107,7 @@ impl LiteLLMOcrRequest { model: String, document: OcrDocument, custom_llm_provider: Option<&str>, - optional_params: Map, + optional_params: OpaqueParams, ) -> Result { let (model, config) = resolve_provider_config(&model, custom_llm_provider)?; diff --git a/litellm-rust/crates/core/src/ocr/wire.rs b/litellm-rust/crates/core/src/ocr/wire.rs index dc006f44252..0816124b430 100644 --- a/litellm-rust/crates/core/src/ocr/wire.rs +++ b/litellm-rust/crates/core/src/ocr/wire.rs @@ -6,6 +6,7 @@ use std::time::Duration; use super::types::{LiteLLMOcrRequest, OcrConnection, OcrDocument}; use crate::Error; use crate::auth::InputSource; +use crate::params::OpaqueParams; use serde::{ Deserialize, de::{DeserializeOwned, IntoDeserializer}, @@ -13,26 +14,6 @@ use serde::{ use serde_json::{Map, Value}; const COMMON_OPTION_FIELDS: &[&str] = &["req_format", "extra_body", "max_response_bytes"]; -const MISTRAL_OPTION_FIELDS: &[&str] = &[ - "pages", - "include_image_base64", - "image_limit", - "image_min_size", - "bbox_annotation_format", - "document_annotation_format", - "document_annotation_prompt", - "extract_header", - "extract_footer", - "table_format", - "confidence_scores_granularity", - "include_blocks", - "id", -]; -const DEEPSEEK_OPTION_FIELDS: &[&str] = - &["stream", "temperature", "max_tokens", "top_p", "n", "stop"]; -const DOCUMENT_INTELLIGENCE_OPTION_FIELDS: &[&str] = &["pages", "features"]; -const REDUCTO_V3_OPTION_FIELDS: &[&str] = &["formatting", "retrieval", "settings"]; -const REDUCTO_LEGACY_OPTION_FIELDS: &[&str] = &["enhance"]; const AZURE_AUTH_OPTION_FIELDS: &[&str] = &[ "azure_ad_token", "tenant_id", @@ -76,7 +57,7 @@ pub struct OcrWireRequest { pub custom_llm_provider: Option, pub extra_headers: Option>, #[serde(default)] - pub optional_params: Map, + pub optional_params: OpaqueParams, #[serde(default)] pub input_sources: BTreeMap, pub timeout_seconds: Option, @@ -92,17 +73,9 @@ pub fn consumed_optional_param_names( ) -> Result, Error> { use super::provider_config::OcrConfigKind; - let (_, config) = super::provider_config::resolve_provider_config(model, custom_llm_provider)?; - let provider_fields: &[&str] = match config { - OcrConfigKind::Cohere | OcrConfigKind::AzureCohere => &["output_format"], - OcrConfigKind::Mistral | OcrConfigKind::AzureAi | OcrConfigKind::VertexAi => { - MISTRAL_OPTION_FIELDS - } - OcrConfigKind::AzureDocumentIntelligence => DOCUMENT_INTELLIGENCE_OPTION_FIELDS, - OcrConfigKind::ReductoV3 => REDUCTO_V3_OPTION_FIELDS, - OcrConfigKind::ReductoLegacy => REDUCTO_LEGACY_OPTION_FIELDS, - OcrConfigKind::VertexDeepSeek => DEEPSEEK_OPTION_FIELDS, - }; + let (provider_model, config) = + super::provider_config::resolve_provider_config(model, custom_llm_provider)?; + let provider_fields = config.get_supported_ocr_params(&provider_model); let auth_fields: &[&str] = match config { OcrConfigKind::AzureAi | OcrConfigKind::AzureDocumentIntelligence diff --git a/litellm-rust/crates/core/src/params.rs b/litellm-rust/crates/core/src/params.rs new file mode 100644 index 00000000000..d5d89a78eb8 --- /dev/null +++ b/litellm-rust/crates/core/src/params.rs @@ -0,0 +1,87 @@ +use std::ops::Deref; + +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct OpaqueParams(Map); + +impl OpaqueParams { + pub fn into_inner(self) -> Map { + self.0 + } + + pub fn retain_supported(&self, supported: &[&str]) -> Self { + Self( + self.iter() + .filter(|(name, _)| supported.contains(&name.as_str())) + .map(|(name, value)| (name.clone(), value.clone())) + .collect(), + ) + } +} + +impl Deref for OpaqueParams { + type Target = Map; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for OpaqueParams { + fn from(value: Map) -> Self { + Self(value) + } +} + +impl From for Map { + fn from(value: OpaqueParams) -> Self { + value.0 + } +} + +impl FromIterator<(String, Value)> for OpaqueParams { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl IntoIterator for OpaqueParams { + type Item = (String, Value); + type IntoIter = serde_json::map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn supported_keys_preserve_opaque_values() { + let params: OpaqueParams = serde_json::from_value(json!({ + "object": {"future": [1, null]}, + "null": null, + "unsupported": true + })) + .unwrap(); + + let retained = params.retain_supported(&["object", "null"]); + + assert_eq!( + serde_json::to_value(retained).unwrap(), + json!({"object": {"future": [1, null]}, "null": null}) + ); + } + + #[test] + fn outer_value_must_be_an_object() { + assert!(serde_json::from_value::(json!(["value"])).is_err()); + } +} diff --git a/litellm-rust/crates/core/src/providers/anthropic/chat_completions/tests.rs b/litellm-rust/crates/core/src/providers/anthropic/chat_completions/tests.rs index b22de6c47de..6ac704a3567 100644 --- a/litellm-rust/crates/core/src/providers/anthropic/chat_completions/tests.rs +++ b/litellm-rust/crates/core/src/providers/anthropic/chat_completions/tests.rs @@ -6,9 +6,9 @@ fn messages(value: Value) -> Vec { serde_json::from_value(value).expect("valid messages") } -fn params(value: Value) -> Map { +fn params(value: Value) -> OpaqueParams { match value { - Value::Object(map) => map, + Value::Object(map) => map.into(), other => panic!("params must be an object, got {other}"), } } @@ -419,13 +419,20 @@ fn resolves_the_messages_url_and_x_api_key_auth() { let config = &ANTHROPIC_CHAT_COMPLETIONS_CONFIG; assert_eq!( config - .complete_url(None, "claude-sonnet-4-5", &Map::new(), &|_| None) + .complete_url(None, "claude-sonnet-4-5", &OpaqueParams::default(), &|_| { + None + }) .expect("url builds"), "https://api.anthropic.com/v1/messages" ); assert_eq!( config - .auth(Some("sk-x"), "claude-sonnet-4-5", &Map::new(), &|_| None) + .auth( + Some("sk-x"), + "claude-sonnet-4-5", + &OpaqueParams::default(), + &|_| None, + ) .expect("auth resolves"), ChatCompletionsAuth::Header { name: "x-api-key", diff --git a/litellm-rust/crates/core/src/providers/anthropic/chat_completions/transformation.rs b/litellm-rust/crates/core/src/providers/anthropic/chat_completions/transformation.rs index 56175322442..8c674c868a1 100644 --- a/litellm-rust/crates/core/src/providers/anthropic/chat_completions/transformation.rs +++ b/litellm-rust/crates/core/src/providers/anthropic/chat_completions/transformation.rs @@ -11,6 +11,7 @@ use crate::chat_completions::types::{ }; use crate::constants::ANTHROPIC_OAUTH_TOKEN_PREFIX; use crate::error::Error; +use crate::params::OpaqueParams; use crate::providers::anthropic::messages::transformation::{ complete_anthropic_url, resolve_anthropic_api_key, }; @@ -77,7 +78,7 @@ impl ChatCompletionsProviderConfig for AnthropicChatCompletionsConfig { &self, api_base: Option<&str>, _model: &str, - _optional_params: &Map, + _optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result { Ok(complete_anthropic_url(api_base, env_lookup)) @@ -87,7 +88,7 @@ impl ChatCompletionsProviderConfig for AnthropicChatCompletionsConfig { &self, api_key: Option<&str>, _model: &str, - _optional_params: &Map, + _optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result { Ok(ChatCompletionsAuth::Header { @@ -124,7 +125,7 @@ impl ChatCompletionsProviderConfig for AnthropicChatCompletionsConfig { fn unsupported_reason( &self, messages: &[ChatMessage], - optional_params: &Map, + optional_params: &OpaqueParams, ) -> Option { unsupported_param(self.supported_openai_params(), &[], optional_params) .or_else(|| messages.iter().find_map(unsupported_message)) @@ -141,10 +142,14 @@ impl ChatCompletionsProviderConfig for AnthropicChatCompletionsConfig { &self, model: &str, messages: Vec, - optional_params: Map, + optional_params: OpaqueParams, ) -> Result { Ok(ProviderChatRequestData { - body: anthropic_body(model, &build_conversation(&messages), optional_params), + body: anthropic_body( + model, + &build_conversation(&messages), + optional_params.into_inner(), + ), }) } diff --git a/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs b/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs index bb4f6afe5f9..3b078ddf037 100644 --- a/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs +++ b/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs @@ -7,6 +7,7 @@ use crate::audio_transcription::types::{ AudioTranscriptionRequestData, AudioTranscriptionResponseData, }; use crate::error::{Error, json_type_name}; +use crate::params::OpaqueParams; pub use super::aws_base::{aws_auth_config, bedrock_model_id_and_region, resolve_bedrock_region}; use super::constants::{BEDROCK_RUNTIME_ENDPOINT_TEMPLATE, BEDROCK_SERVICE}; @@ -54,7 +55,7 @@ impl AudioTranscriptionProviderConfig for BedrockAudioTranscriptionConfig { &self, _model: &str, audio: Value, - optional_params: Map, + optional_params: OpaqueParams, ) -> Result { let (data, format) = audio_fields(audio)?; let mut instruction = "Transcribe the audio. Respond with only the transcript.".to_string(); @@ -109,7 +110,7 @@ impl AudioTranscriptionProviderConfig for BedrockAudioTranscriptionConfig { &self, api_base: Option<&str>, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result { let (model_id, model_region) = bedrock_model_id_and_region(model); @@ -131,7 +132,7 @@ impl AudioTranscriptionProviderConfig for BedrockAudioTranscriptionConfig { fn auth_strategy( &self, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result { let (_, model_region) = bedrock_model_id_and_region(model); @@ -152,12 +153,12 @@ mod tests { #[test] fn request_matches_python_shape() { - let params = Map::from_iter([ + let params = OpaqueParams::from(Map::from_iter([ ("language".to_string(), json!("en")), ("prompt".to_string(), json!("Speaker names")), ("temperature".to_string(), json!(0)), ("timestamp_granularities".to_string(), json!(["word"])), - ]); + ])); let params = BEDROCK_AUDIO_TRANSCRIPTION_CONFIG.map_transcription_params(¶ms); let result = BEDROCK_AUDIO_TRANSCRIPTION_CONFIG .transform_transcription_request( @@ -199,14 +200,17 @@ mod tests { let result = BEDROCK_AUDIO_TRANSCRIPTION_CONFIG.transform_transcription_request( "model", json!({"data": "AQI="}), - Map::new(), + OpaqueParams::default(), ); assert!(result.is_err()); } #[test] fn region_and_url_precedence_match_python() { - let params = Map::from_iter([("aws_region_name".to_string(), json!("eu-west-1"))]); + let params = OpaqueParams::from(Map::from_iter([( + "aws_region_name".to_string(), + json!("eu-west-1"), + )])); let url = BEDROCK_AUDIO_TRANSCRIPTION_CONFIG .complete_url( None, diff --git a/litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs b/litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs index c86f061b9ca..05f8be1f11a 100644 --- a/litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs +++ b/litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs @@ -6,9 +6,9 @@ fn messages(value: Value) -> Vec { serde_json::from_value(value).expect("valid messages") } -fn params(value: Value) -> Map { +fn params(value: Value) -> OpaqueParams { match value { - Value::Object(map) => map, + Value::Object(map) => map.into(), other => panic!("params must be an object, got {other}"), } } @@ -225,9 +225,12 @@ fn builds_the_converse_url_from_the_region_in_the_model_id() { let config = &BEDROCK_CHAT_COMPLETIONS_CONFIG; assert_eq!( config - .complete_url(None, "us-east-1/anthropic.claude-v2", &Map::new(), &|_| { - None - }) + .complete_url( + None, + "us-east-1/anthropic.claude-v2", + &OpaqueParams::default(), + &|_| { None } + ) .expect("url builds"), "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-v2/converse" ); @@ -239,13 +242,23 @@ fn falls_back_to_the_region_env_then_the_default_region() { let with_env = |key: &str| (key == "AWS_REGION_NAME").then(|| "eu-west-1".to_string()); assert_eq!( config - .complete_url(None, "anthropic.claude-v2", &Map::new(), &with_env) + .complete_url( + None, + "anthropic.claude-v2", + &OpaqueParams::default(), + &with_env + ) .expect("url builds"), "https://bedrock-runtime.eu-west-1.amazonaws.com/model/anthropic.claude-v2/converse" ); assert_eq!( config - .complete_url(None, "anthropic.claude-v2", &Map::new(), &|_| None) + .complete_url( + None, + "anthropic.claude-v2", + &OpaqueParams::default(), + &|_| None + ) .expect("url builds"), "https://bedrock-runtime.us-west-2.amazonaws.com/model/anthropic.claude-v2/converse" ); @@ -276,7 +289,7 @@ fn signs_with_sigv4_in_the_resolved_region() { .auth( None, "eu-central-1/anthropic.claude-v2", - &Map::new(), + &OpaqueParams::default(), &|_| None ) .expect("auth resolves"), @@ -300,7 +313,7 @@ fn a_bearer_token_outranks_sigv4_the_way_python_resolves_it() { .auth( api_key, "eu-central-1/anthropic.claude-v2", - &Map::new(), + &OpaqueParams::default(), env, ) .expect("auth resolves") @@ -542,7 +555,7 @@ fn leaves_a_complete_converse_url_untouched() { .complete_url( Some(already_built), "anthropic.claude-v2", - &Map::new(), + &OpaqueParams::default(), &|_| None ) .expect("url builds"), diff --git a/litellm-rust/crates/core/src/providers/bedrock/chat_completions/transformation.rs b/litellm-rust/crates/core/src/providers/bedrock/chat_completions/transformation.rs index 02e6797b338..92a784ccf5a 100644 --- a/litellm-rust/crates/core/src/providers/bedrock/chat_completions/transformation.rs +++ b/litellm-rust/crates/core/src/providers/bedrock/chat_completions/transformation.rs @@ -12,6 +12,7 @@ use crate::chat_completions::types::{ ProviderChatResponseData, }; use crate::error::Error; +use crate::params::OpaqueParams; use super::super::aws_base::{bedrock_model_id_and_region, resolve_bedrock_region}; use super::super::constants::{AWS_BEARER_TOKEN_BEDROCK, BEDROCK_RUNTIME_ENDPOINT_TEMPLATE}; @@ -109,7 +110,7 @@ impl ChatCompletionsProviderConfig for BedrockChatCompletionsConfig { &self, api_base: Option<&str>, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result { let (model_id, model_region) = bedrock_model_id_and_region(model); @@ -136,7 +137,7 @@ impl ChatCompletionsProviderConfig for BedrockChatCompletionsConfig { &self, api_key: Option<&str>, model: &str, - optional_params: &Map, + optional_params: &OpaqueParams, env_lookup: &dyn Fn(&str) -> Option, ) -> Result { // Python reads `api_key` as the Bedrock bearer token and consults the @@ -174,7 +175,7 @@ impl ChatCompletionsProviderConfig for BedrockChatCompletionsConfig { fn unsupported_reason( &self, messages: &[ChatMessage], - optional_params: &Map, + optional_params: &OpaqueParams, ) -> Option { unsupported_param( self.supported_openai_params(), @@ -212,7 +213,7 @@ impl ChatCompletionsProviderConfig for BedrockChatCompletionsConfig { &self, _model: &str, messages: Vec, - optional_params: Map, + optional_params: OpaqueParams, ) -> Result { Ok(ProviderChatRequestData { body: converse_body(&build_conversation(&messages), &optional_params), diff --git a/litellm-rust/crates/core/tests/azure_document_intelligence_ocr.rs b/litellm-rust/crates/core/tests/azure_document_intelligence_ocr.rs index 3fca59033cc..d20c1e8e83b 100644 --- a/litellm-rust/crates/core/tests/azure_document_intelligence_ocr.rs +++ b/litellm-rust/crates/core/tests/azure_document_intelligence_ocr.rs @@ -63,7 +63,7 @@ async fn rejects_invalid_pages_features_and_format() { api_base: Some("http://127.0.0.1:1".into()), custom_llm_provider: None, extra_headers: None, - optional_params: options.as_object().unwrap().clone(), + optional_params: options.as_object().unwrap().clone().into(), input_sources: Default::default(), timeout_seconds: None, }); diff --git a/litellm-rust/crates/core/tests/ocr.rs b/litellm-rust/crates/core/tests/ocr.rs index 55f8713d76e..0ce2e4c1c39 100644 --- a/litellm-rust/crates/core/tests/ocr.rs +++ b/litellm-rust/crates/core/tests/ocr.rs @@ -27,7 +27,8 @@ fn request_boundary_selects_mistral_and_rejects_unknown_providers() { optional_params: json!({"extract_header":true,"unknown":42}) .as_object() .unwrap() - .clone(), + .clone() + .into(), input_sources: Default::default(), timeout_seconds: None, }; @@ -40,7 +41,7 @@ fn request_boundary_selects_mistral_and_rejects_unknown_providers() { api_base: None, custom_llm_provider: Some("unknown".into()), extra_headers: None, - optional_params: serde_json::Map::new(), + optional_params: Default::default(), input_sources: Default::default(), timeout_seconds: None, }) diff --git a/litellm-rust/crates/core/tests/ocr/support.rs b/litellm-rust/crates/core/tests/ocr/support.rs index a2e67dffc7d..4528b9f7ad3 100644 --- a/litellm-rust/crates/core/tests/ocr/support.rs +++ b/litellm-rust/crates/core/tests/ocr/support.rs @@ -29,7 +29,7 @@ pub(crate) fn wire_request(model: &str, base: &str, options: Value) -> LiteLLMOc api_base: Some(base.into()), custom_llm_provider: None, extra_headers: None, - optional_params: options.as_object().unwrap().clone(), + optional_params: options.as_object().unwrap().clone().into(), input_sources: Default::default(), timeout_seconds: Some(2.0), }) diff --git a/litellm-rust/crates/python-bridge/src/routes/audio_transcription/value.rs b/litellm-rust/crates/python-bridge/src/routes/audio_transcription/value.rs index af60515b0e2..4616d770bf7 100644 --- a/litellm-rust/crates/python-bridge/src/routes/audio_transcription/value.rs +++ b/litellm-rust/crates/python-bridge/src/routes/audio_transcription/value.rs @@ -40,7 +40,7 @@ fn prepare_transcription( api_base: api_base.as_deref(), custom_llm_provider: custom_llm_provider.as_deref(), extra_headers, - optional_params, + optional_params: optional_params.into(), timeout, }) .await diff --git a/litellm-rust/crates/python-bridge/src/routes/chat_completions/value.rs b/litellm-rust/crates/python-bridge/src/routes/chat_completions/value.rs index e67bfa89cc7..f8eda2c7290 100644 --- a/litellm-rust/crates/python-bridge/src/routes/chat_completions/value.rs +++ b/litellm-rust/crates/python-bridge/src/routes/chat_completions/value.rs @@ -37,7 +37,7 @@ fn prepare_chat_completions( run_chat_completions(ChatCompletionsRequest { model: &model, messages: Value::Array(messages), - optional_params, + optional_params: optional_params.into(), api_key: api_key.as_deref(), api_base: api_base.as_deref(), custom_llm_provider: custom_llm_provider.as_deref(), @@ -56,7 +56,7 @@ fn chat_completions_decline( #[pyo3(from_py_with = litellm_python_interop::from_py)] optional_params: Option, custom_llm_provider: Option, ) -> PyResult> { - let optional_params = object_or_empty("optional_params", optional_params)?; + let optional_params = object_or_empty("optional_params", optional_params)?.into(); Ok(chat_completions_decline_reason( &model, custom_llm_provider.as_deref(), diff --git a/litellm-rust/crates/python-bridge/src/routes/ocr/project.rs b/litellm-rust/crates/python-bridge/src/routes/ocr/project.rs index 8b6a1b02e19..28ccf466e21 100644 --- a/litellm-rust/crates/python-bridge/src/routes/ocr/project.rs +++ b/litellm-rust/crates/python-bridge/src/routes/ocr/project.rs @@ -143,7 +143,7 @@ pub(super) fn project_request( api_base: arguments.api_base()?, custom_llm_provider, extra_headers: arguments.extra_headers()?, - optional_params, + optional_params: optional_params.into(), input_sources, timeout_seconds: arguments.timeout_seconds()?, }; @@ -500,7 +500,7 @@ kwargs = {'api_key': key} api_base: None, custom_llm_provider: None, extra_headers: None, - optional_params: Map::new(), + optional_params: Default::default(), input_sources: Default::default(), timeout_seconds: None, }) { diff --git a/litellm-rust/crates/python-bridge/src/routes/ocr/value.rs b/litellm-rust/crates/python-bridge/src/routes/ocr/value.rs index 051ac19d4fb..b8b4a481a24 100644 --- a/litellm-rust/crates/python-bridge/src/routes/ocr/value.rs +++ b/litellm-rust/crates/python-bridge/src/routes/ocr/value.rs @@ -44,7 +44,7 @@ fn prepare_ocr( api_base, custom_llm_provider, extra_headers, - optional_params, + optional_params: optional_params.into(), input_sources, timeout_seconds: timeout.map(|value| value.as_secs_f64()), })?;