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