async_dashscope/operation/
validate.rs

1use crate::operation::request::RequestTrait;
2use crate::{
3    error::{DashScopeError, Result},
4    operation::{common::Parameters, embeddings::EmbeddingsParameters},
5};
6
7/// Defines the validation strategy for a given model.
8///
9/// This enum replaces the previous trait-based approach to resolve `dyn` compatibility issues.
10/// It allows for static dispatch, which is more performant and avoids the complexities of
11/// object safety.
12pub enum ModelValidator {
13    /// The default validation, which performs no special checks.
14    Default,
15    /// Validation specific to the `deepseek-r1` model.
16    NotSupportResultFormatText,
17    /// validation enable_thinking
18    NotSupportEnableThinking,
19    NotSupportToolCall,
20    NotSupportJsonOutput,
21    // dimensions 不匹配
22    DimensionNotMatch,
23    OnlyStreaming,
24}
25
26pub trait Validator<T> {
27    /// 验证请求的参数
28    #[allow(clippy::result_large_err)]
29    fn validate<R: RequestTrait<P = T> + ?Sized>(&self, params: &R) -> Result<()>;
30}
31
32impl Validator<EmbeddingsParameters> for ModelValidator {
33    fn validate<R: RequestTrait<P = EmbeddingsParameters> + ?Sized>(
34        &self,
35        params: &R,
36    ) -> Result<()> {
37        match self {
38            ModelValidator::DimensionNotMatch => {
39                // text-embedding-v4 向量维度 只能是以下值: 2,048、1,536、1,024(默认)、768、512、256、128、64
40                // text-embedding-v3 向量维度 只能是以下值: 1,024(默认)、768、512、256、128或64
41                // text-embedding-v2 向量维度 只能是1,536
42                // text-embedding-v1 向量维度 只能是1,536
43
44                if let Some(p) = params.parameters() {
45                    let valid_dimensions = match params.model() {
46                        "text-embedding-v1" | "text-embedding-v2" => {
47                            vec![1536]
48                        }
49                        "text-embedding-v3" => {
50                            vec![1024, 768, 512, 256, 128, 64]
51                        }
52                        "text-embedding-v4" => {
53                            vec![2048, 1536, 1024, 768, 512, 256, 128, 64]
54                        }
55                        _ => vec![], // 未知模型不验证
56                    };
57                    if let Some(dimension) = p.dimension {
58                        if !valid_dimensions.contains(&dimension) {
59                            return Err(DashScopeError::InvalidArgument(format!(
60                                "Invalid dimension: {} for model: {}",
61                                dimension,
62                                params.model()
63                            )));
64                        }
65                    }
66                }
67                Ok(())
68            }
69            _ => Ok(()),
70        }
71    }
72}
73impl Validator<Parameters> for ModelValidator {
74    fn validate<R: RequestTrait<P = Parameters> + ?Sized>(&self, params: &R) -> Result<()> {
75        match self {
76            ModelValidator::Default => {
77                // No specific validation rules for the default case.
78                Ok(())
79            }
80            ModelValidator::NotSupportResultFormatText => {
81                // The deepseek-r1 model does not support `result_format: "text"`.
82                if let Some(p) = params.parameters() {
83                    if let Some(format) = &p.result_format {
84                        if format == "text" {
85                            return Err(DashScopeError::InvalidArgument(
86                                "deepseek-r1 does not support result_format = text".into(),
87                            ));
88                        }
89                    }
90                }
91                Ok(())
92            }
93            ModelValidator::NotSupportEnableThinking => {
94                if let Some(p) = params.parameters() {
95                    if let Some(thinking) = p.enable_thinking {
96                        if thinking {
97                            return Err(DashScopeError::InvalidArgument(
98                                "The model does not support enable_thinking = true".into(),
99                            ));
100                        }
101                    }
102                }
103                Ok(())
104            }
105            ModelValidator::NotSupportJsonOutput => {
106                if let Some(p) = params.parameters() {
107                    if let Some(response_format) = p.response_format.as_ref() {
108                        if response_format.type_ == "json_object" {
109                            return Err(DashScopeError::InvalidArgument(
110                                "The model does not support response_format=json_object".into(),
111                            ));
112                        }
113                    }
114                }
115                Ok(())
116            }
117
118            ModelValidator::NotSupportToolCall => {
119                if let Some(p) = params.parameters() {
120                    if p.tools.is_some() {
121                        return Err(DashScopeError::InvalidArgument(
122                            "The model does not support tool call".into(),
123                        ));
124                    }
125                }
126                Ok(())
127            }
128
129            ModelValidator::OnlyStreaming => {
130                if let Some(p) = params.parameters() {
131                    #[allow(deprecated)]
132                    if p.incremental_output == Some(false) {
133                        return Err(DashScopeError::InvalidArgument(
134                            "The model does not support streaming".into(),
135                        ));
136                    }
137                }
138
139                Ok(())
140            }
141
142            _ => Ok(()),
143        }
144    }
145}
146
147/// Selects the appropriate validator for the given model name.
148///
149/// # Arguments
150///
151/// * `model` - The name of the model as a string slice.
152///
153/// # Returns
154///
155/// A `ModelValidator` enum variant corresponding to the required validation strategy.
156pub(crate) fn check_model_parameters(model: &str) -> Vec<ModelValidator> {
157    match model {
158        "deepseek-r1" => vec![
159            ModelValidator::NotSupportResultFormatText,
160            ModelValidator::NotSupportJsonOutput,
161        ],
162        "qwen-vl" | "qwen-audio" => vec![ModelValidator::NotSupportToolCall],
163        "Moonshot-Kimi-K2-Instruct" => vec![
164            ModelValidator::NotSupportEnableThinking,
165            ModelValidator::NotSupportResultFormatText,
166            ModelValidator::NotSupportJsonOutput,
167            ModelValidator::NotSupportToolCall,
168        ],
169        "text-embedding-v4" | "text-embedding-v3" | "text-embedding-v2" | "text-embedding-v1" => {
170            vec![ModelValidator::DimensionNotMatch]
171        }
172        "qwen-mt-image" => {
173            vec![ModelValidator::Default]
174        }
175        "glm-4.6" | "glm-4.5" | "glm-4.5-air" => {
176            vec![ModelValidator::OnlyStreaming]
177        }
178        _ => vec![ModelValidator::Default],
179    }
180}