use super::core::{ResolvedRun, finish, response_format, run_loop};
use super::stream::AgentStream;
use super::{Agent, AgentError, ChatOptions, StopReason, Usage};
use crate::message::Message;
use crate::prompt::Prompt;
use crate::tool::ToolBox;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use std::any::TypeId;
use std::future::{Future, IntoFuture};
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct RunOptions {
pub model: Option<String>,
pub toolbox: Option<Arc<dyn ToolBox>>,
pub max_iterations: Option<usize>,
pub chat_options: Option<ChatOptions>,
}
impl RunOptions {
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn with_toolbox(self, toolbox: impl ToolBox) -> Self {
self.with_toolbox_arc(Arc::new(toolbox))
}
pub fn with_toolbox_arc(mut self, toolbox: Arc<dyn ToolBox>) -> Self {
self.toolbox = Some(toolbox);
self
}
pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
self.max_iterations = Some(max_iterations);
self
}
pub fn with_chat_options(mut self, chat_options: ChatOptions) -> Self {
self.chat_options = Some(chat_options);
self
}
fn merge(self, other: RunOptions) -> Self {
Self {
model: other.model.or(self.model),
toolbox: other.toolbox.or(self.toolbox),
max_iterations: other.max_iterations.or(self.max_iterations),
chat_options: other.chat_options.or(self.chat_options),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct AgentResult<D> {
pub answer: D,
pub new_messages: Vec<Message>,
pub usage: Usage,
pub stop_reason: Option<StopReason>,
pub reasoning_content: Option<String>,
pub iterations: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct Complete;
#[derive(Debug, Clone, Copy)]
pub struct Streaming;
#[must_use = "a request does nothing until it is awaited"]
pub struct AgentRequest<'a, D = String, M = Complete> {
agent: &'a Agent,
prompt: Prompt,
history: &'a [Message],
options: RunOptions,
marker: PhantomData<fn() -> (D, M)>,
}
impl<'a, D, M> AgentRequest<'a, D, M> {
pub(super) fn new(agent: &'a Agent, prompt: Prompt) -> Self {
Self {
agent,
prompt,
history: &[],
options: RunOptions::default(),
marker: PhantomData,
}
}
pub fn with_history(mut self, history: &'a [Message]) -> Self {
self.history = history;
self
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.options.model = Some(model.into());
self
}
pub fn with_toolbox(self, toolbox: impl ToolBox) -> Self {
self.with_toolbox_arc(Arc::new(toolbox))
}
pub fn with_toolbox_arc(mut self, toolbox: Arc<dyn ToolBox>) -> Self {
self.options.toolbox = Some(toolbox);
self
}
pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
self.options.max_iterations = Some(max_iterations);
self
}
pub fn with_chat_options(mut self, chat_options: ChatOptions) -> Self {
self.options.chat_options = Some(chat_options);
self
}
pub fn with_options(mut self, options: RunOptions) -> Self {
self.options = self.options.merge(options);
self
}
fn resolve(self) -> Result<(ResolvedRun, Vec<Message>, Prompt), AgentError>
where
D: JsonSchema + 'static,
{
let agent = self.agent;
let options = self.options;
let model = options
.model
.or_else(|| agent.model.clone())
.ok_or(AgentError::MissingModel)?;
let is_answer_string = TypeId::of::<String>() == TypeId::of::<D>();
let run = ResolvedRun {
client: agent.client.clone(),
system: agent.system.clone(),
model,
toolbox: options.toolbox.or_else(|| agent.toolbox.clone()),
max_iterations: options.max_iterations.unwrap_or(agent.max_iterations),
chat_options: options
.chat_options
.unwrap_or_else(|| agent.chat_options.clone()),
response_format: if is_answer_string {
None
} else {
Some(response_format::<D>()?)
},
};
Ok((run, self.history.to_vec(), self.prompt))
}
}
impl<D> IntoFuture for AgentRequest<'_, D, Complete>
where
D: DeserializeOwned + JsonSchema + Send + 'static,
{
type Output = Result<AgentResult<D>, AgentError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'static>>;
fn into_future(self) -> Self::IntoFuture {
let resolved = self.resolve();
Box::pin(async move {
let (run, history, prompt) = resolved?;
finish(run_loop::<D>(run, history, prompt, false)).await
})
}
}
impl<D> IntoFuture for AgentRequest<'_, D, Streaming>
where
D: DeserializeOwned + JsonSchema + Send + 'static,
{
type Output = Result<AgentStream<D>, AgentError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'static>>;
fn into_future(self) -> Self::IntoFuture {
let resolved = self.resolve();
Box::pin(async move {
let (run, history, prompt) = resolved?;
Ok(AgentStream::new(run_loop::<D>(run, history, prompt, true)))
})
}
}