1use crate::message::{Conversation, Message, Role};
4use crate::tool::Tool;
5
6#[derive(Debug, Clone, Default, PartialEq)]
8pub struct Request {
9 pub model: String,
10 pub system: Option<String>,
14 pub messages: Vec<Message>,
15 pub temperature: Option<f64>,
16 pub max_tokens: Option<u32>,
17 pub tools: Vec<Tool>,
18 pub stop_sequences: Vec<String>,
19}
20
21impl Request {
22 pub fn new(model: impl Into<String>) -> Request {
23 Request { model: model.into(), ..Request::default() }
24 }
25
26 pub fn model(mut self, model: impl Into<String>) -> Request {
27 self.model = model.into();
28 self
29 }
30
31 pub fn system(mut self, system: impl Into<String>) -> Request {
32 self.system = Some(system.into());
33 self
34 }
35
36 pub fn message(mut self, message: Message) -> Request {
37 self.messages.push(message);
38 self
39 }
40
41 pub fn messages(mut self, messages: impl IntoIterator<Item = Message>) -> Request {
42 self.messages.extend(messages);
43 self
44 }
45
46 pub fn conversation(self, conversation: Conversation) -> Request {
47 self.messages(conversation.into_messages())
48 }
49
50 pub fn user(self, text: impl Into<String>) -> Request {
51 self.message(Message::user(text))
52 }
53
54 pub fn assistant(self, text: impl Into<String>) -> Request {
55 self.message(Message::assistant(text))
56 }
57
58 pub fn temperature(mut self, temperature: f64) -> Request {
60 self.temperature = Some(temperature);
61 self
62 }
63
64 pub fn max_tokens(mut self, max_tokens: u32) -> Request {
65 self.max_tokens = Some(max_tokens);
66 self
67 }
68
69 pub fn tool(mut self, tool: Tool) -> Request {
70 self.tools.push(tool);
71 self
72 }
73
74 pub fn tools(mut self, tools: impl IntoIterator<Item = Tool>) -> Request {
75 self.tools.extend(tools);
76 self
77 }
78
79 pub fn stop(mut self, sequence: impl Into<String>) -> Request {
80 self.stop_sequences.push(sequence.into());
81 self
82 }
83
84 pub fn effective_system(&self) -> Option<String> {
90 let mut parts: Vec<String> = Vec::new();
91 if let Some(system) = &self.system {
92 parts.push(system.clone());
93 }
94 for message in &self.messages {
95 if message.role == Role::System {
96 parts.push(message.text());
97 }
98 }
99 if parts.is_empty() {
100 return None;
101 }
102 Some(parts.join("\n\n"))
103 }
104
105 pub fn dialogue(&self) -> impl Iterator<Item = &Message> {
108 self.messages.iter().filter(|message| message.role != Role::System)
109 }
110
111 pub fn last_user_text(&self) -> Option<String> {
113 self.messages
114 .iter()
115 .rev()
116 .find(|message| message.role == Role::User)
117 .map(Message::text)
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::tool::Tool;
125
126 #[test]
127 fn a_request_is_built_in_one_expression() {
128 let request = Request::new("claude-sonnet-5")
129 .system("Be brief.")
130 .user("Why is the sky blue?")
131 .temperature(0.2)
132 .max_tokens(1024)
133 .stop("END")
134 .tool(Tool::new("search", "Search the web"));
135
136 assert_eq!(request.model, "claude-sonnet-5");
137 assert_eq!(request.temperature, Some(0.2));
138 assert_eq!(request.max_tokens, Some(1024));
139 assert_eq!(request.stop_sequences, vec!["END".to_string()]);
140 assert_eq!(request.tools.len(), 1);
141 assert_eq!(request.last_user_text().as_deref(), Some("Why is the sky blue?"));
142 }
143
144 #[test]
145 fn system_messages_and_the_system_field_are_both_honoured() {
146 let request = Request::new("claude-sonnet-5")
147 .system("Be brief.")
148 .message(Message::system("Answer in English."))
149 .user("Hello");
150
151 assert_eq!(request.effective_system().unwrap(), "Be brief.\n\nAnswer in English.");
152 assert_eq!(request.dialogue().count(), 1);
154 }
155
156 #[test]
157 fn a_request_without_instructions_has_no_system_prompt() {
158 assert!(Request::new("claude-sonnet-5").user("Hi").effective_system().is_none());
159 }
160
161 #[test]
162 fn a_conversation_can_be_handed_over_whole() {
163 let conversation = Conversation::new().user("One").assistant("Two").user("Three");
164 let request = Request::new("claude-sonnet-5").conversation(conversation);
165
166 assert_eq!(request.messages.len(), 3);
167 assert_eq!(request.last_user_text().as_deref(), Some("Three"));
168 }
169}