use crate::agent::Agent;
use crate::completion::{
CompletionError, CompletionModel, CompletionRequestBuilder, CompletionResponse, Message,
};
use crate::message::{AssistantContent, ToolCall, ToolFunction};
use crate::OneOrMany;
use futures::{Stream, StreamExt};
use std::boxed::Box;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
#[derive(Debug, Clone)]
pub enum RawStreamingChoice<R: Clone> {
Message(String),
ToolCall {
id: String,
name: String,
arguments: serde_json::Value,
},
FinalResponse(R),
}
#[cfg(not(target_arch = "wasm32"))]
pub type StreamingResult<R> =
Pin<Box<dyn Stream<Item = Result<RawStreamingChoice<R>, CompletionError>> + Send>>;
#[cfg(target_arch = "wasm32")]
pub type StreamingResult<R> =
Pin<Box<dyn Stream<Item = Result<RawStreamingChoice<R>, CompletionError>>>>;
pub struct StreamingCompletionResponse<R: Clone + Unpin> {
pub(crate) inner: StreamingResult<R>,
text: String,
tool_calls: Vec<ToolCall>,
pub choice: OneOrMany<AssistantContent>,
pub response: Option<R>,
}
impl<R: Clone + Unpin> StreamingCompletionResponse<R> {
pub fn stream(inner: StreamingResult<R>) -> StreamingCompletionResponse<R> {
Self {
inner,
text: "".to_string(),
tool_calls: vec![],
choice: OneOrMany::one(AssistantContent::text("")),
response: None,
}
}
}
impl<R: Clone + Unpin> From<StreamingCompletionResponse<R>> for CompletionResponse<Option<R>> {
fn from(value: StreamingCompletionResponse<R>) -> CompletionResponse<Option<R>> {
CompletionResponse {
choice: value.choice,
raw_response: value.response,
}
}
}
impl<R: Clone + Unpin> Stream for StreamingCompletionResponse<R> {
type Item = Result<AssistantContent, CompletionError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let stream = self.get_mut();
match stream.inner.as_mut().poll_next(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(None) => {
let mut choice = vec![];
stream.tool_calls.iter().for_each(|tc| {
choice.push(AssistantContent::ToolCall(tc.clone()));
});
if choice.is_empty() || !stream.text.is_empty() {
choice.insert(0, AssistantContent::text(stream.text.clone()));
}
stream.choice = OneOrMany::many(choice)
.expect("There should be at least one assistant message");
Poll::Ready(None)
}
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),
Poll::Ready(Some(Ok(choice))) => match choice {
RawStreamingChoice::Message(text) => {
stream.text = format!("{}{}", stream.text, text.clone());
Poll::Ready(Some(Ok(AssistantContent::text(text))))
}
RawStreamingChoice::ToolCall {
id,
name,
arguments,
} => {
stream.tool_calls.push(ToolCall {
id: id.clone(),
function: ToolFunction {
name: name.clone(),
arguments: arguments.clone(),
},
});
Poll::Ready(Some(Ok(AssistantContent::tool_call(id, name, arguments))))
}
RawStreamingChoice::FinalResponse(response) => {
stream.response = Some(response);
stream.poll_next_unpin(cx)
}
},
}
}
}
pub trait StreamingPrompt<R: Clone + Unpin>: Send + Sync {
fn stream_prompt(
&self,
prompt: impl Into<Message> + Send,
) -> impl Future<Output = Result<StreamingCompletionResponse<R>, CompletionError>>;
}
pub trait StreamingChat<R: Clone + Unpin>: Send + Sync {
fn stream_chat(
&self,
prompt: impl Into<Message> + Send,
chat_history: Vec<Message>,
) -> impl Future<Output = Result<StreamingCompletionResponse<R>, CompletionError>>;
}
pub trait StreamingCompletion<M: CompletionModel> {
fn stream_completion(
&self,
prompt: impl Into<Message> + Send,
chat_history: Vec<Message>,
) -> impl Future<Output = Result<CompletionRequestBuilder<M>, CompletionError>>;
}
pub(crate) struct StreamingResultDyn<R: Clone + Unpin> {
pub(crate) inner: StreamingResult<R>,
}
impl<R: Clone + Unpin> Stream for StreamingResultDyn<R> {
type Item = Result<RawStreamingChoice<()>, CompletionError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let stream = self.get_mut();
match stream.inner.as_mut().poll_next(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),
Poll::Ready(Some(Ok(chunk))) => match chunk {
RawStreamingChoice::FinalResponse(_) => {
Poll::Ready(Some(Ok(RawStreamingChoice::FinalResponse(()))))
}
RawStreamingChoice::Message(m) => {
Poll::Ready(Some(Ok(RawStreamingChoice::Message(m))))
}
RawStreamingChoice::ToolCall {
id,
name,
arguments,
} => Poll::Ready(Some(Ok(RawStreamingChoice::ToolCall {
id,
name,
arguments,
}))),
},
}
}
}
pub async fn stream_to_stdout<M: CompletionModel>(
agent: &Agent<M>,
stream: &mut StreamingCompletionResponse<M::StreamingResponse>,
) -> Result<(), std::io::Error> {
print!("Response: ");
while let Some(chunk) = stream.next().await {
match chunk {
Ok(AssistantContent::Text(text)) => {
print!("{}", text.text);
std::io::Write::flush(&mut std::io::stdout())?;
}
Ok(AssistantContent::ToolCall(tool_call)) => {
let res = agent
.tools
.call(
&tool_call.function.name,
tool_call.function.arguments.to_string(),
)
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
println!("\nResult: {res}");
}
Err(e) => {
eprintln!("Error: {e}");
break;
}
}
}
println!();
Ok(())
}