1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::ingredient::Ingredient;
11
12#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
14#[serde(rename_all = "lowercase")]
15pub enum AgentMode {
16 #[default]
18 Managed,
19 External,
21 #[serde(other)]
23 Unknown,
24}
25
26impl std::fmt::Display for AgentMode {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 AgentMode::Managed => write!(f, "managed"),
30 AgentMode::External => write!(f, "external"),
31 AgentMode::Unknown => write!(f, "unknown"),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Guardrail {
39 pub kind: String,
41 #[serde(default = "default_guardrail_stage")]
43 pub stage: String,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub config: Option<serde_json::Value>,
47}
48
49fn default_guardrail_stage() -> String {
50 "pre".into()
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Agent {
56 pub id: String,
58
59 pub name: String,
61
62 pub version: String,
64
65 pub description: String,
67
68 pub framework: AgentFramework,
70
71 #[serde(default)]
73 pub mode: AgentMode,
74
75 #[serde(default)]
77 pub model_provider: String,
78
79 #[serde(default)]
81 pub model_name: String,
82
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub backup_provider: Option<String>,
86
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub backup_model: Option<String>,
90
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub system_prompt: Option<String>,
94
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub max_turns: Option<u32>,
98
99 #[serde(default)]
101 pub skills: Vec<String>,
102
103 #[serde(default)]
105 pub guardrails: Vec<Guardrail>,
106
107 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub a2a_endpoint: Option<String>,
110
111 #[serde(default)]
113 pub ingredients: Vec<Ingredient>,
114
115 #[serde(default)]
117 pub tags: Vec<String>,
118
119 pub status: AgentStatus,
121
122 #[serde(default, alias = "kitchen", skip_serializing_if = "Option::is_none")]
125 pub kitchen_id: Option<String>,
126
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub resolved_config: Option<serde_json::Value>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub created_by: Option<String>,
134
135 pub created_at: DateTime<Utc>,
137
138 pub updated_at: DateTime<Utc>,
140
141 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub metadata: Option<serde_json::Value>,
144}
145
146impl Agent {
147 pub fn builder(name: impl Into<String>) -> AgentBuilder {
149 AgentBuilder::new(name)
150 }
151
152 pub fn qualified_name(&self) -> String {
154 format!("{}@{}", self.name, self.version)
155 }
156}
157
158#[derive(Debug)]
160pub struct AgentBuilder {
161 name: String,
162 version: String,
163 description: String,
164 framework: AgentFramework,
165 mode: AgentMode,
166 model_provider: String,
167 model_name: String,
168 backup_provider: Option<String>,
169 backup_model: Option<String>,
170 system_prompt: Option<String>,
171 max_turns: Option<u32>,
172 skills: Vec<String>,
173 guardrails: Vec<Guardrail>,
174 a2a_endpoint: Option<String>,
175 ingredients: Vec<Ingredient>,
176 tags: Vec<String>,
177 kitchen_id: Option<String>,
178 metadata: Option<serde_json::Value>,
179}
180
181impl AgentBuilder {
182 pub fn new(name: impl Into<String>) -> Self {
183 Self {
184 name: name.into(),
185 version: "0.1.0".into(),
186 description: String::new(),
187 framework: AgentFramework::Custom,
188 mode: AgentMode::default(),
189 model_provider: String::new(),
190 model_name: String::new(),
191 backup_provider: None,
192 backup_model: None,
193 system_prompt: None,
194 max_turns: None,
195 skills: Vec::new(),
196 guardrails: Vec::new(),
197 a2a_endpoint: None,
198 ingredients: Vec::new(),
199 tags: Vec::new(),
200 kitchen_id: None,
201 metadata: None,
202 }
203 }
204
205 pub fn version(mut self, version: impl Into<String>) -> Self {
206 self.version = version.into();
207 self
208 }
209
210 pub fn description(mut self, desc: impl Into<String>) -> Self {
211 self.description = desc.into();
212 self
213 }
214
215 pub fn framework(mut self, framework: AgentFramework) -> Self {
216 self.framework = framework;
217 self
218 }
219
220 pub fn ingredient(mut self, ingredient: Ingredient) -> Self {
221 self.ingredients.push(ingredient);
222 self
223 }
224
225 pub fn tag(mut self, tag: impl Into<String>) -> Self {
226 self.tags.push(tag.into());
227 self
228 }
229
230 pub fn kitchen(mut self, kitchen_id: impl Into<String>) -> Self {
231 self.kitchen_id = Some(kitchen_id.into());
232 self
233 }
234
235 pub fn mode(mut self, mode: AgentMode) -> Self {
236 self.mode = mode;
237 self
238 }
239
240 pub fn model_provider(mut self, provider: impl Into<String>) -> Self {
241 self.model_provider = provider.into();
242 self
243 }
244
245 pub fn model_name(mut self, name: impl Into<String>) -> Self {
246 self.model_name = name.into();
247 self
248 }
249
250 pub fn backup_provider(mut self, provider: impl Into<String>) -> Self {
251 self.backup_provider = Some(provider.into());
252 self
253 }
254
255 pub fn backup_model(mut self, model: impl Into<String>) -> Self {
256 self.backup_model = Some(model.into());
257 self
258 }
259
260 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
261 self.system_prompt = Some(prompt.into());
262 self
263 }
264
265 pub fn max_turns(mut self, turns: u32) -> Self {
266 self.max_turns = Some(turns);
267 self
268 }
269
270 pub fn skill(mut self, s: impl Into<String>) -> Self {
271 self.skills.push(s.into());
272 self
273 }
274
275 pub fn guardrail(mut self, g: Guardrail) -> Self {
276 self.guardrails.push(g);
277 self
278 }
279
280 pub fn a2a_endpoint(mut self, endpoint: impl Into<String>) -> Self {
281 self.a2a_endpoint = Some(endpoint.into());
282 self
283 }
284
285 pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
286 self.metadata = Some(metadata);
287 self
288 }
289
290 pub fn build(self) -> Agent {
291 let now = Utc::now();
292 Agent {
293 id: Uuid::new_v4().to_string(),
294 name: self.name,
295 version: self.version,
296 description: self.description,
297 framework: self.framework,
298 mode: self.mode,
299 model_provider: self.model_provider,
300 model_name: self.model_name,
301 backup_provider: self.backup_provider,
302 backup_model: self.backup_model,
303 system_prompt: self.system_prompt,
304 max_turns: self.max_turns,
305 skills: self.skills,
306 guardrails: self.guardrails,
307 a2a_endpoint: self.a2a_endpoint,
308 ingredients: self.ingredients,
309 tags: self.tags,
310 status: AgentStatus::Draft,
311 kitchen_id: self.kitchen_id,
312 resolved_config: None,
313 created_by: None,
314 created_at: now,
315 updated_at: now,
316 metadata: self.metadata,
317 }
318 }
319}
320
321#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
323#[serde(rename_all = "lowercase")]
324pub enum AgentFramework {
325 #[serde(alias = "lang-graph", alias = "langgraph")]
327 Langchain,
328 #[serde(alias = "crew-ai")]
330 Crewai,
331 #[serde(alias = "openai-sdk", alias = "open-ai-sdk")]
333 Openai,
334 #[serde(alias = "auto-gen")]
336 Autogen,
337 Managed,
339 #[default]
341 #[serde(other)]
342 Custom,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
347#[serde(rename_all = "lowercase")]
348pub enum AgentStatus {
349 Draft,
351 Baking,
353 Ready,
355 Cooled,
357 Burnt,
359 Retired,
361}
362
363impl std::fmt::Display for AgentStatus {
364 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365 match self {
366 AgentStatus::Draft => write!(f, "π‘ draft"),
367 AgentStatus::Baking => write!(f, "π₯ baking"),
368 AgentStatus::Ready => write!(f, "π’ ready"),
369 AgentStatus::Cooled => write!(f, "βΈοΈ cooled"),
370 AgentStatus::Burnt => write!(f, "π΄ burnt"),
371 AgentStatus::Retired => write!(f, "β« retired"),
372 }
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::ingredient::Ingredient;
380
381 #[test]
382 fn test_agent_builder() {
383 let agent = Agent::builder("summarizer")
384 .version("1.0.0")
385 .description("Summarizes documents")
386 .framework(AgentFramework::Langchain)
387 .ingredient(Ingredient::model("gpt-4o").provider("azure-openai").build())
388 .tag("nlp")
389 .tag("summarization")
390 .build();
391
392 assert_eq!(agent.name, "summarizer");
393 assert_eq!(agent.version, "1.0.0");
394 assert_eq!(agent.qualified_name(), "summarizer@1.0.0");
395 assert_eq!(agent.status, AgentStatus::Draft);
396 assert_eq!(agent.ingredients.len(), 1);
397 assert_eq!(agent.tags, vec!["nlp", "summarization"]);
398 }
399}