Skip to main content

ragrig_gpui/model/
settings.rs

1//! Chat settings shown on the settings page.
2
3/// Chat-model configuration.
4///
5/// This is deliberately smaller than Ragrig's full configuration: it covers
6/// the fields a chat surface exposes directly. Memory, embedding, parsing and
7/// retrieval settings arrive with the M2 controller.
8#[derive(Clone, Debug, PartialEq)]
9pub struct ChatSettings {
10    /// Model identifier, e.g. `deepseek-chat`.
11    pub model: String,
12    /// Sampling temperature in `0..=2`.
13    pub temperature: f64,
14    /// Maximum number of generated tokens.
15    pub max_tokens: usize,
16    /// System prompt sent before the conversation.
17    pub system_prompt: String,
18    /// Whether tokens are streamed into the transcript.
19    pub stream: bool,
20}
21
22impl Default for ChatSettings {
23    fn default() -> Self {
24        Self {
25            model: "deepseek-chat".to_owned(),
26            temperature: 0.2,
27            max_tokens: 2048,
28            system_prompt: "Answer from the indexed corpus and cite sources.".to_owned(),
29            stream: true,
30        }
31    }
32}
33
34impl ChatSettings {
35    /// Models offered by the settings dropdown.
36    ///
37    /// The host may replace this list; it is a convenience for the demo and
38    /// the default settings page.
39    pub const MODELS: &'static [(&'static str, &'static str)] = &[
40        ("deepseek-chat", "DeepSeek Chat"),
41        ("qwen3:8b", "Qwen3 8B (Ollama)"),
42        ("gpt-oss:20b", "GPT-OSS 20B (Ollama)"),
43    ];
44
45    /// Check the values before applying them.
46    pub fn validate(&self) -> Result<(), String> {
47        if self.model.trim().is_empty() {
48            return Err("choose a chat model".to_owned());
49        }
50        if !self.temperature.is_finite() || !(0.0..=2.0).contains(&self.temperature) {
51            return Err("temperature must be between 0 and 2".to_owned());
52        }
53        if self.max_tokens == 0 {
54            return Err("max tokens must be at least 1".to_owned());
55        }
56        Ok(())
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn defaults_are_valid() {
66        assert_eq!(ChatSettings::default().validate(), Ok(()));
67    }
68
69    #[test]
70    fn invalid_values_are_rejected() {
71        let mut settings = ChatSettings {
72            model: " ".to_owned(),
73            ..Default::default()
74        };
75        assert_eq!(settings.validate(), Err("choose a chat model".to_owned()));
76
77        settings = ChatSettings {
78            temperature: 2.5,
79            ..Default::default()
80        };
81        assert_eq!(
82            settings.validate(),
83            Err("temperature must be between 0 and 2".to_owned())
84        );
85
86        settings = ChatSettings {
87            max_tokens: 0,
88            ..Default::default()
89        };
90        assert_eq!(
91            settings.validate(),
92            Err("max tokens must be at least 1".to_owned())
93        );
94    }
95
96    #[test]
97    fn the_default_model_is_in_the_offered_list() {
98        assert!(
99            ChatSettings::MODELS
100                .iter()
101                .any(|(id, _)| *id == ChatSettings::default().model)
102        );
103    }
104}