Files

1 line
22 KiB
JSON

{"repo": "big-dao/ai-gateway", "files": {"crates/providers/src/openai_compat.rs": {"c819eb2c03f615f9a125eaa3070b65b084dc0dc6": {"t": 1785746248.7941005, "n": 5221, "h": "c1e40208432122b5", "c": "//! OpenAI-compatible provider adapter.\n//!\n//! Most modern LLM APIs (DeepSeek, Groq, Together, vLLM, SGLang, TGI, Kimi,\n//! Zhipu GLM, etc.) reuse the OpenAI `/v1/chat/completions` wire format. This\n//! adapter wraps the production `OpenAIProvider` and applies per-provider\n//! behavior tweaks via [`FieldOverrides`] — without duplicating the request /\n//! response / SSE plumbing.\n\npub use gateway_core::config::FieldOverrides;\n\nuse std::collections::HashMap;\n\nuse crate::openai::OpenAIProvider;\n\n/// An OpenAI-compatible provider.\n///\n/// Wraps [`OpenAIProvider`] and tags it with a logical name + overrides.\n/// The `LLMProvider` impl is delegated directly to the inner OAI provider.\npub struct OpenAICompatProvider {\n name: String,\n inner: OpenAIProvider,\n pub field_overrides: FieldOverrides,\n}\n\nimpl OpenAICompatProvider {\n pub fn new(\n name: impl Into<String>,\n api_key: Option<String>,\n base_url: impl Into<String>,\n _models: Vec<String>,\n extra_headers: HashMap<String, String>,\n field_overrides: FieldOverrides,\n ) -> Self {\n Self {\n name: name.into(),\n inner: OpenAIProvider::new(api_key, Some(base_url.into()), extra_headers),\n field_overrides,\n }\n }\n}\n\n// Implement LLMProvider by delegation.\n#[async_trait::async_trait]\nimpl gateway_core::provider::LLMProvider for OpenAICompatProvider {\n fn name(&self) -> &str {\n &self.name\n }\n\n async fn chat_completion(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<gateway_core::types::ChatCompletionResponse, gateway_core::error::GatewayError>\n {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion(request).await\n }\n\n async fn chat_completion_stream(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<\n futures::stream::BoxStream<\n 'static,\n Result<gateway_core::types::ChatCompletionChunk, gateway_core::error::GatewayError>,\n >,\n gateway_core::error::GatewayError,\n > {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion_stream(request).await\n }\n}\n\n/// Apply [`FieldOverrides`] to a request before sending.\nfn apply_field_overrides(\n overrides: &FieldOverrides,\n request: &mut gateway_core::types::ChatCompletionRequest,\n) {\n if overrides.emit_reasoning_content {\n // Marker so future handlers know to surface reasoning_content.\n request\n .extra\n .insert(\"emit_reasoning_content\".into(), serde_json::json!(true));\n }\n if let Some(kwargs) = &overrides.chat_template_kwargs {\n request\n .extra\n .insert(\"chat_template_kwargs\".into(), kwargs.clone());\n }\n if !overrides.stream_field_renames.is_empty() {\n request.extra.insert(\n \"stream_field_renames\".into(),\n serde_json::to_value(&overrides.stream_field_renames).unwrap_or_default(),\n );\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_field_overrides_default() {\n let o = FieldOverrides::default();\n assert!(!o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_none());\n assert!(o.stream_field_renames.is_empty());\n }\n\n #[test]\n fn test_field_overrides_serde() {\n let json = r#\"{\n \"emit_reasoning_content\": true,\n \"chat_template_kwargs\": {\"thinking\": {\"type\": \"enabled\"}},\n \"stream_field_renames\": {\"delta\": \"content\"}\n }\"#;\n let o: FieldOverrides = serde_json::from_str(json).unwrap();\n assert!(o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_some());\n assert_eq!(o.stream_field_renames[\"delta\"], \"content\");\n }\n\n #[test]\n fn test_compat_provider_name() {\n use gateway_core::provider::LLMProvider;\n let p = OpenAICompatProvider::new(\n \"deepseek\",\n Some(\"test-key\".into()),\n \"https://api.deepseek.com/v1\",\n vec![\"deepseek-chat\".into()],\n HashMap::new(),\n FieldOverrides::default(),\n );\n assert_eq!(p.name(), \"deepseek\");\n }\n\n #[test]\n fn test_apply_overrides() {\n let overrides = FieldOverrides {\n emit_reasoning_content: true,\n chat_template_kwargs: Some(serde_json::json!({\"thinking\": true})),\n stream_field_renames: HashMap::new(),\n };\n let mut req = gateway_core::types::ChatCompletionRequest {\n model: \"m\".into(),\n messages: vec![],\n temperature: None,\n max_tokens: None,\n top_p: None,\n stream: false,\n stop: None,\n presence_penalty: None,\n frequency_penalty: None,\n user: None,\n response_format: None,\n extra: HashMap::new(),\n };\n apply_field_overrides(&overrides, &mut req);\n assert_eq!(req.extra[\"emit_reasoning_content\"], serde_json::json!(true));\n assert!(req.extra.contains_key(\"chat_template_kwargs\"));\n }\n}\n"}, "a54c0d5ce6608686330631f63b0aa93c134cd2ef": {"t": 1785761064.4768996, "n": 5221, "h": "c1e40208432122b5", "c": "//! OpenAI-compatible provider adapter.\n//!\n//! Most modern LLM APIs (DeepSeek, Groq, Together, vLLM, SGLang, TGI, Kimi,\n//! Zhipu GLM, etc.) reuse the OpenAI `/v1/chat/completions` wire format. This\n//! adapter wraps the production `OpenAIProvider` and applies per-provider\n//! behavior tweaks via [`FieldOverrides`] — without duplicating the request /\n//! response / SSE plumbing.\n\npub use gateway_core::config::FieldOverrides;\n\nuse std::collections::HashMap;\n\nuse crate::openai::OpenAIProvider;\n\n/// An OpenAI-compatible provider.\n///\n/// Wraps [`OpenAIProvider`] and tags it with a logical name + overrides.\n/// The `LLMProvider` impl is delegated directly to the inner OAI provider.\npub struct OpenAICompatProvider {\n name: String,\n inner: OpenAIProvider,\n pub field_overrides: FieldOverrides,\n}\n\nimpl OpenAICompatProvider {\n pub fn new(\n name: impl Into<String>,\n api_key: Option<String>,\n base_url: impl Into<String>,\n _models: Vec<String>,\n extra_headers: HashMap<String, String>,\n field_overrides: FieldOverrides,\n ) -> Self {\n Self {\n name: name.into(),\n inner: OpenAIProvider::new(api_key, Some(base_url.into()), extra_headers),\n field_overrides,\n }\n }\n}\n\n// Implement LLMProvider by delegation.\n#[async_trait::async_trait]\nimpl gateway_core::provider::LLMProvider for OpenAICompatProvider {\n fn name(&self) -> &str {\n &self.name\n }\n\n async fn chat_completion(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<gateway_core::types::ChatCompletionResponse, gateway_core::error::GatewayError>\n {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion(request).await\n }\n\n async fn chat_completion_stream(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<\n futures::stream::BoxStream<\n 'static,\n Result<gateway_core::types::ChatCompletionChunk, gateway_core::error::GatewayError>,\n >,\n gateway_core::error::GatewayError,\n > {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion_stream(request).await\n }\n}\n\n/// Apply [`FieldOverrides`] to a request before sending.\nfn apply_field_overrides(\n overrides: &FieldOverrides,\n request: &mut gateway_core::types::ChatCompletionRequest,\n) {\n if overrides.emit_reasoning_content {\n // Marker so future handlers know to surface reasoning_content.\n request\n .extra\n .insert(\"emit_reasoning_content\".into(), serde_json::json!(true));\n }\n if let Some(kwargs) = &overrides.chat_template_kwargs {\n request\n .extra\n .insert(\"chat_template_kwargs\".into(), kwargs.clone());\n }\n if !overrides.stream_field_renames.is_empty() {\n request.extra.insert(\n \"stream_field_renames\".into(),\n serde_json::to_value(&overrides.stream_field_renames).unwrap_or_default(),\n );\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_field_overrides_default() {\n let o = FieldOverrides::default();\n assert!(!o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_none());\n assert!(o.stream_field_renames.is_empty());\n }\n\n #[test]\n fn test_field_overrides_serde() {\n let json = r#\"{\n \"emit_reasoning_content\": true,\n \"chat_template_kwargs\": {\"thinking\": {\"type\": \"enabled\"}},\n \"stream_field_renames\": {\"delta\": \"content\"}\n }\"#;\n let o: FieldOverrides = serde_json::from_str(json).unwrap();\n assert!(o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_some());\n assert_eq!(o.stream_field_renames[\"delta\"], \"content\");\n }\n\n #[test]\n fn test_compat_provider_name() {\n use gateway_core::provider::LLMProvider;\n let p = OpenAICompatProvider::new(\n \"deepseek\",\n Some(\"test-key\".into()),\n \"https://api.deepseek.com/v1\",\n vec![\"deepseek-chat\".into()],\n HashMap::new(),\n FieldOverrides::default(),\n );\n assert_eq!(p.name(), \"deepseek\");\n }\n\n #[test]\n fn test_apply_overrides() {\n let overrides = FieldOverrides {\n emit_reasoning_content: true,\n chat_template_kwargs: Some(serde_json::json!({\"thinking\": true})),\n stream_field_renames: HashMap::new(),\n };\n let mut req = gateway_core::types::ChatCompletionRequest {\n model: \"m\".into(),\n messages: vec![],\n temperature: None,\n max_tokens: None,\n top_p: None,\n stream: false,\n stop: None,\n presence_penalty: None,\n frequency_penalty: None,\n user: None,\n response_format: None,\n extra: HashMap::new(),\n };\n apply_field_overrides(&overrides, &mut req);\n assert_eq!(req.extra[\"emit_reasoning_content\"], serde_json::json!(true));\n assert!(req.extra.contains_key(\"chat_template_kwargs\"));\n }\n}\n"}, "c3f16ef3bbd71b13d17ae2cd6e634bf223686f2a": {"t": 1785761066.7808115, "n": 5220, "h": "2b9a91e2881e58b5", "c": "//! OpenAI-compatible provider adapter.\n//!\n//! Most modern LLM APIs (DeepSeek, Groq, Together, vLLM, SGLang, TGI, Kimi,\n//! Zhipu GLM, etc.) reuse the OpenAI `/v1/chat/completions` wire format. This\n//! adapter wraps the production `OpenAIProvider` and applies per-provider\n//! behavior tweaks via [`FieldOverrides`] — without duplicating the request /\n//! response / SSE plumbing.\n\npub use gateway_core::config::FieldOverrides;\n\nuse std::collections::HashMap;\n\nuse crate::openai::OpenAIProvider;\n\n/// An OpenAI-compatible provider.\n///\n/// Wraps [`OpenAIProvider`] and tags it with a logical name + overrides.\n/// The `LLMProvider` impl is delegated directly to the inner OAI provider.\npub struct OpenAICompatProvider {\n name: String,\n inner: OpenAIProvider,\n pub field_overrides: FieldOverrides,\n}\n\nimpl OpenAICompatProvider {\n pub fn new(\n name: impl Into<String>,\n api_key: Option<String>,\n base_url: impl Into<String>,\n models: Vec<String>,\n extra_headers: HashMap<String, String>,\n field_overrides: FieldOverrides,\n ) -> Self {\n Self {\n name: name.into(),\n inner: OpenAIProvider::new(api_key, Some(base_url.into()), extra_headers),\n field_overrides,\n }\n }\n}\n\n// Implement LLMProvider by delegation.\n#[async_trait::async_trait]\nimpl gateway_core::provider::LLMProvider for OpenAICompatProvider {\n fn name(&self) -> &str {\n &self.name\n }\n\n async fn chat_completion(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<gateway_core::types::ChatCompletionResponse, gateway_core::error::GatewayError>\n {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion(request).await\n }\n\n async fn chat_completion_stream(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<\n futures::stream::BoxStream<\n 'static,\n Result<gateway_core::types::ChatCompletionChunk, gateway_core::error::GatewayError>,\n >,\n gateway_core::error::GatewayError,\n > {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion_stream(request).await\n }\n}\n\n/// Apply [`FieldOverrides`] to a request before sending.\nfn apply_field_overrides(\n overrides: &FieldOverrides,\n request: &mut gateway_core::types::ChatCompletionRequest,\n) {\n if overrides.emit_reasoning_content {\n // Marker so future handlers know to surface reasoning_content.\n request\n .extra\n .insert(\"emit_reasoning_content\".into(), serde_json::json!(true));\n }\n if let Some(kwargs) = &overrides.chat_template_kwargs {\n request\n .extra\n .insert(\"chat_template_kwargs\".into(), kwargs.clone());\n }\n if !overrides.stream_field_renames.is_empty() {\n request.extra.insert(\n \"stream_field_renames\".into(),\n serde_json::to_value(&overrides.stream_field_renames).unwrap_or_default(),\n );\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_field_overrides_default() {\n let o = FieldOverrides::default();\n assert!(!o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_none());\n assert!(o.stream_field_renames.is_empty());\n }\n\n #[test]\n fn test_field_overrides_serde() {\n let json = r#\"{\n \"emit_reasoning_content\": true,\n \"chat_template_kwargs\": {\"thinking\": {\"type\": \"enabled\"}},\n \"stream_field_renames\": {\"delta\": \"content\"}\n }\"#;\n let o: FieldOverrides = serde_json::from_str(json).unwrap();\n assert!(o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_some());\n assert_eq!(o.stream_field_renames[\"delta\"], \"content\");\n }\n\n #[test]\n fn test_compat_provider_name() {\n use gateway_core::provider::LLMProvider;\n let p = OpenAICompatProvider::new(\n \"deepseek\",\n Some(\"test-key\".into()),\n \"https://api.deepseek.com/v1\",\n vec![\"deepseek-chat\".into()],\n HashMap::new(),\n FieldOverrides::default(),\n );\n assert_eq!(p.name(), \"deepseek\");\n }\n\n #[test]\n fn test_apply_overrides() {\n let overrides = FieldOverrides {\n emit_reasoning_content: true,\n chat_template_kwargs: Some(serde_json::json!({\"thinking\": true})),\n stream_field_renames: HashMap::new(),\n };\n let mut req = gateway_core::types::ChatCompletionRequest {\n model: \"m\".into(),\n messages: vec![],\n temperature: None,\n max_tokens: None,\n top_p: None,\n stream: false,\n stop: None,\n presence_penalty: None,\n frequency_penalty: None,\n user: None,\n response_format: None,\n extra: HashMap::new(),\n };\n apply_field_overrides(&overrides, &mut req);\n assert_eq!(req.extra[\"emit_reasoning_content\"], serde_json::json!(true));\n assert!(req.extra.contains_key(\"chat_template_kwargs\"));\n }\n}\n"}, "775a8078aaf4a83a58e8242976a7fe50cfc3eb18": {"t": 1785761069.187022, "n": 5217, "h": "7bc3c5cf36d2a046", "c": "//! OpenAI-compatible provider adapter.\n//!\n//! Most modern LLM APIs (DeepSeek, Groq, Together, vLLM, SGLang, TGI, Kimi,\n//! Zhipu GLM, etc.) reuse the OpenAI `/v1/chat/completions` wire format. This\n//! adapter wraps the production `OpenAIProvider` and applies per-provider\n//! behavior tweaks via [`FieldOverrides`] — without duplicating the request /\n//! response / SSE plumbing.\n\npub use gateway_core::config::FieldOverrides;\n\nuse std::collections::HashMap;\n\nuse crate::openai::OpenAIProvider;\n\n/// An OpenAI-compatible provider.\n///\n/// Wraps [`OpenAIProvider`] and tags it with a logical name + overrides.\n/// The `LLMProvider` impl is delegated directly to the inner OAI provider.\npub struct OpenAICompatProvider {\n name: String,\n inner: OpenAIProvider,\n pub field_overrides: FieldOverrides,\n}\n\nimpl OpenAICompatProvider {\n pub fn new(\n name: impl Into<String>,\n api_key: Option<String>,\n base_url: impl Into<String>,\n models: Vec<String>,\n extra_headers: HashMap<String, String>,\n field_overrides: FieldOverrides,\n ) -> Self {\n Self {\n name: name.into(),\n inner: OpenAIProvider::new(api_key, Some(base_url.into()), extra_headers),\n field_overrides,\n }\n }\n\n}\n\n// Implement LLMProvider by delegation.\n#[async_trait::async_trait]\nimpl gateway_core::provider::LLMProvider for OpenAICompatProvider {\n fn name(&self) -> &str {\n &self.name\n }\n\n async fn chat_completion(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<gateway_core::types::ChatCompletionResponse, gateway_core::error::GatewayError> {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion(request).await\n }\n\n async fn chat_completion_stream(\n &self,\n mut request: gateway_core::types::ChatCompletionRequest,\n ) -> Result<\n futures::stream::BoxStream<\n 'static,\n Result<gateway_core::types::ChatCompletionChunk, gateway_core::error::GatewayError>,\n >,\n gateway_core::error::GatewayError,\n > {\n apply_field_overrides(&self.field_overrides, &mut request);\n self.inner.chat_completion_stream(request).await\n }\n}\n\n/// Apply [`FieldOverrides`] to a request before sending.\nfn apply_field_overrides(\n overrides: &FieldOverrides,\n request: &mut gateway_core::types::ChatCompletionRequest,\n) {\n if overrides.emit_reasoning_content {\n // Marker so future handlers know to surface reasoning_content.\n request\n .extra\n .insert(\"emit_reasoning_content\".into(), serde_json::json!(true));\n }\n if let Some(kwargs) = &overrides.chat_template_kwargs {\n request\n .extra\n .insert(\"chat_template_kwargs\".into(), kwargs.clone());\n }\n if !overrides.stream_field_renames.is_empty() {\n request.extra.insert(\n \"stream_field_renames\".into(),\n serde_json::to_value(&overrides.stream_field_renames).unwrap_or_default(),\n );\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_field_overrides_default() {\n let o = FieldOverrides::default();\n assert!(!o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_none());\n assert!(o.stream_field_renames.is_empty());\n }\n\n #[test]\n fn test_field_overrides_serde() {\n let json = r#\"{\n \"emit_reasoning_content\": true,\n \"chat_template_kwargs\": {\"thinking\": {\"type\": \"enabled\"}},\n \"stream_field_renames\": {\"delta\": \"content\"}\n }\"#;\n let o: FieldOverrides = serde_json::from_str(json).unwrap();\n assert!(o.emit_reasoning_content);\n assert!(o.chat_template_kwargs.is_some());\n assert_eq!(o.stream_field_renames[\"delta\"], \"content\");\n }\n\n #[test]\n fn test_compat_provider_name() {\n use gateway_core::provider::LLMProvider;\n let p = OpenAICompatProvider::new(\n \"deepseek\",\n Some(\"test-key\".into()),\n \"https://api.deepseek.com/v1\",\n vec![\"deepseek-chat\".into()],\n HashMap::new(),\n FieldOverrides::default(),\n );\n assert_eq!(p.name(), \"deepseek\");\n }\n\n #[test]\n fn test_apply_overrides() {\n let overrides = FieldOverrides {\n emit_reasoning_content: true,\n chat_template_kwargs: Some(serde_json::json!({\"thinking\": true})),\n stream_field_renames: HashMap::new(),\n };\n let mut req = gateway_core::types::ChatCompletionRequest {\n model: \"m\".into(),\n messages: vec![],\n temperature: None,\n max_tokens: None,\n top_p: None,\n stream: false,\n stop: None,\n presence_penalty: None,\n frequency_penalty: None,\n user: None,\n response_format: None,\n extra: HashMap::new(),\n };\n apply_field_overrides(&overrides, &mut req);\n assert_eq!(req.extra[\"emit_reasoning_content\"], serde_json::json!(true));\n assert!(req.extra.contains_key(\"chat_template_kwargs\"));\n }\n}\n"}}}}