use serde::de::DeserializeOwned;
use crate::backend::{LLMClient, MaterializeFailure, MaterializeReport, MediaFile};
#[cfg(feature = "streaming")]
use crate::error::RStructorError;
use crate::error::Result;
use crate::model::Instructor;
#[cfg(feature = "streaming")]
const STREAMING_MEDIA_UNSUPPORTED: &str = "streaming requests with media are not supported; \
remove the media attachment or use a non-streaming request terminal";
pub struct Request<'a, C: ?Sized> {
client: &'a C,
system: Option<String>,
media: Vec<MediaFile>,
#[cfg(feature = "tools")]
tools: Option<&'a crate::backend::tools::Toolbox>,
#[cfg(feature = "tools")]
max_iterations: usize,
}
impl<'a, C: ?Sized> Request<'a, C> {
fn new(client: &'a C) -> Self {
Self {
client,
system: None,
media: Vec::new(),
#[cfg(feature = "tools")]
tools: None,
#[cfg(feature = "tools")]
max_iterations: crate::backend::tools::DEFAULT_MAX_TOOL_ITERATIONS,
}
}
#[must_use]
pub fn system(mut self, system: impl Into<String>) -> Self {
self.system = Some(system.into());
self
}
#[must_use]
pub fn media(mut self, media: impl Into<Vec<MediaFile>>) -> Self {
self.media = media.into();
self
}
#[cfg(feature = "tools")]
#[must_use]
pub fn tools(mut self, toolbox: &'a crate::backend::tools::Toolbox) -> Self {
self.tools = Some(toolbox);
self
}
#[cfg(feature = "tools")]
#[must_use]
pub fn max_iterations(mut self, max_iterations: usize) -> Self {
self.max_iterations = max_iterations;
self
}
}
impl<C: LLMClient + Sync + ?Sized> Request<'_, C> {
pub async fn materialize<T>(self, prompt: &str) -> Result<T>
where
T: Instructor + DeserializeOwned + Send + 'static,
{
self.client
.materialize_request(self.system.as_deref(), prompt, &self.media)
.await
}
pub async fn materialize_with_attempts<T>(
self,
prompt: &str,
) -> std::result::Result<MaterializeReport<T>, MaterializeFailure>
where
T: Instructor + DeserializeOwned + Send + 'static,
{
self.client
.materialize_request_with_attempts(self.system.as_deref(), prompt, &self.media)
.await
}
pub async fn generate(self, prompt: &str) -> Result<String> {
self.client
.generate_request(self.system.as_deref(), prompt, &self.media)
.await
}
}
#[cfg(feature = "streaming")]
impl<'a, C: LLMClient + Sync + ?Sized> Request<'a, C> {
pub fn materialize_iter<T>(self, prompt: &str) -> crate::backend::streaming::ItemStream<'a, T>
where
T: Instructor + DeserializeOwned + Send + 'static,
{
if !self.media.is_empty() {
return crate::backend::streaming::error_stream(RStructorError::Unsupported(
STREAMING_MEDIA_UNSUPPORTED.to_string(),
));
}
self.client
.materialize_iter_request::<T>(self.system, prompt.to_string())
}
pub fn generate_stream(self, prompt: &str) -> crate::backend::streaming::TextStream<'a> {
if !self.media.is_empty() {
return crate::backend::streaming::error_stream(RStructorError::Unsupported(
STREAMING_MEDIA_UNSUPPORTED.to_string(),
));
}
self.client
.generate_stream_request(self.system, prompt.to_string())
}
pub fn materialize_stream<T>(
self,
prompt: &str,
) -> crate::backend::streaming::ObjectStream<'a, T>
where
T: Instructor + DeserializeOwned + Send + 'static,
{
if !self.media.is_empty() {
return crate::backend::streaming::error_stream(RStructorError::Unsupported(
STREAMING_MEDIA_UNSUPPORTED.to_string(),
));
}
self.client
.materialize_stream_request::<T>(self.system, prompt.to_string())
}
}
#[cfg(feature = "tools")]
impl<C: crate::backend::tools::ToolRunner + LLMClient + Sync + ?Sized> Request<'_, C> {
pub async fn run(self, prompt: &str) -> Result<String> {
match self.tools {
Some(toolbox) => {
self.client
.run_tool_loop(
self.system.as_deref(),
prompt,
&self.media,
toolbox,
self.max_iterations,
)
.await
}
None => {
self.client
.generate_request(self.system.as_deref(), prompt, &self.media)
.await
}
}
}
}
pub trait RequestExt: LLMClient {
fn request(&self) -> Request<'_, Self> {
Request::new(self)
}
fn with_system(&self, system: impl Into<String>) -> Request<'_, Self> {
Request::new(self).system(system)
}
fn with_media<'a>(&'a self, media: &'a [MediaFile]) -> Request<'a, Self> {
Request::new(self).media(media.to_vec())
}
#[cfg(feature = "tools")]
fn with_tools<'a>(&'a self, toolbox: &'a crate::backend::tools::Toolbox) -> Request<'a, Self> {
Request::new(self).tools(toolbox)
}
}
impl<C: LLMClient + ?Sized> RequestExt for C {}