mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
refactor(core): use string for DeepSeek model
This commit is contained in:
parent
85e70ea374
commit
0f636c5db1
3 changed files with 11 additions and 239 deletions
|
|
@ -12,11 +12,10 @@ use crate::ocr::types::{
|
|||
PreparedOcrRequest,
|
||||
};
|
||||
use crate::params::OpaqueParams;
|
||||
use crate::providers::model::{ModelNamespace, ProviderModel, RoutedModel};
|
||||
use crate::url_utils::ApiUrl;
|
||||
|
||||
const DEFAULT_API_BASE: &str = "https://aiplatform.googleapis.com";
|
||||
const MODEL_NAMESPACE: &str = "deepseek-ai";
|
||||
const MODEL_PREFIX: &str = "deepseek-ai/";
|
||||
const DEFAULT_LOCATION: &str = "us-central1";
|
||||
const DEEPSEEK_OCR_PARAMS: &[&str] = &["stream", "temperature", "max_tokens", "top_p", "n", "stop"];
|
||||
|
||||
|
|
@ -24,7 +23,7 @@ pub(crate) type DeepSeekOcrParams = OpaqueParams;
|
|||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct DeepSeekOcrRequest {
|
||||
pub model: ProviderModel<DeepSeekAi>,
|
||||
pub model: String,
|
||||
pub messages: Vec<DeepSeekOcrMessage>,
|
||||
#[serde(flatten)]
|
||||
pub params: OpaqueParams,
|
||||
|
|
@ -87,13 +86,6 @@ struct DeepSeekPage {
|
|||
dimensions: Option<OcrPageDimensions>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct DeepSeekAi;
|
||||
|
||||
impl ModelNamespace for DeepSeekAi {
|
||||
const NAME: &'static str = MODEL_NAMESPACE;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct VertexAIDeepSeekOCRConfig;
|
||||
|
||||
|
|
@ -367,12 +359,14 @@ fn response_field(field: &str) -> crate::ocr::Error {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provider_model(model: &str) -> Result<ProviderModel<DeepSeekAi>, crate::ocr::Error> {
|
||||
RoutedModel::new(model)
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.map_err(|_| crate::ocr::Error::RequestField {
|
||||
pub(crate) fn provider_model(model: &str) -> Result<String, crate::ocr::Error> {
|
||||
let local_model = model.trim_start_matches(MODEL_PREFIX);
|
||||
if local_model.is_empty() {
|
||||
return Err(crate::ocr::Error::RequestField {
|
||||
path: "model".into(),
|
||||
})
|
||||
});
|
||||
}
|
||||
Ok(format!("{MODEL_PREFIX}{local_model}"))
|
||||
}
|
||||
|
||||
impl VertexAIDeepSeekOCRConfig {
|
||||
|
|
@ -443,13 +437,11 @@ mod tests {
|
|||
#[test]
|
||||
fn config_owns_model_namespace_and_endpoint() {
|
||||
assert_eq!(
|
||||
provider_model("deepseek-ocr-maas").unwrap().as_str(),
|
||||
provider_model("deepseek-ocr-maas").unwrap(),
|
||||
"deepseek-ai/deepseek-ocr-maas"
|
||||
);
|
||||
assert_eq!(
|
||||
provider_model("deepseek-ai/deepseek-ocr-maas")
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
provider_model("deepseek-ai/deepseek-ocr-maas").unwrap(),
|
||||
"deepseek-ai/deepseek-ocr-maas"
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -2,5 +2,4 @@ pub mod anthropic;
|
|||
pub mod azure_ai;
|
||||
pub mod bedrock;
|
||||
pub mod custom_llm_provider;
|
||||
pub(crate) mod model;
|
||||
pub mod openai;
|
||||
|
|
|
|||
|
|
@ -1,219 +0,0 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
|
||||
pub(crate) enum ModelNameError {
|
||||
#[error("model name cannot be empty")]
|
||||
EmptyModel,
|
||||
#[error("model namespace must be one non-empty path segment: {0}")]
|
||||
InvalidNamespace(&'static str),
|
||||
}
|
||||
|
||||
pub(crate) trait ModelNamespace {
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct RoutedModel<'a>(&'a str);
|
||||
|
||||
impl<'a> RoutedModel<'a> {
|
||||
pub(crate) fn new(value: &'a str) -> Result<Self, ModelNameError> {
|
||||
if value.is_empty() {
|
||||
return Err(ModelNameError::EmptyModel);
|
||||
}
|
||||
Ok(Self(value))
|
||||
}
|
||||
|
||||
pub(crate) fn into_provider<N: ModelNamespace>(
|
||||
self,
|
||||
) -> Result<ProviderModel<N>, ModelNameError> {
|
||||
let namespace = N::NAME;
|
||||
if namespace.is_empty() || namespace.contains('/') {
|
||||
return Err(ModelNameError::InvalidNamespace(namespace));
|
||||
}
|
||||
let prefix = format!("{namespace}/");
|
||||
let local_model = self.0.trim_start_matches(prefix.as_str());
|
||||
if local_model.is_empty() {
|
||||
return Err(ModelNameError::EmptyModel);
|
||||
}
|
||||
Ok(ProviderModel {
|
||||
value: format!("{prefix}{local_model}"),
|
||||
namespace: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct ProviderModel<N> {
|
||||
value: String,
|
||||
namespace: PhantomData<N>,
|
||||
}
|
||||
|
||||
impl<N> ProviderModel<N> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> Serialize for ProviderModel<N> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.value.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, N: ModelNamespace> Deserialize<'de> for ProviderModel<N> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?;
|
||||
RoutedModel::new(&value)
|
||||
.and_then(RoutedModel::into_provider::<N>)
|
||||
.map_err(<D::Error as serde::de::Error>::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct DeepSeekAi;
|
||||
|
||||
impl ModelNamespace for DeepSeekAi {
|
||||
const NAME: &'static str = "deepseek-ai";
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct FalAi;
|
||||
|
||||
impl ModelNamespace for FalAi {
|
||||
const NAME: &'static str = "fal-ai";
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualifies_a_bare_model() {
|
||||
let model = RoutedModel::new("deepseek-ocr-maas")
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(model.as_str(), "deepseek-ai/deepseek-ocr-maas");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_an_already_qualified_model() {
|
||||
let model = RoutedModel::new("deepseek-ai/deepseek-ocr-maas")
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(model.as_str(), "deepseek-ai/deepseek-ocr-maas");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collapses_repeated_owned_namespaces() {
|
||||
let model = RoutedModel::new("deepseek-ai/deepseek-ai/deepseek-ai/deepseek-ocr-maas")
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(model.as_str(), "deepseek-ai/deepseek-ocr-maas");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_the_namespace_as_a_complete_segment() {
|
||||
let model = RoutedModel::new("deepseek-ai-v2/model")
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(model.as_str(), "deepseek-ai/deepseek-ai-v2/model");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_nested_provider_model_paths() {
|
||||
let model = RoutedModel::new("publishers/vendor/models/model-v1")
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.as_str(),
|
||||
"deepseek-ai/publishers/vendor/models/model-v1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_markers_select_different_wire_names() {
|
||||
let routed = RoutedModel::new("model-v1").unwrap();
|
||||
let deepseek = routed.into_provider::<DeepSeekAi>().unwrap();
|
||||
let fal = routed.into_provider::<FalAi>().unwrap();
|
||||
|
||||
assert_eq!(deepseek.as_str(), "deepseek-ai/model-v1");
|
||||
assert_eq!(fal.as_str(), "fal-ai/model-v1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_empty_routed_models() {
|
||||
assert_eq!(RoutedModel::new(""), Err(ModelNameError::EmptyModel));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_a_namespace_without_a_model() {
|
||||
let result =
|
||||
RoutedModel::new("deepseek-ai/").and_then(RoutedModel::into_provider::<DeepSeekAi>);
|
||||
|
||||
assert_eq!(result, Err(ModelNameError::EmptyModel));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_namespace_markers() {
|
||||
struct Empty;
|
||||
impl ModelNamespace for Empty {
|
||||
const NAME: &'static str = "";
|
||||
}
|
||||
struct MultipleSegments;
|
||||
impl ModelNamespace for MultipleSegments {
|
||||
const NAME: &'static str = "one/two";
|
||||
}
|
||||
|
||||
assert!(matches!(
|
||||
RoutedModel::new("model").and_then(RoutedModel::into_provider::<Empty>),
|
||||
Err(ModelNameError::InvalidNamespace(""))
|
||||
));
|
||||
assert!(matches!(
|
||||
RoutedModel::new("model").and_then(RoutedModel::into_provider::<MultipleSegments>),
|
||||
Err(ModelNameError::InvalidNamespace("one/two"))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_models_serialize_as_plain_strings() {
|
||||
let model = RoutedModel::new("deepseek-ocr-maas")
|
||||
.and_then(RoutedModel::into_provider::<DeepSeekAi>)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(model).unwrap(),
|
||||
json!("deepseek-ai/deepseek-ocr-maas")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization_reestablishes_the_namespace_invariant() {
|
||||
let model: ProviderModel<DeepSeekAi> =
|
||||
serde_json::from_value(json!("deepseek-ai/deepseek-ai/model-v1")).unwrap();
|
||||
|
||||
assert_eq!(model.as_str(), "deepseek-ai/model-v1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization_rejects_missing_model_names() {
|
||||
let result = serde_json::from_value::<ProviderModel<DeepSeekAi>>(json!("deepseek-ai/"));
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue