use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
use crate::{
completion::{CompletionModel, Document},
message::ToolChoice,
tool::{
Tool, ToolSet,
server::{ToolServer, ToolServerHandle},
},
vector_store::VectorStoreIndexDyn,
};
#[cfg(feature = "rmcp")]
#[cfg_attr(docsrs, doc(cfg(feature = "rmcp")))]
use crate::tool::rmcp::McpTool as RmcpTool;
use super::Agent;
pub struct AgentBuilder<M>
where
M: CompletionModel,
{
name: Option<String>,
description: Option<String>,
model: M,
preamble: Option<String>,
static_context: Vec<Document>,
additional_params: Option<serde_json::Value>,
max_tokens: Option<u64>,
dynamic_context: Vec<(usize, Box<dyn VectorStoreIndexDyn + Send + Sync>)>,
temperature: Option<f64>,
tool_server_handle: Option<ToolServerHandle>,
tool_choice: Option<ToolChoice>,
}
impl<M> AgentBuilder<M>
where
M: CompletionModel,
{
pub fn new(model: M) -> Self {
Self {
name: None,
description: None,
model,
preamble: None,
static_context: vec![],
temperature: None,
max_tokens: None,
additional_params: None,
dynamic_context: vec![],
tool_server_handle: None,
tool_choice: None,
}
}
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.into());
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.into());
self
}
pub fn preamble(mut self, preamble: &str) -> Self {
self.preamble = Some(preamble.into());
self
}
pub fn without_preamble(mut self) -> Self {
self.preamble = None;
self
}
pub fn append_preamble(mut self, doc: &str) -> Self {
self.preamble = Some(format!(
"{}\n{}",
self.preamble.unwrap_or_else(|| "".into()),
doc
));
self
}
pub fn context(mut self, doc: &str) -> Self {
self.static_context.push(Document {
id: format!("static_doc_{}", self.static_context.len()),
text: doc.into(),
additional_props: HashMap::new(),
});
self
}
pub fn tool(self, tool: impl Tool + 'static) -> AgentBuilderSimple<M> {
let toolname = tool.name();
let tools = ToolSet::from_tools(vec![tool]);
let static_tools = vec![toolname];
AgentBuilderSimple {
name: self.name,
description: self.description,
model: self.model,
preamble: self.preamble,
static_context: self.static_context,
static_tools,
additional_params: self.additional_params,
max_tokens: self.max_tokens,
dynamic_context: vec![],
dynamic_tools: vec![],
temperature: self.temperature,
tools,
tool_choice: self.tool_choice,
}
}
pub fn tool_server_handle(mut self, handle: ToolServerHandle) -> Self {
self.tool_server_handle = Some(handle);
self
}
#[cfg(feature = "rmcp")]
#[cfg_attr(docsrs, doc(cfg(feature = "rmcp")))]
pub fn rmcp_tool(
self,
tool: rmcp::model::Tool,
client: rmcp::service::ServerSink,
) -> AgentBuilderSimple<M> {
let toolname = tool.name.clone().to_string();
let tools = ToolSet::from_tools(vec![RmcpTool::from_mcp_server(tool, client)]);
let static_tools = vec![toolname];
AgentBuilderSimple {
name: self.name,
description: self.description,
model: self.model,
preamble: self.preamble,
static_context: self.static_context,
static_tools,
additional_params: self.additional_params,
max_tokens: self.max_tokens,
dynamic_context: vec![],
dynamic_tools: vec![],
temperature: self.temperature,
tools,
tool_choice: self.tool_choice,
}
}
#[cfg(feature = "rmcp")]
#[cfg_attr(docsrs, doc(cfg(feature = "rmcp")))]
pub fn rmcp_tools(
self,
tools: Vec<rmcp::model::Tool>,
client: rmcp::service::ServerSink,
) -> AgentBuilderSimple<M> {
let (static_tools, tools) = tools.into_iter().fold(
(Vec::new(), Vec::new()),
|(mut toolnames, mut toolset), tool| {
let tool_name = tool.name.to_string();
let tool = RmcpTool::from_mcp_server(tool, client.clone());
toolnames.push(tool_name);
toolset.push(tool);
(toolnames, toolset)
},
);
let tools = ToolSet::from_tools(tools);
AgentBuilderSimple {
name: self.name,
description: self.description,
model: self.model,
preamble: self.preamble,
static_context: self.static_context,
static_tools,
additional_params: self.additional_params,
max_tokens: self.max_tokens,
dynamic_context: vec![],
dynamic_tools: vec![],
temperature: self.temperature,
tools,
tool_choice: self.tool_choice,
}
}
pub fn dynamic_context(
mut self,
sample: usize,
dynamic_context: impl VectorStoreIndexDyn + Send + Sync + 'static,
) -> Self {
self.dynamic_context
.push((sample, Box::new(dynamic_context)));
self
}
pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = Some(tool_choice);
self
}
pub fn dynamic_tools(
self,
sample: usize,
dynamic_tools: impl VectorStoreIndexDyn + Send + Sync + 'static,
toolset: ToolSet,
) -> AgentBuilderSimple<M> {
let thing: Box<dyn VectorStoreIndexDyn + Send + Sync + 'static> = Box::new(dynamic_tools);
let dynamic_tools = vec![(sample, thing)];
AgentBuilderSimple {
name: self.name,
description: self.description,
model: self.model,
preamble: self.preamble,
static_context: self.static_context,
static_tools: vec![],
additional_params: self.additional_params,
max_tokens: self.max_tokens,
dynamic_context: vec![],
dynamic_tools,
temperature: self.temperature,
tools: toolset,
tool_choice: self.tool_choice,
}
}
pub fn temperature(mut self, temperature: f64) -> Self {
self.temperature = Some(temperature);
self
}
pub fn max_tokens(mut self, max_tokens: u64) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn additional_params(mut self, params: serde_json::Value) -> Self {
self.additional_params = Some(params);
self
}
pub fn build(self) -> Agent<M> {
let tool_server_handle = if let Some(handle) = self.tool_server_handle {
handle
} else {
ToolServer::new().run()
};
Agent {
name: self.name,
description: self.description,
model: Arc::new(self.model),
preamble: self.preamble,
static_context: self.static_context,
temperature: self.temperature,
max_tokens: self.max_tokens,
additional_params: self.additional_params,
tool_choice: self.tool_choice,
dynamic_context: Arc::new(RwLock::new(self.dynamic_context)),
tool_server_handle,
}
}
}
pub struct AgentBuilderSimple<M>
where
M: CompletionModel,
{
name: Option<String>,
description: Option<String>,
model: M,
preamble: Option<String>,
static_context: Vec<Document>,
static_tools: Vec<String>,
additional_params: Option<serde_json::Value>,
max_tokens: Option<u64>,
dynamic_context: Vec<(usize, Box<dyn VectorStoreIndexDyn + Send + Sync>)>,
dynamic_tools: Vec<(usize, Box<dyn VectorStoreIndexDyn + Send + Sync>)>,
temperature: Option<f64>,
tools: ToolSet,
tool_choice: Option<ToolChoice>,
}
impl<M> AgentBuilderSimple<M>
where
M: CompletionModel,
{
pub fn new(model: M) -> Self {
Self {
name: None,
description: None,
model,
preamble: None,
static_context: vec![],
static_tools: vec![],
temperature: None,
max_tokens: None,
additional_params: None,
dynamic_context: vec![],
dynamic_tools: vec![],
tools: ToolSet::default(),
tool_choice: None,
}
}
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.into());
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.into());
self
}
pub fn preamble(mut self, preamble: &str) -> Self {
self.preamble = Some(preamble.into());
self
}
pub fn without_preamble(mut self) -> Self {
self.preamble = None;
self
}
pub fn append_preamble(mut self, doc: &str) -> Self {
self.preamble = Some(format!(
"{}\n{}",
self.preamble.unwrap_or_else(|| "".into()),
doc
));
self
}
pub fn context(mut self, doc: &str) -> Self {
self.static_context.push(Document {
id: format!("static_doc_{}", self.static_context.len()),
text: doc.into(),
additional_props: HashMap::new(),
});
self
}
pub fn tool(mut self, tool: impl Tool + 'static) -> Self {
let toolname = tool.name();
self.tools.add_tool(tool);
self.static_tools.push(toolname);
self
}
#[cfg(feature = "rmcp")]
#[cfg_attr(docsrs, doc(cfg(feature = "rmcp")))]
pub fn rmcp_tools(
mut self,
tools: Vec<rmcp::model::Tool>,
client: rmcp::service::ServerSink,
) -> Self {
for tool in tools {
let tool_name = tool.name.to_string();
let tool = RmcpTool::from_mcp_server(tool, client.clone());
self.static_tools.push(tool_name);
self.tools.add_tool(tool);
}
self
}
pub fn dynamic_context(
mut self,
sample: usize,
dynamic_context: impl VectorStoreIndexDyn + Send + Sync + 'static,
) -> Self {
self.dynamic_context
.push((sample, Box::new(dynamic_context)));
self
}
pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = Some(tool_choice);
self
}
pub fn dynamic_tools(
mut self,
sample: usize,
dynamic_tools: impl VectorStoreIndexDyn + Send + Sync + 'static,
toolset: ToolSet,
) -> Self {
self.dynamic_tools.push((sample, Box::new(dynamic_tools)));
self.tools.add_tools(toolset);
self
}
pub fn temperature(mut self, temperature: f64) -> Self {
self.temperature = Some(temperature);
self
}
pub fn max_tokens(mut self, max_tokens: u64) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn additional_params(mut self, params: serde_json::Value) -> Self {
self.additional_params = Some(params);
self
}
pub fn build(self) -> Agent<M> {
let tool_server_handle = ToolServer::new()
.static_tool_names(self.static_tools)
.add_tools(self.tools)
.add_dynamic_tools(self.dynamic_tools)
.run();
Agent {
name: self.name,
description: self.description,
model: Arc::new(self.model),
preamble: self.preamble,
static_context: self.static_context,
temperature: self.temperature,
max_tokens: self.max_tokens,
additional_params: self.additional_params,
tool_choice: self.tool_choice,
dynamic_context: Arc::new(RwLock::new(self.dynamic_context)),
tool_server_handle,
}
}
}