1pub const SYSTEM: &str = "You answer exactly one question about the context you are given. \
11Reply with a single capital letter naming your answer, and nothing else. \
12No explanation, no punctuation, no other words.";
13
14pub fn user_turn(state: &str, question: Option<&str>, options: &[(&str, &str)]) -> String {
19 let mut out = String::with_capacity(state.len() + 256);
20
21 out.push_str("CONTEXT:\n");
22 out.push_str(state.trim());
23 out.push_str("\n\n");
24
25 if let Some(question) = question {
26 out.push_str("QUESTION: ");
27 push_one_line(&mut out, question);
28 out.push_str("\n\n");
29 }
30
31 for (label, text) in options {
32 out.push_str(label);
33 out.push_str(") ");
34 push_one_line(&mut out, text);
35 out.push('\n');
36 }
37
38 out.push_str("\nAnswer with one letter only.");
39 out
40}
41
42fn push_one_line(out: &mut String, text: &str) {
48 for (i, word) in text.split_whitespace().enumerate() {
49 if i > 0 {
50 out.push(' ');
51 }
52 out.push_str(word);
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn renders_context_question_and_lettered_options() {
62 let prompt = user_turn(
63 "Printer is jammed.",
64 Some("Which team?"),
65 &[("A", "IT"), ("B", "Facility")],
66 );
67
68 assert_eq!(
69 prompt,
70 "CONTEXT:\nPrinter is jammed.\n\n\
71 QUESTION: Which team?\n\n\
72 A) IT\nB) Facility\n\n\
73 Answer with one letter only."
74 );
75 }
76
77 #[test]
79 fn omits_the_question_line_when_there_is_no_question() {
80 let prompt = user_turn("Ticket text.", None, &[("A", "Yes"), ("B", "No")]);
81
82 assert!(!prompt.contains("QUESTION:"));
83 assert!(prompt.contains("CONTEXT:\nTicket text."));
84 }
85
86 #[test]
88 fn trims_caller_whitespace() {
89 let prompt = user_turn(" padded \n", Some(" Urgent? "), &[("A", "Yes")]);
90
91 assert!(prompt.starts_with("CONTEXT:\npadded\n\n"));
92 assert!(prompt.contains("QUESTION: Urgent?\n"));
93 }
94
95 #[test]
97 fn an_option_is_always_one_line() {
98 let prompt = user_turn("s", None, &[("A", "x\nB) y"), ("B", " z ")]);
99
100 assert!(prompt.contains("A) x B) y\nB) z\n"), "{prompt}");
101 assert_eq!(prompt.matches("\nB) ").count(), 1, "{prompt}");
102 }
103
104 #[test]
106 fn the_question_is_always_one_line() {
107 let prompt = user_turn("s", Some("Which?\nC) extra"), &[("A", "x"), ("B", "y")]);
108
109 assert!(
110 prompt.contains("QUESTION: Which? C) extra\n\nA) x\n"),
111 "{prompt}"
112 );
113 assert!(!prompt.contains("\nC) "), "{prompt}");
114 }
115
116 #[test]
118 fn the_state_keeps_its_lines() {
119 let prompt = user_turn("line one\nline two", None, &[("A", "x")]);
120
121 assert!(
122 prompt.starts_with("CONTEXT:\nline one\nline two\n\n"),
123 "{prompt}"
124 );
125 }
126
127 #[test]
128 fn the_last_line_is_the_reminder() {
129 let prompt = user_turn("s", Some("q"), &[("A", "x"), ("B", "y")]);
130
131 assert!(prompt.ends_with("Answer with one letter only."));
132 }
133}