use crate::types::common::{LettaId, Metadata, Timestamp};
use crate::types::memory::Block;
use bon::Builder;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentEnvironmentVariable {
#[serde(skip_serializing_if = "Option::is_none")]
pub created_by_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_updated_by_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<LettaId>,
pub key: String,
pub value: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub agent_id: LettaId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, SmartDefault)]
pub enum AgentType {
#[default]
#[serde(rename = "memgpt_agent")]
MemGPT,
#[serde(rename = "memgpt_v2_agent")]
MemGPTv2,
#[serde(rename = "react_agent")]
React,
#[serde(rename = "workflow_agent")]
Workflow,
#[serde(rename = "split_thread_agent")]
SplitThread,
#[serde(rename = "sleeptime_agent")]
Sleeptime,
#[serde(rename = "voice_convo_agent")]
VoiceConvo,
#[serde(rename = "voice_sleeptime_agent")]
VoiceSleeptime,
#[serde(other)]
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LLMConfig {
pub model: String,
pub model_endpoint_type: ModelEndpointType,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_wrapper: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub put_inner_thoughts_in_kwargs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_reasoner: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_reasoning_tokens: Option<u32>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl LLMConfig {
pub fn openai(model: impl Into<String>) -> Self {
Self {
model: model.into(),
model_endpoint_type: ModelEndpointType::Openai,
model_endpoint: None,
context_window: Some(128000), provider_name: Some("openai".to_string()),
provider_category: None,
model_wrapper: None,
put_inner_thoughts_in_kwargs: None,
handle: None,
temperature: None,
max_tokens: None,
enable_reasoner: None,
reasoning_effort: None,
max_reasoning_tokens: None,
extra: HashMap::new(),
}
}
pub fn anthropic(model: impl Into<String>) -> Self {
Self {
model: model.into(),
model_endpoint_type: ModelEndpointType::Anthropic,
model_endpoint: None,
context_window: Some(200000), provider_name: Some("anthropic".to_string()),
provider_category: None,
model_wrapper: None,
put_inner_thoughts_in_kwargs: None,
handle: None,
temperature: None,
max_tokens: None,
enable_reasoner: None,
reasoning_effort: None,
max_reasoning_tokens: None,
extra: HashMap::new(),
}
}
pub fn local(model: impl Into<String>, endpoint: impl Into<String>) -> Self {
Self {
model: model.into(),
model_endpoint_type: ModelEndpointType::Ollama,
model_endpoint: Some(endpoint.into()),
context_window: Some(4096), provider_name: Some("ollama".to_string()),
provider_category: None,
model_wrapper: None,
put_inner_thoughts_in_kwargs: None,
handle: None,
temperature: None,
max_tokens: None,
enable_reasoner: None,
reasoning_effort: None,
max_reasoning_tokens: None,
extra: HashMap::new(),
}
}
pub fn with_context_window(mut self, size: u32) -> Self {
self.context_window = Some(size);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.model_endpoint = Some(endpoint.into());
self
}
pub fn with_reasoner(mut self, effort: Option<&str>, max_tokens: Option<u32>) -> Self {
self.enable_reasoner = Some(true);
if let Some(e) = effort {
self.reasoning_effort = Some(e.to_string());
}
if let Some(t) = max_tokens {
self.max_reasoning_tokens = Some(t);
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ModelEndpointType {
Openai,
Anthropic,
Cohere,
GoogleAi,
GoogleVertex,
Azure,
Groq,
Ollama,
Webui,
#[serde(rename = "webui-legacy")]
WebuiLegacy,
Lmstudio,
#[serde(rename = "lmstudio-legacy")]
LmstudioLegacy,
#[serde(rename = "lmstudio-chatcompletions")]
LmstudioChatCompletions,
Llamacpp,
Koboldcpp,
Vllm,
#[serde(rename = "hugging-face")]
HuggingFace,
Mistral,
Together,
#[serde(other)]
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EmbeddingEndpointType {
Openai,
Azure,
Cohere,
#[serde(rename = "hugging-face")]
HuggingFace,
Ollama,
#[serde(other)]
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AzureEmbeddingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub azure_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub azure_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub azure_deployment: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_endpoint_type: Option<EmbeddingEndpointType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_dim: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_chunk_size: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub azure_config: Option<AzureEmbeddingConfig>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentMemory {
#[serde(default)]
pub blocks: Vec<Block>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub file_blocks: Vec<Block>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_template: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolReference {
Id(String),
Object(serde_json::Value),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolRule {
#[serde(rename = "continue_loop")]
ContinueLoop {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
},
#[serde(rename = "exit_loop")]
ExitLoop {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
},
Terminal {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
},
MaxCountPerStep {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
max_count_limit: u32,
},
Conditional {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
default_child: Option<String>,
child_output_mapping: HashMap<String, String>,
#[serde(default)]
require_output_mapping: bool,
},
Child {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
child_tool_name: String,
},
Parent {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
parent_tool_name: String,
},
RequiredBeforeExit {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
},
Init {
tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
prompt_template: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
pub struct ConditionalToolRule {
pub tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_child: Option<String>,
#[builder(default)]
pub child_output_mapping: HashMap<String, String>,
#[serde(default)]
#[builder(default)]
pub require_output_mapping: bool,
}
impl ConditionalToolRule {
pub fn with_mapping(mut self, output: impl Into<String>, child: impl Into<String>) -> Self {
self.child_output_mapping
.insert(output.into(), child.into());
self
}
pub fn build(self) -> ToolRule {
ToolRule::Conditional {
tool_name: self.tool_name,
prompt_template: self.prompt_template,
default_child: self.default_child,
child_output_mapping: self.child_output_mapping,
require_output_mapping: self.require_output_mapping,
}
}
}
impl ToolRule {
pub fn continue_loop(tool_name: impl Into<String>) -> Self {
Self::ContinueLoop {
tool_name: tool_name.into(),
prompt_template: None,
}
}
pub fn exit_loop(tool_name: impl Into<String>) -> Self {
Self::ExitLoop {
tool_name: tool_name.into(),
prompt_template: None,
}
}
pub fn terminal(tool_name: impl Into<String>) -> Self {
Self::Terminal {
tool_name: tool_name.into(),
prompt_template: None,
}
}
pub fn max_count_per_step(tool_name: impl Into<String>, max_count_limit: u32) -> Self {
Self::MaxCountPerStep {
tool_name: tool_name.into(),
prompt_template: None,
max_count_limit,
}
}
pub fn conditional(tool_name: impl Into<String>) -> ConditionalToolRule {
ConditionalToolRule::builder()
.tool_name(tool_name.into())
.build()
}
pub fn child(tool_name: impl Into<String>, child_tool_name: impl Into<String>) -> Self {
Self::Child {
tool_name: tool_name.into(),
prompt_template: None,
child_tool_name: child_tool_name.into(),
}
}
pub fn parent(tool_name: impl Into<String>, parent_tool_name: impl Into<String>) -> Self {
Self::Parent {
tool_name: tool_name.into(),
prompt_template: None,
parent_tool_name: parent_tool_name.into(),
}
}
pub fn required_before_exit(tool_name: impl Into<String>) -> Self {
Self::RequiredBeforeExit {
tool_name: tool_name.into(),
prompt_template: None,
}
}
pub fn init(tool_name: impl Into<String>) -> Self {
Self::Init {
tool_name: tool_name.into(),
prompt_template: None,
}
}
pub fn with_prompt_template(mut self, template: impl Into<String>) -> Self {
match &mut self {
Self::ContinueLoop {
prompt_template, ..
}
| Self::ExitLoop {
prompt_template, ..
}
| Self::Terminal {
prompt_template, ..
}
| Self::MaxCountPerStep {
prompt_template, ..
}
| Self::Child {
prompt_template, ..
}
| Self::Parent {
prompt_template, ..
}
| Self::RequiredBeforeExit {
prompt_template, ..
}
| Self::Init {
prompt_template, ..
} => {
*prompt_template = Some(template.into());
}
Self::Conditional {
prompt_template, ..
} => {
*prompt_template = Some(template.into());
}
}
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, SmartDefault)]
#[serde(rename_all = "snake_case")]
pub enum ResponseFormatType {
#[default]
Text,
JsonObject,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseFormat {
#[serde(rename = "type")]
pub format_type: ResponseFormatType,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_schema: Option<serde_json::Value>,
}
impl ResponseFormat {
pub fn text() -> Self {
Self {
format_type: ResponseFormatType::Text,
json_schema: None,
}
}
pub fn json(schema: Option<serde_json::Value>) -> Self {
Self {
format_type: ResponseFormatType::JsonObject,
json_schema: schema,
}
}
pub fn json_with_schema(schema_str: &str) -> Result<Self, serde_json::Error> {
let schema = serde_json::from_str(schema_str)?;
Ok(Self {
format_type: ResponseFormatType::JsonObject,
json_schema: Some(schema),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentState {
pub id: LettaId,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(default)]
pub agent_type: AgentType,
#[serde(skip_serializing_if = "Option::is_none")]
pub llm_config: Option<LLMConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_config: Option<EmbeddingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory: Option<AgentMemory>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolReference>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sources: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_by_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_updated_by_id: Option<LettaId>,
pub created_at: Timestamp,
pub updated_at: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_rules: Option<Vec<ToolRule>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub message_ids: Vec<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi_agent_group: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_template_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identity_ids: Option<Vec<LettaId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_exec_environment_variables: Option<Vec<AgentEnvironmentVariable>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_run_completion: Option<Timestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_run_duration_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_sleeptime: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_buffer_autoclear: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CreateAgentRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_type: Option<AgentType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub llm_config: Option<LLMConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_config: Option<EmbeddingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_blocks: Option<Vec<Block>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_ids: Option<Vec<LettaId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_ids: Option<Vec<LettaId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_base_tools: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_multi_agent_tools: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_base_tool_rules: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_default_source: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_rules: Option<Vec<ToolRule>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_message_sequence: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_exec_environment_variables: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_reasoner: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_buffer_autoclear: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_ids: Option<Vec<LettaId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window_limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_chunk_size: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_reasoning_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_variables: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_template_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identity_ids: Option<Vec<LettaId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_sleeptime: Option<bool>,
}
impl CreateAgentRequest {
pub fn builder() -> CreateAgentRequestBuilder {
CreateAgentRequestBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct CreateAgentRequestBuilder {
request: CreateAgentRequest,
}
impl CreateAgentRequestBuilder {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.request.name = Some(name.into());
self
}
pub fn system(mut self, system: impl Into<String>) -> Self {
self.request.system = Some(system.into());
self
}
pub fn agent_type(mut self, agent_type: AgentType) -> Self {
self.request.agent_type = Some(agent_type);
self
}
pub fn llm_config(mut self, config: LLMConfig) -> Self {
self.request.llm_config = Some(config);
self
}
pub fn embedding_config(mut self, config: EmbeddingConfig) -> Self {
self.request.embedding_config = Some(config);
self
}
pub fn memory_blocks(mut self, blocks: Vec<Block>) -> Self {
self.request.memory_blocks = Some(blocks);
self
}
pub fn memory_block(mut self, block: Block) -> Self {
self.request
.memory_blocks
.get_or_insert_with(Vec::new)
.push(block);
self
}
pub fn tools(mut self, tools: Vec<String>) -> Self {
self.request.tools = Some(tools);
self
}
pub fn tags(mut self, tags: Vec<String>) -> Self {
self.request.tags = Some(tags);
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.request.description = Some(description.into());
self
}
pub fn timezone(mut self, timezone: impl Into<String>) -> Self {
self.request.timezone = Some(timezone.into());
self
}
pub fn include_base_tools(mut self, include: bool) -> Self {
self.request.include_base_tools = Some(include);
self
}
pub fn tool_rules(mut self, rules: Vec<ToolRule>) -> Self {
self.request.tool_rules = Some(rules);
self
}
pub fn initial_message_sequence(mut self, sequence: Vec<serde_json::Value>) -> Self {
self.request.initial_message_sequence = Some(sequence);
self
}
pub fn tool_exec_environment_variables(mut self, vars: HashMap<String, String>) -> Self {
self.request.tool_exec_environment_variables = Some(vars);
self
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.request.model = Some(model.into());
self
}
pub fn embedding(mut self, embedding: impl Into<String>) -> Self {
self.request.embedding = Some(embedding.into());
self
}
pub fn build(self) -> CreateAgentRequest {
self.request
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UpdateAgentRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_type: Option<AgentType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub llm_config: Option<LLMConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_config: Option<EmbeddingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
}
#[derive(Debug, Clone, Default)]
pub struct ImportAgentRequest {
pub append_copy_suffix: Option<bool>,
pub override_existing_tools: Option<bool>,
pub project_id: Option<LettaId>,
pub strip_messages: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentsSearchResponse {
pub agents: Vec<AgentState>,
#[serde(rename = "nextCursor")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AgentsSearchRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub search: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub combinator: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
#[serde(rename = "sortBy")]
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ascending: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ListAgentsParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub match_all_tags: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_template_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identity_id: Option<LettaId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identifier_keys: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_relationships: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ascending: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_by: Option<String>,
}
impl ListAgentsParams {
pub fn builder() -> ListAgentsParamsBuilder {
ListAgentsParamsBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct ListAgentsParamsBuilder {
params: ListAgentsParams,
}
impl ListAgentsParamsBuilder {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.params.name = Some(name.into());
self
}
pub fn tags(mut self, tags: Vec<String>) -> Self {
self.params.tags = Some(tags);
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.params.limit = Some(limit);
self
}
pub fn query_text(mut self, query: impl Into<String>) -> Self {
self.params.query_text = Some(query.into());
self
}
pub fn build(self) -> ListAgentsParams {
self.params
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_agent_serialization() {
let agent = AgentState {
id: LettaId::from_str("agent-00000000-0000-0000-0000-000000000000").unwrap(),
name: "Test Agent".to_string(),
system: Some("You are a helpful assistant".to_string()),
agent_type: AgentType::MemGPT,
llm_config: Some(LLMConfig {
model: "gpt-4".to_string(),
model_endpoint_type: ModelEndpointType::Openai,
model_endpoint: None,
context_window: Some(8192),
provider_name: None,
provider_category: None,
model_wrapper: None,
put_inner_thoughts_in_kwargs: None,
handle: None,
temperature: None,
max_tokens: None,
enable_reasoner: None,
reasoning_effort: None,
max_reasoning_tokens: None,
extra: HashMap::new(),
}),
embedding_config: None,
memory: None,
tools: vec![],
sources: vec![],
tags: vec!["test".to_string()],
description: Some("A test agent".to_string()),
metadata: None,
project_id: None,
created_by_id: None,
last_updated_by_id: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
tool_rules: None,
message_ids: vec![],
multi_agent_group: None,
template_id: None,
base_template_id: None,
identity_ids: None,
tool_exec_environment_variables: None,
organization_id: None,
timezone: None,
last_run_completion: None,
last_run_duration_ms: None,
enable_sleeptime: None,
response_format: None,
message_buffer_autoclear: None,
};
let json = serde_json::to_string(&agent).unwrap();
let deserialized: AgentState = serde_json::from_str(&json).unwrap();
assert_eq!(agent.name, deserialized.name);
}
#[test]
fn test_create_agent_request_builder() {
let request = CreateAgentRequest::builder()
.name("My Agent")
.system("You are helpful")
.agent_type(AgentType::MemGPTv2)
.memory_block(Block {
id: None,
label: "human".to_string(),
value: "The human's name is Bob.".to_string(),
limit: Some(5000),
is_template: false,
preserve_on_migration: false,
read_only: false,
description: None,
metadata: None,
name: None,
organization_id: None,
created_by_id: None,
last_updated_by_id: None,
created_at: None,
updated_at: None,
})
.memory_block(Block {
id: None,
label: "persona".to_string(),
value: "I am Sam, a helpful assistant.".to_string(),
limit: Some(5000),
is_template: false,
preserve_on_migration: false,
read_only: false,
description: None,
metadata: None,
name: None,
organization_id: None,
created_by_id: None,
last_updated_by_id: None,
created_at: None,
updated_at: None,
})
.tags(vec!["test".to_string(), "demo".to_string()])
.build();
assert_eq!(request.name.as_deref(), Some("My Agent"));
assert_eq!(request.memory_blocks.as_ref().unwrap().len(), 2);
assert_eq!(request.tags.as_ref().unwrap().len(), 2);
}
#[test]
fn test_agent_type_serialization() {
let agent_type = AgentType::Sleeptime;
let json = serde_json::to_string(&agent_type).unwrap();
assert_eq!(json, r#""sleeptime_agent""#);
let deserialized: AgentType = serde_json::from_str(&json).unwrap();
assert_eq!(agent_type, deserialized);
}
#[test]
fn test_memory_block_serialization() {
let block = Block {
id: Some(LettaId::from_str("block-550e8400-e29b-41d4-a716-446655440002").unwrap()),
label: "human".to_string(),
value: "The human's name is Alice.".to_string(),
limit: Some(1000),
is_template: false,
preserve_on_migration: true,
read_only: false,
description: Some("Human information".to_string()),
metadata: None,
name: None,
organization_id: None,
created_by_id: None,
last_updated_by_id: None,
created_at: None,
updated_at: None,
};
let json = serde_json::to_string(&block).unwrap();
let deserialized: Block = serde_json::from_str(&json).unwrap();
assert_eq!(block.label, deserialized.label);
assert_eq!(block.value, deserialized.value);
}
#[test]
fn test_list_agents_params_builder() {
let params = ListAgentsParams::builder()
.name("test")
.tags(vec!["production".to_string()])
.limit(50)
.query_text("search term")
.build();
assert_eq!(params.name.as_deref(), Some("test"));
assert_eq!(params.limit, Some(50));
}
#[test]
fn test_embedding_endpoint_type_serialization() {
let endpoint_type = EmbeddingEndpointType::Openai;
let json = serde_json::to_string(&endpoint_type).unwrap();
assert_eq!(json, r#""openai""#);
let deserialized: EmbeddingEndpointType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, EmbeddingEndpointType::Openai);
}
#[test]
fn test_model_endpoint_type_serialization() {
let endpoint_type = ModelEndpointType::Anthropic;
let json = serde_json::to_string(&endpoint_type).unwrap();
assert_eq!(json, r#""anthropic""#);
let deserialized: ModelEndpointType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ModelEndpointType::Anthropic);
}
#[test]
fn test_azure_embedding_config_flattening() {
let config = EmbeddingConfig {
embedding_model: Some("text-embedding-ada-002".to_string()),
embedding_endpoint_type: Some(EmbeddingEndpointType::Azure),
embedding_endpoint: Some("https://myazure.openai.azure.com".to_string()),
embedding_dim: Some(1536),
embedding_chunk_size: Some(300),
handle: None,
azure_config: Some(AzureEmbeddingConfig {
azure_endpoint: Some("https://myazure.openai.azure.com".to_string()),
azure_version: Some("2023-05-15".to_string()),
azure_deployment: Some("my-deployment".to_string()),
}),
extra: HashMap::new(),
};
let json = serde_json::to_string(&config).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["azure_endpoint"], "https://myazure.openai.azure.com");
assert_eq!(parsed["azure_version"], "2023-05-15");
assert_eq!(parsed["azure_deployment"], "my-deployment");
let deserialized: EmbeddingConfig = serde_json::from_str(&json).unwrap();
assert_eq!(
deserialized
.azure_config
.as_ref()
.unwrap()
.azure_version
.as_deref(),
Some("2023-05-15")
);
}
#[test]
fn test_create_agent_request_shorthand_fields() {
let request = CreateAgentRequest {
name: Some("Test Agent".to_string()),
model: Some("gpt-4".to_string()),
embedding: Some("text-embedding-ada-002".to_string()),
context_window_limit: Some(8192),
embedding_chunk_size: Some(512),
max_tokens: Some(2048),
from_template: Some("customer-support".to_string()),
template: Some(true),
..Default::default()
};
let json = serde_json::to_string(&request).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["model"], "gpt-4");
assert_eq!(parsed["embedding"], "text-embedding-ada-002");
assert_eq!(parsed["context_window_limit"], 8192);
assert_eq!(parsed["template"], true);
}
}