1use crate::{
2 tools::{CalculatorTool, WeatherTool},
3 Agent, FunctionFactory,
4};
5use clap::{Arg, Command};
6use dotenvy;
7use std::env;
8use tracing::{error, info};
9
10pub async fn run() -> Result<(), Box<dyn std::error::Error>> {
12 dotenvy::dotenv().ok();
14 tracing_subscriber::fmt::init();
15
16 let matches = Command::new("tiny-agent")
17 .version("0.1.0")
18 .about("A lightweight Rust agent for LLM tool calling with OpenRouter")
19 .arg(
20 Arg::new("prompt")
21 .help("The prompt to send to the agent")
22 .required(true)
23 .index(1),
24 )
25 .arg(
26 Arg::new("model")
27 .short('m')
28 .long("model")
29 .value_name("MODEL")
30 .help("The OpenRouter model to use")
31 .default_value("openai/gpt-4.1-mini"),
32 )
33 .arg(
34 Arg::new("api-key")
35 .short('k')
36 .long("api-key")
37 .value_name("KEY")
38 .help("OpenRouter API key (or set OPENAI_API_KEY env var)"),
39 )
40 .arg(
41 Arg::new("base-url")
42 .short('u')
43 .long("base-url")
44 .value_name("URL")
45 .help(
46 "OpenRouter base URL (or set OPENAI_BASE_URL / OPENROUTER_BASE_URL env vars)",
47 ),
48 )
49 .arg(
50 Arg::new("timeout")
51 .short('t')
52 .long("timeout")
53 .value_name("SECONDS")
54 .help("Request timeout in seconds")
55 .default_value("120"),
56 )
57 .arg(
58 Arg::new("max-iterations")
59 .short('i')
60 .long("max-iterations")
61 .value_name("COUNT")
62 .help("Maximum agent iterations")
63 .default_value("10"),
64 )
65 .get_matches();
66
67 let api_key = matches
69 .get_one::<String>("api-key")
70 .cloned()
71 .or_else(|| env::var("OPENAI_API_KEY").ok())
72 .ok_or("OpenRouter API key is required. Set OPENAI_API_KEY environment variable or use --api-key")?;
73
74 let base_url = matches
76 .get_one::<String>("base-url")
77 .cloned()
78 .or_else(|| env::var("OPENAI_BASE_URL").ok())
79 .or_else(|| env::var("OPENROUTER_BASE_URL").ok())
80 .unwrap_or_else(|| "https://openrouter.ai/api/v1".to_string());
81
82 let mut function_factory = FunctionFactory::new();
84 function_factory.register_tool(CalculatorTool::new());
85 function_factory.register_tool(WeatherTool::new());
86
87 let timeout_seconds: u64 = matches.get_one::<String>("timeout").unwrap().parse()?;
89 let max_iterations: usize = matches
90 .get_one::<String>("max-iterations")
91 .unwrap()
92 .parse()?;
93
94 let agent = Agent::new(api_key, function_factory)
95 .with_model(matches.get_one::<String>("model").unwrap().as_str())
96 .with_timeout(std::time::Duration::from_secs(timeout_seconds))
97 .with_max_iterations(max_iterations)
98 .with_base_url(base_url.clone());
99
100 let prompt = matches.get_one::<String>("prompt").unwrap();
102 info!("Running agent with prompt: {}", prompt);
103 info!(
104 "Using model: {}",
105 matches.get_one::<String>("model").unwrap()
106 );
107 info!("Base URL: {}", base_url);
108
109 match agent.run(prompt).await {
110 Ok(response) => {
111 println!("\nAgent Response:\n{}", response);
112 info!("Agent execution completed successfully");
113 }
114 Err(e) => {
115 error!("Agent execution failed: {}", e);
116 return Err(e.into());
117 }
118 }
119
120 Ok(())
121}