1use crate::prelude::*;
2use utoipa::ToSchema;
3
4#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Type, ToSchema)]
5#[sqlx(type_name = "reasoning_effort_level", rename_all = "snake_case")]
6#[serde(rename_all = "snake_case")]
7pub enum ReasoningEffortLevel {
8 None,
9 Minimal,
10 Low,
11 Medium,
12 High,
13 Xhigh,
14}
15
16#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Type, ToSchema)]
17#[sqlx(type_name = "verbosity_level", rename_all = "snake_case")]
18#[serde(rename_all = "snake_case")]
19pub enum VerbosityLevel {
20 Low,
21 Medium,
22 High,
23}
24
25#[derive(Clone, PartialEq, Deserialize, Serialize, ToSchema)]
26pub struct ChatbotConfiguration {
27 pub id: Uuid,
28 pub created_at: DateTime<Utc>,
29 pub updated_at: DateTime<Utc>,
30 pub deleted_at: Option<DateTime<Utc>>,
31 pub course_id: Option<Uuid>,
32 pub enabled_to_students: bool,
33 pub chatbot_name: String,
34 pub model_id: Uuid,
35 pub prompt: String,
36 pub initial_message: String,
37 pub weekly_tokens_per_user: i32,
38 pub daily_tokens_per_user: i32,
39 pub temperature: f32,
40 pub top_p: f32,
41 pub frequency_penalty: f32,
42 pub presence_penalty: f32,
43 pub max_output_tokens: i32,
44 pub verbosity: VerbosityLevel,
45 pub reasoning_effort: ReasoningEffortLevel,
46 pub use_azure_search: bool,
47 pub maintain_azure_search_index: bool,
48 pub hide_citations: bool,
49 pub use_semantic_reranking: bool,
50 pub use_tools: bool,
51 pub default_chatbot: bool,
52 pub suggest_next_messages: bool,
53 pub initial_suggested_messages: Option<Vec<String>>,
54}
55
56impl Default for ChatbotConfiguration {
57 fn default() -> Self {
58 Self {
59 id: Uuid::nil(),
60 created_at: Default::default(),
61 updated_at: Default::default(),
62 deleted_at: None,
63 course_id: Default::default(),
64 enabled_to_students: false,
65 chatbot_name: Default::default(),
66 model_id: Uuid::nil(),
67 prompt: Default::default(),
68 initial_message: Default::default(),
69 weekly_tokens_per_user: 20000 * 5,
70 daily_tokens_per_user: 20000,
71 max_output_tokens: 20_000,
72 temperature: 0.7,
73 top_p: 1.0,
74 frequency_penalty: 0.0,
75 presence_penalty: 0.0,
76 reasoning_effort: ReasoningEffortLevel::Minimal,
77 verbosity: VerbosityLevel::Medium,
78 use_azure_search: false,
79 maintain_azure_search_index: false,
80 hide_citations: false,
81 use_semantic_reranking: false,
82 use_tools: false,
83 default_chatbot: false,
84 suggest_next_messages: false,
85 initial_suggested_messages: None,
86 }
87 }
88}
89
90#[derive(Clone, PartialEq, Deserialize, Serialize, Debug, ToSchema)]
91
92pub struct NewChatbotConf {
93 pub course_id: Option<Uuid>,
94 pub enabled_to_students: bool,
95 pub chatbot_name: String,
96 pub model_id: Uuid,
97 pub prompt: String,
98 pub initial_message: String,
99 pub weekly_tokens_per_user: i32,
100 pub daily_tokens_per_user: i32,
101 pub temperature: f32,
102 pub top_p: f32,
103 pub frequency_penalty: f32,
104 pub presence_penalty: f32,
105 pub max_output_tokens: i32,
106 pub verbosity: VerbosityLevel,
107 pub reasoning_effort: ReasoningEffortLevel,
108 pub use_azure_search: bool,
109 pub maintain_azure_search_index: bool,
110 pub hide_citations: bool,
111 pub use_semantic_reranking: bool,
112 pub use_tools: bool,
113 pub default_chatbot: bool,
114 pub chatbotconf_id: Option<Uuid>,
115 pub suggest_next_messages: bool,
116 pub initial_suggested_messages: Option<Vec<String>>,
117}
118
119impl Default for NewChatbotConf {
120 fn default() -> Self {
121 let chatbot_conf: ChatbotConfiguration = ChatbotConfiguration::default();
122 Self {
123 course_id: chatbot_conf.course_id,
124 enabled_to_students: chatbot_conf.enabled_to_students,
125 chatbot_name: chatbot_conf.chatbot_name,
126 model_id: chatbot_conf.model_id,
127 prompt: chatbot_conf.prompt,
128 initial_message: chatbot_conf.initial_message,
129 weekly_tokens_per_user: chatbot_conf.weekly_tokens_per_user,
130 daily_tokens_per_user: chatbot_conf.daily_tokens_per_user,
131 temperature: chatbot_conf.temperature,
132 top_p: chatbot_conf.top_p,
133 frequency_penalty: chatbot_conf.frequency_penalty,
134 presence_penalty: chatbot_conf.presence_penalty,
135 max_output_tokens: chatbot_conf.max_output_tokens,
136 verbosity: chatbot_conf.verbosity,
137 reasoning_effort: chatbot_conf.reasoning_effort,
138 use_azure_search: chatbot_conf.use_azure_search,
139 maintain_azure_search_index: chatbot_conf.maintain_azure_search_index,
140 hide_citations: chatbot_conf.hide_citations,
141 use_semantic_reranking: chatbot_conf.use_semantic_reranking,
142 use_tools: chatbot_conf.use_tools,
143 default_chatbot: chatbot_conf.default_chatbot,
144 chatbotconf_id: None,
145 suggest_next_messages: chatbot_conf.suggest_next_messages,
146 initial_suggested_messages: chatbot_conf.initial_suggested_messages,
147 }
148 }
149}
150
151const MIN_MAX_OUTPUT_TOKENS: i32 = 10_000;
155
156fn validate_max_output_tokens(input: &NewChatbotConf) -> ModelResult<()> {
158 if input.max_output_tokens < MIN_MAX_OUTPUT_TOKENS {
159 return Err(model_err!(
160 PreconditionFailed,
161 format!("max_output_tokens must be at least {MIN_MAX_OUTPUT_TOKENS}.")
162 ));
163 }
164 Ok(())
165}
166
167pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<ChatbotConfiguration> {
168 let res = sqlx::query_as!(
169 ChatbotConfiguration,
170 r#"
171SELECT *
172FROM chatbot_configurations
173WHERE id = $1
174AND deleted_at IS NULL
175 "#,
176 id
177 )
178 .fetch_one(conn)
179 .await?;
180 Ok(res)
181}
182
183pub async fn insert(
184 conn: &mut PgConnection,
185 pkey_policy: PKeyPolicy<Uuid>,
186 input: NewChatbotConf,
187) -> ModelResult<ChatbotConfiguration> {
188 validate_max_output_tokens(&input)?;
189 let maintain_azure_search_index = input.use_azure_search;
190 let res = sqlx::query_as!(
191 ChatbotConfiguration,
192 r#"
193INSERT INTO chatbot_configurations (
194 id,
195 course_id,
196 enabled_to_students,
197 chatbot_name,
198 model_id,
199 prompt,
200 initial_message,
201 weekly_tokens_per_user,
202 daily_tokens_per_user,
203 temperature,
204 top_p,
205 hide_citations,
206 frequency_penalty,
207 presence_penalty,
208 max_output_tokens,
209 verbosity,
210 reasoning_effort,
211 use_azure_search,
212 use_tools,
213 maintain_azure_search_index,
214 default_chatbot,
215 suggest_next_messages,
216 initial_suggested_messages
217 )
218VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)
219RETURNING *
220 "#,
221 pkey_policy.into_uuid(),
222 input.course_id,
223 input.enabled_to_students,
224 input.chatbot_name,
225 input.model_id,
226 input.prompt,
227 input.initial_message,
228 input.weekly_tokens_per_user,
229 input.daily_tokens_per_user,
230 input.temperature,
231 input.top_p,
232 input.hide_citations,
233 input.frequency_penalty,
234 input.presence_penalty,
235 input.max_output_tokens,
236 input.verbosity as VerbosityLevel,
237 input.reasoning_effort as ReasoningEffortLevel,
238 input.use_azure_search,
239 input.use_tools,
240 maintain_azure_search_index,
241 input.default_chatbot,
242 input.suggest_next_messages,
243 input.initial_suggested_messages.as_deref(),
244 )
245 .fetch_one(conn)
246 .await?;
247 Ok(res)
248}
249
250pub async fn edit(
251 conn: &mut PgConnection,
252 input: NewChatbotConf,
253 chatbot_configuration_id: Uuid,
254) -> ModelResult<ChatbotConfiguration> {
255 validate_max_output_tokens(&input)?;
256 let res = sqlx::query_as!(
257 ChatbotConfiguration,
258 r#"
259UPDATE chatbot_configurations
260SET
261 enabled_to_students = $1,
262 chatbot_name = $2,
263 prompt = $3,
264 initial_message = $4,
265 weekly_tokens_per_user = $5,
266 daily_tokens_per_user = $6,
267 temperature = $7,
268 top_p = $8,
269 frequency_penalty = $9,
270 presence_penalty = $10,
271 max_output_tokens = $11,
272 use_azure_search = $12,
273 maintain_azure_search_index = $13,
274 hide_citations = $14,
275 use_semantic_reranking = $15,
276 default_chatbot = $16,
277 model_id = $17,
278 verbosity = $18,
279 reasoning_effort = $19,
280 use_tools = $20,
281 suggest_next_messages = $21,
282 initial_suggested_messages = $22
283WHERE id = $23
284 AND deleted_at IS NULL
285RETURNING *
286"#,
287 input.enabled_to_students,
288 input.chatbot_name,
289 input.prompt,
290 input.initial_message,
291 input.weekly_tokens_per_user,
292 input.daily_tokens_per_user,
293 input.temperature,
294 input.top_p,
295 input.frequency_penalty,
296 input.presence_penalty,
297 input.max_output_tokens,
298 input.use_azure_search,
299 input.maintain_azure_search_index,
300 input.hide_citations,
301 input.use_semantic_reranking,
302 input.default_chatbot,
303 input.model_id,
304 input.verbosity as VerbosityLevel,
305 input.reasoning_effort as ReasoningEffortLevel,
306 input.use_tools,
307 input.suggest_next_messages,
308 input.initial_suggested_messages.as_deref(),
309 chatbot_configuration_id,
310 )
311 .fetch_one(conn)
312 .await?;
313 Ok(res)
314}
315
316pub async fn delete(conn: &mut PgConnection, chatbot_configuration_id: Uuid) -> ModelResult<()> {
317 sqlx::query!(
318 r#"
319UPDATE chatbot_configurations
320SET deleted_at = now()
321WHERE id = $1
322AND deleted_at IS NULL
323 "#,
324 chatbot_configuration_id
325 )
326 .execute(conn)
327 .await?;
328 Ok(())
329}
330
331pub async fn get_for_course(
332 conn: &mut PgConnection,
333 course_id: Uuid,
334) -> ModelResult<Vec<ChatbotConfiguration>> {
335 let res = sqlx::query_as!(
336 ChatbotConfiguration,
337 r#"
338SELECT *
339FROM chatbot_configurations
340WHERE course_id = $1
341AND deleted_at IS NULL
342"#,
343 course_id
344 )
345 .fetch_all(conn)
346 .await?;
347 Ok(res)
348}
349
350pub async fn get_enabled_nondefault_for_course(
351 conn: &mut PgConnection,
352 course_id: Uuid,
353) -> ModelResult<Vec<ChatbotConfiguration>> {
354 let res = sqlx::query_as!(
355 ChatbotConfiguration,
356 r#"
357SELECT *
358FROM chatbot_configurations
359WHERE course_id = $1
360AND default_chatbot IS false
361AND enabled_to_students IS true
362AND deleted_at IS NULL
363"#,
364 course_id
365 )
366 .fetch_all(conn)
367 .await?;
368 Ok(res)
369}
370
371pub async fn get_for_azure_search_maintenance(
372 conn: &mut PgConnection,
373) -> ModelResult<Vec<ChatbotConfiguration>> {
374 let res = sqlx::query_as!(
375 ChatbotConfiguration,
376 r#"
377SELECT *
378FROM chatbot_configurations
379WHERE maintain_azure_search_index = true
380AND deleted_at IS NULL
381"#,
382 )
383 .fetch_all(conn)
384 .await?;
385 Ok(res)
386}
387
388pub async fn remove_default_chatbot_from_course(
389 conn: &mut PgConnection,
390 course_id: Uuid,
391) -> ModelResult<()> {
392 sqlx::query!(
393 r#"
394UPDATE chatbot_configurations
395SET default_chatbot = false
396WHERE course_id = $1
397AND default_chatbot = true
398AND deleted_at IS NULL
399"#,
400 course_id,
401 )
402 .execute(conn)
403 .await?;
404 Ok(())
405}
406
407pub async fn set_default_chatbot_for_course(
408 conn: &mut PgConnection,
409 chatbot_configuration_id: Uuid,
410) -> ModelResult<ChatbotConfiguration> {
411 let res = sqlx::query_as!(
412 ChatbotConfiguration,
413 r#"
414UPDATE chatbot_configurations
415SET default_chatbot = TRUE
416WHERE id = $1
417 AND deleted_at IS NULL
418RETURNING *
419"#,
420 chatbot_configuration_id,
421 )
422 .fetch_one(conn)
423 .await?;
424 Ok(res)
425}
426
427pub async fn get_all_chatbots(conn: &mut PgConnection) -> ModelResult<Vec<ChatbotConfiguration>> {
428 let res = sqlx::query_as!(
429 ChatbotConfiguration,
430 r#"
431 SELECT *
432 FROM chatbot_configurations
433 WHERE deleted_at IS NULL
434 "#,
435 )
436 .fetch_all(conn)
437 .await?;
438 Ok(res)
439}