lumos_macro/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use syn::{Expr, Ident, LitStr, Token, parse::{Parse, ParseStream}};
4// use syn::spanned::Spanned; // 暂时未使用
5
6mod parser;
7mod tool_macro;
8mod agent_macro;
9mod workflow;
10mod rag;
11mod eval;
12mod mcp;
13mod agent;
14mod tools;
15mod lumos;
16
17/// Macro for defining a tool in a simplified way
18/// 
19/// # Example
20/// ```
21/// use lumos_macro::tool;
22/// 
23/// #[tool(
24///     name = "calculator",
25///     description = "Performs basic math operations"
26/// )]
27/// fn calculator(
28///     #[parameter(
29///         name = "operation",
30///         description = "The operation to perform: add, subtract, multiply, divide",
31///         r#type = "string", 
32///         required = true
33///     )]
34///     operation: String,
35///     
36///     #[parameter(
37///         name = "a",
38///         description = "First number",
39///         r#type = "number",
40///         required = true
41///     )]
42///     a: f64,
43///     
44///     #[parameter(
45///         name = "b",
46///         description = "Second number",
47///         r#type = "number",
48///         required = true
49///     )]
50///     b: f64,
51/// ) -> Result<serde_json::Value, lumosai_core::Error> {
52///     // Function implementation
53/// }
54/// ```
55#[proc_macro_attribute]
56pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream {
57    tool_macro::tool_macro(attr, item)
58}
59
60struct ToolAttributes {
61    name: LitStr,
62    description: LitStr,
63}
64
65impl Parse for ToolAttributes {
66    fn parse(input: ParseStream) -> syn::Result<Self> {
67        let mut name = None;
68        let mut description = None;
69        
70        while !input.is_empty() {
71            let ident: Ident = input.parse()?;
72            input.parse::<Token![=]>()?;
73            
74            if ident == "name" {
75                name = Some(input.parse()?);
76            } else if ident == "description" {
77                description = Some(input.parse()?);
78            } else {
79                return Err(syn::Error::new(ident.span(), "Unknown attribute"));
80            }
81            
82            // Allow trailing comma
83            if input.peek(Token![,]) {
84                input.parse::<Token![,]>()?;
85            }
86        }
87        
88        let name = name.ok_or_else(|| syn::Error::new(input.span(), "Missing name attribute"))?;
89        let description = description.ok_or_else(|| syn::Error::new(input.span(), "Missing description attribute"))?;
90        
91        Ok(ToolAttributes { name, description })
92    }
93}
94
95struct ParameterAttributes {
96    name: LitStr,
97    description: LitStr,
98    type_: LitStr,
99    required: bool,
100}
101
102impl Parse for ParameterAttributes {
103    fn parse(input: ParseStream) -> syn::Result<Self> {
104        let mut name = None;
105        let mut description = None;
106        let mut type_ = None;
107        let mut required = None;
108        
109        let content;
110        syn::parenthesized!(content in input);
111        let input = &content;
112        
113        while !input.is_empty() {
114            let ident: Ident = input.parse()?;
115            input.parse::<Token![=]>()?;
116            
117            if ident == "name" {
118                name = Some(input.parse()?);
119            } else if ident == "description" {
120                description = Some(input.parse()?);
121            } else if ident == "r#type" || ident == "type" {
122                type_ = Some(input.parse()?);
123            } else if ident == "required" {
124                let expr: Expr = input.parse()?;
125                if let Expr::Lit(lit) = &expr {
126                    if let syn::Lit::Bool(b) = &lit.lit {
127                        required = Some(b.value);
128                    }
129                }
130            } else {
131                return Err(syn::Error::new(ident.span(), "Unknown parameter attribute"));
132            }
133            
134            // Allow trailing comma
135            if input.peek(Token![,]) {
136                input.parse::<Token![,]>()?;
137            }
138        }
139        
140        let name = name.ok_or_else(|| syn::Error::new(input.span(), "Missing name attribute"))?;
141        let description = description.ok_or_else(|| syn::Error::new(input.span(), "Missing description attribute"))?;
142        let type_ = type_.ok_or_else(|| syn::Error::new(input.span(), "Missing type attribute"))?;
143        let required = required.unwrap_or(false);
144        
145        Ok(ParameterAttributes { name, description, type_, required })
146    }
147}
148
149/// Macro for defining an agent with tools in a simplified way
150/// 
151/// # Example
152/// ```
153/// use lumos_macro::agent_attr;
154/// 
155/// #[agent_attr(
156///     name = "math_agent",
157///     instructions = "You are a helpful math assistant that can perform calculations.",
158///     model = "gpt-4"
159/// )]
160/// struct MathAgent {
161///     #[tool]
162///     calculator: CalculatorTool,
163///     
164///     #[tool]
165///     unit_converter: UnitConverterTool,
166/// }
167/// ```
168#[proc_macro_attribute]
169pub fn agent_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
170    agent_macro::agent_impl(attr, item)
171}
172
173struct AgentAttributes {
174    name: LitStr,
175    instructions: LitStr,
176    model: LitStr,
177}
178
179impl Parse for AgentAttributes {
180    fn parse(input: ParseStream) -> syn::Result<Self> {
181        let mut name = None;
182        let mut instructions = None;
183        let mut model = None;
184        
185        while !input.is_empty() {
186            let ident: Ident = input.parse()?;
187            input.parse::<Token![=]>()?;
188            
189            if ident == "name" {
190                name = Some(input.parse()?);
191            } else if ident == "instructions" {
192                instructions = Some(input.parse()?);
193            } else if ident == "model" {
194                model = Some(input.parse()?);
195            } else {
196                return Err(syn::Error::new(ident.span(), "Unknown attribute"));
197            }
198            
199            // Allow trailing comma
200            if input.peek(Token![,]) {
201                input.parse::<Token![,]>()?;
202            }
203        }
204        
205        let name = name.ok_or_else(|| syn::Error::new(input.span(), "Missing name attribute"))?;
206        let instructions = instructions.ok_or_else(|| syn::Error::new(input.span(), "Missing instructions attribute"))?;
207        let model = model.ok_or_else(|| syn::Error::new(input.span(), "Missing model attribute"))?;
208        
209        Ok(AgentAttributes { name, instructions, model })
210    }
211}
212
213/// A convenient derive macro for defining a model adapter
214/// 
215/// # Example
216/// ```
217/// use lumos_macro::LlmAdapter;
218/// 
219/// #[derive(LlmAdapter)]
220/// struct OpenAIAdapter {
221///     api_key: String,
222///     model: String,
223/// }
224/// ```
225#[proc_macro_derive(LlmAdapter)]
226pub fn derive_llm_adapter(input: TokenStream) -> TokenStream {
227    agent_macro::derive_llm_adapter(input)
228}
229
230/// A macro for quick tool execution setup
231///
232/// # Example
233/// ```
234/// lumos_execute_tool! {
235///     tool: calculator,
236///     params: {
237///         "operation": "add",
238///         "a": 10.5,
239///         "b": 20.3
240///     }
241/// }
242/// ```
243#[proc_macro]
244pub fn lumos_execute_tool(input: TokenStream) -> TokenStream {
245    tool_macro::lumos_execute_tool(input)
246}
247
248struct ToolExecuteArgs {
249    tool: Expr,
250    params: Expr,
251}
252
253impl Parse for ToolExecuteArgs {
254    fn parse(input: ParseStream) -> syn::Result<Self> {
255        let mut tool = None;
256        let mut params = None;
257        
258        while !input.is_empty() {
259            let ident: Ident = input.parse()?;
260            input.parse::<Token![:]>()?;
261            
262            if ident == "tool" {
263                tool = Some(input.parse()?);
264            } else if ident == "params" {
265                params = Some(input.parse()?);
266            } else {
267                return Err(syn::Error::new(ident.span(), "Unknown field"));
268            }
269            
270            // Allow trailing comma
271            if input.peek(Token![,]) {
272                input.parse::<Token![,]>()?;
273            }
274        }
275        
276        let tool = tool.ok_or_else(|| syn::Error::new(input.span(), "Missing tool field"))?;
277        let params = params.ok_or_else(|| syn::Error::new(input.span(), "Missing params field"))?;
278        
279        Ok(ToolExecuteArgs { tool, params })
280    }
281}
282
283/// 创建一个工作流定义,参考Mastra的工作流API设计
284/// 
285/// # 示例
286/// 
287/// ```rust
288/// workflow! {
289///     name: "content_creation",
290///     description: "创建高质量的内容",
291///     steps: {
292///         {
293///             name: "research",
294///             agent: researcher,
295///             instructions: "进行深入的主题研究",
296///         },
297///         {
298///             name: "writing",
299///             agent: writer,
300///             instructions: "将研究结果整理成文章",
301///             when: { completed("research") },
302///         }
303///     }
304/// }
305/// ```
306#[proc_macro]
307pub fn workflow(input: TokenStream) -> TokenStream {
308    workflow::workflow_impl(input)
309}
310
311/// 创建一个RAG管道,参考Mastra的RAG原语API设计
312/// 
313/// # 示例
314/// 
315/// ```rust
316/// rag_pipeline! {
317///     name: "knowledge_base",
318///     
319///     source: DocumentSource::from_directory("./docs"),
320///     
321///     pipeline: {
322///         chunk: {
323///             chunk_size: 1000,
324///             chunk_overlap: 200
325///         },
326///         
327///         embed: {
328///             model: "text-embedding-3-small",
329///             dimensions: 1536
330///         },
331///         
332///         store: {
333///             db: "pgvector",
334///             collection: "embeddings"
335///         }
336///     },
337///     
338///     query_pipeline: {
339///         rerank: true,
340///         top_k: 5,
341///         filter: r#"{ "type": { "$in": ["article", "faq"] } }"#
342///     }
343/// }
344/// ```
345#[proc_macro]
346pub fn rag_pipeline(input: TokenStream) -> TokenStream {
347    rag::rag_pipeline_impl(input)
348}
349
350/// 创建一个评估套件,参考Mastra的Eval框架
351/// 
352/// # 示例
353/// 
354/// ```rust
355/// eval_suite! {
356///     name: "agent_performance",
357///     
358///     metrics: {
359///         accuracy: AccuracyMetric::new(0.8),
360///         relevance: RelevanceMetric::new(0.7),
361///         completeness: CompletenessMetric::new(0.6)
362///     },
363///     
364///     test_cases: {
365///         basic_queries: "./tests/basic_queries.json",
366///         complex_queries: "./tests/complex_queries.json"
367///     },
368///     
369///     reporting: {
370///         format: "html",
371///         output: "./reports/eval_results.html"
372///     }
373/// }
374/// ```
375#[proc_macro]
376pub fn eval_suite(input: TokenStream) -> TokenStream {
377    eval::eval_suite_impl(input)
378}
379
380/// 创建一个MCP客户端配置,参考Mastra的MCP支持
381/// 
382/// # 示例
383/// 
384/// ```rust
385/// mcp_client! {
386///     discovery: {
387///         endpoints: ["https://tools.example.com/mcp", "https://api.mcp.run"],
388///         auto_register: true
389///     },
390///     
391///     tools: {
392///         data_analysis: {
393///             enabled: true,
394///             auth: {
395///                 type: "api_key",
396///                 key_env: "DATA_ANALYSIS_API_KEY"
397///             }
398///         },
399///         image_processing: {
400///             enabled: true,
401///             rate_limit: 100
402///         }
403///     }
404/// }
405/// ```
406#[proc_macro]
407pub fn mcp_client(input: TokenStream) -> TokenStream {
408    mcp::mcp_client_impl(input)
409}
410
411/// 创建一个代理定义,参考Mastra的Agent API设计
412/// 
413/// # 示例
414/// 
415/// ```rust
416/// agent! {
417///     name: "research_assistant",
418///     instructions: "你是一个专业的研究助手,擅长收集和整理信息。",
419///     
420///     llm: {
421///         provider: openai_adapter,
422///         model: "gpt-4"
423///     },
424///     
425///     memory: {
426///         store_type: "buffer",
427///         capacity: 10
428///     },
429///     
430///     tools: {
431///         search_tool,
432///         calculator_tool: { precision: 2 },
433///         web_browser: { javascript: true, screenshots: true }
434///     }
435/// }
436/// ```
437#[proc_macro]
438pub fn agent(input: TokenStream) -> TokenStream {
439    agent::agent(input)
440}
441
442/// 一次性定义多个工具,参考Mastra的工具API设计
443/// 
444/// # 示例
445/// 
446/// ```rust
447/// tools! {
448///     {
449///         name: "calculator",
450///         description: "执行基本的数学运算",
451///         parameters: {
452///             {
453///                 name: "operation",
454///                 description: "要执行的操作: add, subtract, multiply, divide",
455///                 type: "string",
456///                 required: true
457///             },
458///             {
459///                 name: "a",
460///                 description: "第一个数字",
461///                 type: "number",
462///                 required: true
463///             },
464///             {
465///                 name: "b",
466///                 description: "第二个数字",
467///                 type: "number",
468///                 required: true
469///             }
470///         },
471///         handler: |params| async move {
472///             let operation = params.get("operation").unwrap().as_str().unwrap();
473///             let a = params.get("a").unwrap().as_f64().unwrap();
474///             let b = params.get("b").unwrap().as_f64().unwrap();
475///             
476///             let result = match operation {
477///                 "add" => a + b,
478///                 "subtract" => a - b,
479///                 "multiply" => a * b,
480///                 "divide" => a / b,
481///                 _ => return Err(Error::InvalidInput("Unknown operation".into()))
482///             };
483///             
484///             Ok(json!({ "result": result }))
485///         }
486///     },
487///     {
488///         name: "weather",
489///         description: "获取指定城市的天气信息",
490///         parameters: {
491///             {
492///                 name: "city",
493///                 description: "城市名称",
494///                 type: "string",
495///                 required: true
496///             }
497///         },
498///         handler: get_weather_data
499///     }
500/// }
501/// ```
502#[proc_macro]
503pub fn tools(input: TokenStream) -> TokenStream {
504    tools::tools(input)
505}
506
507/// 基于nom的tool!宏 - 新的语法设计
508///
509/// # 示例
510///
511/// ```rust
512/// tool! {
513///     name: "calculator",
514///     description: "执行基本的数学运算",
515///     parameters: [
516///         {
517///             name: "operation",
518///             description: "要执行的操作: add, subtract, multiply, divide",
519///             type: "string",
520///             required: true
521///         },
522///         {
523///             name: "a",
524///             description: "第一个数字",
525///             type: "number",
526///             required: true
527///         }
528///     ],
529///     handler: handle_calculator
530/// }
531/// ```
532#[proc_macro]
533pub fn tool_nom(input: TokenStream) -> TokenStream {
534    tool_macro::tool_nom_macro(input)
535}
536
537/// 配置整个Lumos应用,参考Mastra的应用级API
538///
539/// # 示例
540///
541/// ```rust
542/// let app = lumos! {
543///     name: "stock_assistant",
544///     description: "一个能够提供股票信息的AI助手",
545///
546///     agents: {
547///         stockAgent
548///     },
549///
550///     tools: {
551///         stockPriceTool,
552///         stockInfoTool
553///     },
554///
555///     rags: {
556///         stockKnowledgeBase
557///     },
558///
559///     workflows: {
560///         stockAnalysisWorkflow
561///     },
562///
563///     mcp_endpoints: vec!["https://api.example.com/mcp"]
564/// };
565/// ```
566#[proc_macro]
567pub fn lumos(input: TokenStream) -> TokenStream {
568    lumos::lumos(input)
569}