zai_rs/model/text_rerank/
request.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum RerankModel {
7 #[default]
9 Rerank,
10}
11
12#[derive(Clone, Serialize)]
14pub struct RerankBody {
15 pub model: RerankModel,
17
18 pub query: String,
20
21 pub documents: Vec<String>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
27 pub top_n: Option<usize>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub return_documents: Option<bool>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub return_raw_scores: Option<bool>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub request_id: Option<String>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub user_id: Option<String>,
44}
45
46impl std::fmt::Debug for RerankBody {
47 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 formatter
49 .debug_struct("RerankBody")
50 .field("model", &self.model)
51 .field("query", &"[REDACTED]")
52 .field("documents_len", &self.documents.len())
53 .field("top_n", &self.top_n)
54 .field("return_documents", &self.return_documents)
55 .field("return_raw_scores", &self.return_raw_scores)
56 .field(
57 "request_id",
58 &self.request_id.as_ref().map(|_| "[REDACTED]"),
59 )
60 .field("user_id", &self.user_id.as_ref().map(|_| "[REDACTED]"))
61 .finish()
62 }
63}
64
65impl RerankBody {
66 pub fn new(model: RerankModel, query: impl Into<String>, documents: Vec<String>) -> Self {
68 Self {
69 model,
70 query: query.into(),
71 documents,
72 top_n: None,
73 return_documents: None,
74 return_raw_scores: None,
75 request_id: None,
76 user_id: None,
77 }
78 }
79
80 pub fn with_top_n(mut self, n: usize) -> Self {
82 self.top_n = Some(n);
83 self
84 }
85 pub fn with_return_documents(mut self, v: bool) -> Self {
87 self.return_documents = Some(v);
88 self
89 }
90 pub fn with_return_raw_scores(mut self, v: bool) -> Self {
92 self.return_raw_scores = Some(v);
93 self
94 }
95 pub fn with_request_id(mut self, v: impl Into<String>) -> Self {
97 self.request_id = Some(v.into());
98 self
99 }
100 pub fn with_user_id(mut self, v: impl Into<String>) -> Self {
102 self.user_id = Some(v.into());
103 self
104 }
105
106 pub fn validate_constraints(&self) -> crate::ZaiResult<()> {
108 if self.query.trim().is_empty() {
109 return Err(crate::client::validation::invalid(
110 "query must not be empty",
111 ));
112 }
113 if self.query.chars().count() > 4096 {
114 return Err(crate::client::validation::invalid(
115 "query length exceeds 4096 characters",
116 ));
117 }
118 if self.documents.is_empty() {
119 return Err(crate::client::validation::invalid(
120 "documents must not be empty",
121 ));
122 }
123 if self.documents.len() > 128 {
124 return Err(crate::client::validation::invalid(
125 "documents length exceeds 128",
126 ));
127 }
128 for (i, d) in self.documents.iter().enumerate() {
129 if d.trim().is_empty() {
130 return Err(crate::client::validation::invalid(format!(
131 "document at index {i} must not be empty"
132 )));
133 }
134 if d.chars().count() > 4096 {
135 return Err(crate::client::validation::invalid(format!(
136 "document at index {i} exceeds 4096 characters"
137 )));
138 }
139 }
140 if let Some(n) = self.top_n
141 && n > self.documents.len()
142 {
143 return Err(crate::client::validation::invalid(
144 "top_n cannot exceed documents length",
145 ));
146 }
147 if let Some(request_id) = self.request_id.as_deref()
148 && !(6..=64).contains(&request_id.chars().count())
149 {
150 return Err(crate::client::validation::invalid(
151 "request_id must contain between 6 and 64 characters",
152 ));
153 }
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn validation_rejects_blank_values_and_accepts_zero_top_n() {
164 assert!(
165 RerankBody::new(RerankModel::Rerank, " ", vec!["doc".into()])
166 .validate_constraints()
167 .is_err()
168 );
169 assert!(
170 RerankBody::new(RerankModel::Rerank, "query", vec![" ".into()])
171 .validate_constraints()
172 .is_err()
173 );
174 assert!(
175 RerankBody::new(RerankModel::Rerank, "query", vec!["doc".into()])
176 .with_top_n(0)
177 .validate_constraints()
178 .is_ok()
179 );
180 }
181
182 #[test]
183 fn debug_redacts_query_documents_and_identifiers() {
184 let body = RerankBody::new(
185 RerankModel::Rerank,
186 "private query",
187 vec!["private document".to_owned()],
188 )
189 .with_request_id("private-request")
190 .with_user_id("private-user");
191 let debug = format!("{body:?}");
192 for secret in [
193 "private query",
194 "private document",
195 "private-request",
196 "private-user",
197 ] {
198 assert!(!debug.contains(secret));
199 }
200 }
201}