gemini_genai_rs/chats/
mod.rs

1//! Stateful chat API — multi-turn conversation over generateContent.
2//!
3//! Feature-gated behind `chats` (depends on `generate`).
4//!
5//! Wraps the generateContent endpoint with automatic conversation history
6//! management, similar to js-genai's `ChatSession`.
7
8use crate::client::Client;
9use crate::generate::{GenerateContentConfig, GenerateContentResponse, GenerateError};
10use crate::protocol::types::{Content, ModelId};
11
12/// A stateful chat session that tracks conversation history.
13///
14/// Each call to `send_message` appends the user message and model response
15/// to the history, so subsequent calls include full conversation context.
16pub struct ChatSession<'a> {
17    client: &'a Client,
18    model: ModelId,
19    history: Vec<Content>,
20    system_instruction: Option<String>,
21}
22
23impl<'a> ChatSession<'a> {
24    /// Send a text message and get the model's response.
25    ///
26    /// The message and response are appended to conversation history.
27    pub async fn send_message(
28        &mut self,
29        text: impl Into<String>,
30    ) -> Result<GenerateContentResponse, GenerateError> {
31        let user_content = Content::user(text);
32        self.history.push(user_content);
33
34        let mut config = GenerateContentConfig::from_contents(self.history.clone());
35        if let Some(ref si) = self.system_instruction {
36            config = config.system_instruction(si.clone());
37        }
38
39        let response = self
40            .client
41            .generate_content_with(config, Some(&self.model))
42            .await?;
43
44        // Append model response to history
45        if let Some(candidate) = response.candidates.first()
46            && let Some(content) = &candidate.content
47        {
48            self.history.push(content.clone());
49        }
50
51        Ok(response)
52    }
53
54    /// Get the current conversation history.
55    pub fn history(&self) -> &[Content] {
56        &self.history
57    }
58
59    /// Get the number of turns in the conversation.
60    pub fn turn_count(&self) -> usize {
61        self.history.len()
62    }
63}
64
65impl Client {
66    /// Start a new chat session with the default model.
67    pub fn chat(&self) -> ChatSessionBuilder<'_> {
68        ChatSessionBuilder {
69            client: self,
70            model: self.default_model().clone(),
71            history: vec![],
72            system_instruction: None,
73        }
74    }
75}
76
77/// Builder for configuring a [`ChatSession`].
78pub struct ChatSessionBuilder<'a> {
79    client: &'a Client,
80    model: ModelId,
81    history: Vec<Content>,
82    system_instruction: Option<String>,
83}
84
85impl<'a> ChatSessionBuilder<'a> {
86    /// Set the model for this chat.
87    pub fn model(mut self, model: impl Into<ModelId>) -> Self {
88        self.model = model.into();
89        self
90    }
91
92    /// Set initial conversation history (for resuming).
93    pub fn history(mut self, history: Vec<Content>) -> Self {
94        self.history = history;
95        self
96    }
97
98    /// Set system instruction.
99    pub fn system_instruction(mut self, instruction: impl Into<String>) -> Self {
100        self.system_instruction = Some(instruction.into());
101        self
102    }
103
104    /// Build the chat session.
105    pub fn build(self) -> ChatSession<'a> {
106        ChatSession {
107            client: self.client,
108            model: self.model,
109            history: self.history,
110            system_instruction: self.system_instruction,
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn chat_builder() {
121        let client = Client::from_api_key("key");
122        let chat = client
123            .chat()
124            .model(ModelId::from_static("models/gemini-2.0-flash-live-001"))
125            .system_instruction("You are helpful")
126            .build();
127        assert_eq!(chat.turn_count(), 0);
128        assert!(chat.history().is_empty());
129    }
130
131    #[test]
132    fn chat_with_initial_history() {
133        use crate::protocol::types::{Part, Role};
134        let client = Client::from_api_key("key");
135        let history = vec![
136            Content::user("Hello"),
137            Content {
138                role: Some(Role::Model),
139                parts: vec![Part::text("Hi there!")],
140            },
141        ];
142        let chat = client.chat().history(history).build();
143        assert_eq!(chat.turn_count(), 2);
144    }
145}