rig_agent/integrations/
cli_chatbot.rs1use rig_core::{
2 markers::{Missing, Provided},
3 message::Message,
4};
5
6use crate::{
7 agent::{Agent, MultiTurnStreamItem, Text},
8 completion::{Chat, CompletionError, PromptError, Usage},
9 streaming::{StreamedAssistantContent, StreamingPrompt},
10};
11use futures::StreamExt;
12use std::io::{self, Write};
13
14pub struct ChatImpl<T>(T)
15where
16 T: Chat;
17
18pub struct AgentImpl {
19 agent: Agent,
20 max_turns: usize,
21 show_usage: bool,
22 usage: Usage,
23}
24
25pub struct ChatBotBuilder<T = Missing>(T);
26
27pub struct ChatBot<T>(T);
28
29#[allow(private_interfaces)]
31trait CliChat {
32 async fn request(
33 &mut self,
34 prompt: &str,
35 history: &mut Vec<Message>,
36 ) -> Result<String, PromptError>;
37
38 fn show_usage(&self) -> bool {
39 false
40 }
41
42 fn usage(&self) -> Option<Usage> {
43 None
44 }
45}
46
47impl<T> CliChat for ChatImpl<T>
48where
49 T: Chat,
50{
51 async fn request(
52 &mut self,
53 prompt: &str,
54 history: &mut Vec<Message>,
55 ) -> Result<String, PromptError> {
56 let res = self.0.chat(prompt, history).await?;
57 println!("{res}");
58
59 Ok(res)
60 }
61}
62
63impl CliChat for AgentImpl {
64 async fn request(
65 &mut self,
66 prompt: &str,
67 history: &mut Vec<Message>,
68 ) -> Result<String, PromptError> {
69 let mut response_stream = self
70 .agent
71 .stream_prompt(prompt)
72 .history(history.clone())
73 .max_turns(self.max_turns)
74 .await;
75
76 let mut acc = String::new();
77 let mut messages = None;
78
79 let result = loop {
80 let Some(chunk) = response_stream.next().await else {
81 println!();
82 break Ok(acc);
83 };
84
85 match chunk {
86 Ok(MultiTurnStreamItem::StreamAssistantItem(StreamedAssistantContent::Text(
87 Text { text, .. },
88 ))) => {
89 print!("{}", text);
90 acc.push_str(&text);
91 }
92 Ok(MultiTurnStreamItem::FinalResponse(final_response)) => {
93 self.usage = final_response.usage();
94 messages = final_response.messages().map(|history| history.to_vec());
95 }
96 Err(e) => {
97 break Err(PromptError::CompletionError(
98 CompletionError::ResponseError(e.to_string()),
99 ));
100 }
101 _ => continue,
102 }
103 };
104
105 if let Ok(response) = &result {
106 if let Some(messages) = messages {
107 history.extend(messages);
108 } else {
109 history.push(Message::user(prompt));
110 history.push(Message::assistant(response.as_str()));
111 }
112 }
113
114 result
115 }
116
117 fn show_usage(&self) -> bool {
118 self.show_usage
119 }
120
121 fn usage(&self) -> Option<Usage> {
122 Some(self.usage)
123 }
124}
125
126impl Default for ChatBotBuilder<Missing> {
127 fn default() -> Self {
128 Self(Missing)
129 }
130}
131
132impl ChatBotBuilder<Missing> {
133 pub fn new() -> Self {
134 Self::default()
135 }
136
137 pub fn agent(self, agent: Agent) -> ChatBotBuilder<Provided<AgentImpl>> {
138 ChatBotBuilder(Provided(AgentImpl {
139 agent,
140 max_turns: 1,
141 show_usage: false,
142 usage: Usage::default(),
143 }))
144 }
145
146 pub fn chat<T: Chat>(self, chatbot: T) -> ChatBotBuilder<Provided<ChatImpl<T>>> {
147 ChatBotBuilder(Provided(ChatImpl(chatbot)))
148 }
149}
150
151impl<T> ChatBotBuilder<Provided<ChatImpl<T>>>
152where
153 T: Chat,
154{
155 pub fn build(self) -> ChatBot<ChatImpl<T>> {
156 ChatBot(self.0.0)
157 }
158}
159
160impl ChatBotBuilder<Provided<AgentImpl>> {
161 pub fn max_turns(self, max_turns: usize) -> Self {
164 ChatBotBuilder(Provided(AgentImpl {
165 max_turns,
166 ..self.0.0
167 }))
168 }
169
170 pub fn show_usage(self) -> Self {
171 ChatBotBuilder(Provided(AgentImpl {
172 show_usage: true,
173 ..self.0.0
174 }))
175 }
176
177 pub fn build(self) -> ChatBot<AgentImpl> {
178 ChatBot(self.0.0)
179 }
180}
181
182#[allow(private_bounds)]
183impl<T> ChatBot<T>
184where
185 T: CliChat,
186{
187 pub async fn run(mut self) -> Result<(), PromptError> {
188 let stdin = io::stdin();
189 let mut stdout = io::stdout();
190 let mut history = vec![];
191
192 loop {
193 print!("> ");
194 stdout.flush().map_err(|e| {
195 PromptError::CompletionError(CompletionError::ResponseError(format!(
196 "failed to flush stdout: {e}"
197 )))
198 })?;
199
200 let mut input = String::new();
201 match stdin.read_line(&mut input) {
202 Ok(_) => {
203 let input = input.trim();
204 if input == "exit" {
205 break;
206 }
207
208 tracing::info!("Prompt:\n{input}\n");
209
210 println!();
211 println!("========================== Response ============================");
212
213 self.0.request(input, &mut history).await?;
214
215 println!("================================================================");
216 println!();
217
218 if self.0.show_usage()
219 && let Some(Usage {
220 input_tokens,
221 output_tokens,
222 ..
223 }) = self.0.usage()
224 {
225 println!("Input {input_tokens} tokens\nOutput {output_tokens} tokens");
226 }
227 }
228 Err(e) => println!("Error reading request: {e}"),
229 }
230 }
231
232 Ok(())
233 }
234}