use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::ingredient::Ingredient;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agent {
pub id: String,
pub name: String,
pub version: String,
pub description: String,
pub framework: AgentFramework,
pub ingredients: Vec<Ingredient>,
#[serde(default)]
pub tags: Vec<String>,
pub status: AgentStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub kitchen_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_by: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl Agent {
pub fn builder(name: impl Into<String>) -> AgentBuilder {
AgentBuilder::new(name)
}
pub fn qualified_name(&self) -> String {
format!("{}@{}", self.name, self.version)
}
}
#[derive(Debug)]
pub struct AgentBuilder {
name: String,
version: String,
description: String,
framework: AgentFramework,
ingredients: Vec<Ingredient>,
tags: Vec<String>,
kitchen_id: Option<String>,
metadata: Option<serde_json::Value>,
}
impl AgentBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
version: "0.1.0".into(),
description: String::new(),
framework: AgentFramework::Custom,
ingredients: Vec::new(),
tags: Vec::new(),
kitchen_id: None,
metadata: None,
}
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn framework(mut self, framework: AgentFramework) -> Self {
self.framework = framework;
self
}
pub fn ingredient(mut self, ingredient: Ingredient) -> Self {
self.ingredients.push(ingredient);
self
}
pub fn tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn kitchen(mut self, kitchen_id: impl Into<String>) -> Self {
self.kitchen_id = Some(kitchen_id.into());
self
}
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn build(self) -> Agent {
let now = Utc::now();
Agent {
id: Uuid::new_v4().to_string(),
name: self.name,
version: self.version,
description: self.description,
framework: self.framework,
ingredients: self.ingredients,
tags: self.tags,
status: AgentStatus::Draft,
kitchen_id: self.kitchen_id,
created_by: None,
created_at: now,
updated_at: now,
metadata: self.metadata,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum AgentFramework {
LangGraph,
CrewAi,
OpenAiSdk,
AutoGen,
AgentFramework,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum AgentStatus {
Draft,
Baking,
Ready,
Cooled,
Burnt,
Retired,
}
impl std::fmt::Display for AgentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AgentStatus::Draft => write!(f, "🟡 draft"),
AgentStatus::Baking => write!(f, "🔥 baking"),
AgentStatus::Ready => write!(f, "🟢 ready"),
AgentStatus::Cooled => write!(f, "⏸️ cooled"),
AgentStatus::Burnt => write!(f, "🔴 burnt"),
AgentStatus::Retired => write!(f, "⚫ retired"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ingredient::Ingredient;
#[test]
fn test_agent_builder() {
let agent = Agent::builder("summarizer")
.version("1.0.0")
.description("Summarizes documents")
.framework(AgentFramework::LangGraph)
.ingredient(Ingredient::model("gpt-4o").provider("azure-openai").build())
.tag("nlp")
.tag("summarization")
.build();
assert_eq!(agent.name, "summarizer");
assert_eq!(agent.version, "1.0.0");
assert_eq!(agent.qualified_name(), "summarizer@1.0.0");
assert_eq!(agent.status, AgentStatus::Draft);
assert_eq!(agent.ingredients.len(), 1);
assert_eq!(agent.tags, vec!["nlp", "summarization"]);
}
}