253 lines
6.0 KiB
Rust
253 lines
6.0 KiB
Rust
//! Provider abstractions for vendor-specific API transformations.
|
|
|
|
use std::{collections::HashMap, sync::Arc};
|
|
|
|
use reqwest::RequestBuilder;
|
|
use serde_json::Value;
|
|
use thiserror::Error;
|
|
|
|
use crate::core::{model_type::Endpoint, ProviderType};
|
|
|
|
const SGLANG_FIELDS: &[&str] = &[
|
|
"request_id",
|
|
"priority",
|
|
"top_k",
|
|
"min_p",
|
|
"min_tokens",
|
|
"regex",
|
|
"ebnf",
|
|
"json_schema",
|
|
"stop_token_ids",
|
|
"no_stop_trim",
|
|
"ignore_eos",
|
|
"continue_final_message",
|
|
"skip_special_tokens",
|
|
"lora_path",
|
|
"session_params",
|
|
"separate_reasoning",
|
|
"stream_reasoning",
|
|
"chat_template",
|
|
"chat_template_kwargs",
|
|
"return_hidden_states",
|
|
"repetition_penalty",
|
|
"sampling_seed",
|
|
"backend_url",
|
|
];
|
|
|
|
fn strip_sglang_fields(payload: &mut Value) {
|
|
if let Some(obj) = payload.as_object_mut() {
|
|
for field in SGLANG_FIELDS {
|
|
obj.remove(*field);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum ProviderError {
|
|
#[error("Unsupported endpoint: {0:?}")]
|
|
UnsupportedEndpoint(Endpoint),
|
|
|
|
#[error("Transform error: {0}")]
|
|
TransformError(String),
|
|
}
|
|
|
|
/// Default `transform_request` strips SGLang fields.
|
|
pub trait Provider: Send + Sync {
|
|
fn provider_type(&self) -> ProviderType;
|
|
|
|
fn transform_request(
|
|
&self,
|
|
payload: &mut Value,
|
|
_endpoint: Endpoint,
|
|
) -> Result<(), ProviderError> {
|
|
strip_sglang_fields(payload);
|
|
Ok(())
|
|
}
|
|
|
|
fn transform_response(
|
|
&self,
|
|
_response: &mut Value,
|
|
_endpoint: Endpoint,
|
|
) -> Result<(), ProviderError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn apply_headers(&self, builder: RequestBuilder) -> RequestBuilder {
|
|
builder
|
|
}
|
|
}
|
|
|
|
pub struct SGLangProvider;
|
|
|
|
impl Provider for SGLangProvider {
|
|
fn provider_type(&self) -> ProviderType {
|
|
ProviderType::OpenAI
|
|
}
|
|
|
|
fn transform_request(
|
|
&self,
|
|
_payload: &mut Value,
|
|
_endpoint: Endpoint,
|
|
) -> Result<(), ProviderError> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct OpenAIProvider;
|
|
|
|
impl Provider for OpenAIProvider {
|
|
fn provider_type(&self) -> ProviderType {
|
|
ProviderType::OpenAI
|
|
}
|
|
}
|
|
|
|
pub struct AnthropicProvider;
|
|
|
|
impl Provider for AnthropicProvider {
|
|
fn provider_type(&self) -> ProviderType {
|
|
ProviderType::Anthropic
|
|
}
|
|
}
|
|
|
|
pub struct XAIProvider;
|
|
|
|
impl Provider for XAIProvider {
|
|
fn provider_type(&self) -> ProviderType {
|
|
ProviderType::XAI
|
|
}
|
|
|
|
fn transform_request(
|
|
&self,
|
|
payload: &mut Value,
|
|
endpoint: Endpoint,
|
|
) -> Result<(), ProviderError> {
|
|
strip_sglang_fields(payload);
|
|
|
|
if endpoint == Endpoint::Responses {
|
|
if let Some(obj) = payload.as_object_mut() {
|
|
Self::transform_responses_input(obj);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl XAIProvider {
|
|
fn transform_responses_input(obj: &mut serde_json::Map<String, Value>) {
|
|
let Some(input_arr) = obj.get_mut("input").and_then(Value::as_array_mut) else {
|
|
return;
|
|
};
|
|
|
|
for item in input_arr.iter_mut().filter_map(Value::as_object_mut) {
|
|
item.remove("id");
|
|
item.remove("status");
|
|
|
|
let Some(content_arr) = item.get_mut("content").and_then(Value::as_array_mut) else {
|
|
continue;
|
|
};
|
|
|
|
for content in content_arr.iter_mut().filter_map(Value::as_object_mut) {
|
|
if content.get("type").and_then(Value::as_str) == Some("output_text") {
|
|
content.insert("type".to_string(), Value::String("input_text".to_string()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct GeminiProvider;
|
|
|
|
impl Provider for GeminiProvider {
|
|
fn provider_type(&self) -> ProviderType {
|
|
ProviderType::Gemini
|
|
}
|
|
|
|
fn transform_request(
|
|
&self,
|
|
payload: &mut Value,
|
|
endpoint: Endpoint,
|
|
) -> Result<(), ProviderError> {
|
|
strip_sglang_fields(payload);
|
|
|
|
if endpoint == Endpoint::Chat {
|
|
if let Some(obj) = payload.as_object_mut() {
|
|
if obj.get("logprobs").and_then(|v| v.as_bool()) == Some(false) {
|
|
obj.remove("logprobs");
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct ProviderRegistry {
|
|
providers: HashMap<ProviderType, Arc<dyn Provider>>,
|
|
default_provider: Arc<dyn Provider>,
|
|
}
|
|
|
|
impl Default for ProviderRegistry {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ProviderRegistry {
|
|
pub fn new() -> Self {
|
|
let mut providers = HashMap::new();
|
|
|
|
providers.insert(
|
|
ProviderType::OpenAI,
|
|
Arc::new(OpenAIProvider) as Arc<dyn Provider>,
|
|
);
|
|
providers.insert(
|
|
ProviderType::XAI,
|
|
Arc::new(XAIProvider) as Arc<dyn Provider>,
|
|
);
|
|
providers.insert(
|
|
ProviderType::Gemini,
|
|
Arc::new(GeminiProvider) as Arc<dyn Provider>,
|
|
);
|
|
providers.insert(
|
|
ProviderType::Anthropic,
|
|
Arc::new(AnthropicProvider) as Arc<dyn Provider>,
|
|
);
|
|
|
|
Self {
|
|
providers,
|
|
default_provider: Arc::new(SGLangProvider),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn get(&self, provider_type: &ProviderType) -> &dyn Provider {
|
|
self.providers
|
|
.get(provider_type)
|
|
.map(|p| p.as_ref())
|
|
.unwrap_or(self.default_provider.as_ref())
|
|
}
|
|
|
|
pub fn get_arc(&self, provider_type: &ProviderType) -> Arc<dyn Provider> {
|
|
self.providers
|
|
.get(provider_type)
|
|
.cloned()
|
|
.unwrap_or_else(|| Arc::clone(&self.default_provider))
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn get_for_model(&self, model_name: &str) -> &dyn Provider {
|
|
match ProviderType::from_model_name(model_name) {
|
|
Some(pt) => self.get(&pt),
|
|
None => self.default_provider.as_ref(),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn default_provider(&self) -> &dyn Provider {
|
|
self.default_provider.as_ref()
|
|
}
|
|
|
|
pub fn default_provider_arc(&self) -> Arc<dyn Provider> {
|
|
Arc::clone(&self.default_provider)
|
|
}
|
|
}
|