1#[macro_export]
31macro_rules! agent {
32 (@build $b:expr $(,)?) => { $b.build() };
34
35 (@build $b:expr, model: $v:expr, $($rest:tt)*) => {
36 $crate::agent!(@build $b.model($v), $($rest)*)
37 };
38 (@build $b:expr, system_prompt: $v:expr, $($rest:tt)*) => {
39 $crate::agent!(@build $b.system_prompt($v), $($rest)*)
40 };
41 (@build $b:expr, name: $v:expr, $($rest:tt)*) => {
42 $crate::agent!(@build $b.name($v), $($rest)*)
43 };
44 (@build $b:expr, max_iterations: $v:expr, $($rest:tt)*) => {
45 $crate::agent!(@build $b.max_iterations($v), $($rest)*)
46 };
47 (@build $b:expr, token_limit: $v:expr, $($rest:tt)*) => {
48 $crate::agent!(@build $b.token_limit($v), $($rest)*)
49 };
50 (@build $b:expr, llm_config: $v:expr, $($rest:tt)*) => {
51 $crate::agent!(@build $b.llm_config($v), $($rest)*)
52 };
53 (@build $b:expr, session_id: $v:expr, $($rest:tt)*) => {
54 $crate::agent!(@build $b.session_id($v), $($rest)*)
55 };
56 (@build $b:expr, conversation_id: $v:expr, $($rest:tt)*) => {
57 $crate::agent!(@build $b.conversation_id($v), $($rest)*)
58 };
59 (@build $b:expr, enable_memory: $v:expr, $($rest:tt)*) => {
60 $crate::agent!(@build { if $v { $b.enable_memory() } else { $b } }, $($rest)*)
61 };
62 (@build $b:expr, enable_cot: $v:expr, $($rest:tt)*) => {
63 $crate::agent!(@build { if $v { $b.enable_cot() } else { $b.disable_cot() } }, $($rest)*)
64 };
65 (@build $b:expr, permission_policy: $v:expr, $($rest:tt)*) => {
66 $crate::agent!(@build $b.permission_policy(::std::sync::Arc::new($v)), $($rest)*)
67 };
68 (@build $b:expr, audit_logger: $v:expr, $($rest:tt)*) => {
69 $crate::agent!(@build $b.audit_logger(::std::sync::Arc::new($v)), $($rest)*)
70 };
71 (@build $b:expr, tools: [$($t:expr),* $(,)?], $($rest:tt)*) => {
72 $crate::agent!(@build {
73 let mut __b = $b.enable_tools();
74 $( __b = __b.tool(Box::new($t)); )*
75 __b
76 }, $($rest)*)
77 };
78 (@build $b:expr, callbacks: [$($c:expr),* $(,)?], $($rest:tt)*) => {
79 $crate::agent!(@build {
80 let mut __b = $b;
81 $( __b = __b.callback(::std::sync::Arc::new($c)); )*
82 __b
83 }, $($rest)*)
84 };
85 (@build $b:expr, guards: [$($g:expr),* $(,)?], $($rest:tt)*) => {
86 $crate::agent!(@build {
87 let mut __b = $b;
88 $( __b = __b.guard(::std::sync::Arc::new($g)); )*
89 __b
90 }, $($rest)*)
91 };
92
93 ( $($body:tt)* ) => {
95 $crate::agent!(@build $crate::agent::ReactAgentBuilder::new(), $($body)*)
96 };
97}
98
99#[macro_export]
118macro_rules! messages {
119 ( $( $role:ident($content:expr) ),* $(,)? ) => {
120 vec![
121 $( $crate::llm::types::Message::$role($content.to_string()) ),*
122 ]
123 };
124}
125
126#[macro_export]
139macro_rules! tool_params {
140 ( $( $name:literal => $spec:tt ),* $(,)? ) => {{
141 let mut __properties = ::serde_json::Map::new();
142 let mut __required: Vec<&str> = Vec::new();
143 $( $crate::__tool_param_field!(__properties, __required, $name, $spec); )*
144 ::serde_json::json!({
145 "type": "object",
146 "properties": ::serde_json::Value::Object(__properties),
147 "required": __required,
148 })
149 }};
150}
151
152#[doc(hidden)]
153#[macro_export]
154macro_rules! __tool_param_field {
155 ($props:expr, $req:expr, $name:literal, ($ty:ident, required, $desc:literal)) => {
156 let mut __p = ::serde_json::Map::new();
157 __p.insert(
158 "type".to_string(),
159 ::serde_json::Value::String(stringify!($ty).to_string()),
160 );
161 __p.insert(
162 "description".to_string(),
163 ::serde_json::Value::String($desc.to_string()),
164 );
165 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
166 $req.push($name);
167 };
168 ($props:expr, $req:expr, $name:literal, ($ty:ident, required)) => {
169 let mut __p = ::serde_json::Map::new();
170 __p.insert(
171 "type".to_string(),
172 ::serde_json::Value::String(stringify!($ty).to_string()),
173 );
174 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
175 $req.push($name);
176 };
177 ($props:expr, $req:expr, $name:literal, ($ty:ident, $desc:literal)) => {
178 let mut __p = ::serde_json::Map::new();
179 __p.insert(
180 "type".to_string(),
181 ::serde_json::Value::String(stringify!($ty).to_string()),
182 );
183 __p.insert(
184 "description".to_string(),
185 ::serde_json::Value::String($desc.to_string()),
186 );
187 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
188 };
189 ($props:expr, $req:expr, $name:literal, ($ty:ident)) => {
190 let mut __p = ::serde_json::Map::new();
191 __p.insert(
192 "type".to_string(),
193 ::serde_json::Value::String(stringify!($ty).to_string()),
194 );
195 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
196 };
197}
198
199#[macro_export]
214macro_rules! chat_request {
215 ( messages: [$( $role:ident($content:expr) ),* $(,)?] $(, $key:ident : $val:expr)* $(,)? ) => {{
216 #[allow(unused_mut)]
217 let mut req = $crate::llm::ChatRequest {
218 messages: vec![
219 $( $crate::llm::types::Message::$role($content.to_string()) ),*
220 ],
221 ..Default::default()
222 };
223 $( $crate::__chat_request_field!(req, $key, $val); )*
224 req
225 }};
226}
227
228#[doc(hidden)]
229#[macro_export]
230macro_rules! __chat_request_field {
231 ($req:expr, temperature, $v:expr) => {
232 $req.temperature = Some($v);
233 };
234 ($req:expr, max_tokens, $v:expr) => {
235 $req.max_tokens = Some($v as u32);
236 };
237 ($req:expr, tool_choice, $v:expr) => {
238 $req.tool_choice = Some($v.to_string());
239 };
240}
241
242#[cfg(test)]
243mod tests {
244 use crate::llm::types::Message;
245
246 #[test]
247 fn messages_macro_basic() {
248 let msgs = messages![
249 system("You are an assistant"),
250 user("Hello"),
251 assistant("Hello! How can I help you?"),
252 ];
253
254 assert_eq!(msgs.len(), 3);
255 assert_eq!(msgs[0].role, "system");
256 assert_eq!(msgs[0].content.as_text_ref(), Some("You are an assistant"));
257 assert_eq!(msgs[1].role, "user");
258 assert_eq!(msgs[2].role, "assistant");
259 }
260
261 #[test]
262 fn messages_macro_single() {
263 let msgs = messages![user("hello")];
264 assert_eq!(msgs.len(), 1);
265 assert_eq!(msgs[0].role, "user");
266 }
267
268 #[test]
269 fn messages_macro_empty() {
270 let msgs: Vec<Message> = messages![];
271 assert!(msgs.is_empty());
272 }
273
274 #[test]
275 fn tool_params_macro_basic() {
276 let schema = tool_params! {
277 "expression" => (string, required, "Math expression"),
278 "precision" => (number, "Decimal precision"),
279 };
280
281 let obj = schema.as_object().unwrap();
282 assert_eq!(obj["type"], "object");
283
284 let props = obj["properties"].as_object().unwrap();
285 assert!(props.contains_key("expression"));
286 assert!(props.contains_key("precision"));
287
288 let expr_prop = props["expression"].as_object().unwrap();
289 assert_eq!(expr_prop["type"], "string");
290 assert_eq!(expr_prop["description"], "Math expression");
291
292 let required = obj["required"].as_array().unwrap();
293 assert_eq!(required.len(), 1);
294 assert_eq!(required[0], "expression");
295 }
296
297 #[test]
298 fn tool_params_macro_all_required() {
299 let schema = tool_params! {
300 "a" => (number, required, "param a"),
301 "b" => (number, required, "param b"),
302 };
303 let required = schema["required"].as_array().unwrap();
304 assert_eq!(required.len(), 2);
305 }
306
307 #[test]
308 fn tool_params_macro_none_required() {
309 let schema = tool_params! {
310 "hint" => (string, "optional hint"),
311 };
312 let required = schema["required"].as_array().unwrap();
313 assert!(required.is_empty());
314 }
315
316 #[test]
317 fn chat_request_macro_basic() {
318 let req = chat_request!(
319 messages: [system("You are an assistant"), user("Hello")],
320 temperature: 0.7,
321 max_tokens: 2048,
322 );
323
324 assert_eq!(req.messages.len(), 2);
325 assert_eq!(req.messages[0].role, "system");
326 assert_eq!(req.temperature, Some(0.7));
327 assert_eq!(req.max_tokens, Some(2048));
328 }
329
330 #[test]
331 fn chat_request_macro_no_options() {
332 let req = chat_request!(
333 messages: [user("hello")],
334 );
335
336 assert_eq!(req.messages.len(), 1);
337 assert_eq!(req.temperature, None);
338 assert_eq!(req.max_tokens, None);
339 }
340
341 #[test]
342 fn agent_macro_basic() {
343 let result = agent! {
344 model: "test-model",
345 system_prompt: "You are an assistant",
346 };
347 assert!(result.is_ok());
348 }
349
350 #[test]
351 fn agent_macro_with_tools_and_options() {
352 use crate::tools::builtin::answer::FinalAnswerTool;
353
354 let result = agent! {
355 model: "test-model",
356 system_prompt: "You are a calculation assistant",
357 name: "calc",
358 tools: [FinalAnswerTool],
359 max_iterations: 5,
360 };
361 assert!(result.is_ok());
362
363 let agent = result.unwrap();
364 assert!(agent.tool_names().contains(&"final_answer"));
365 }
366}