use std::env;
use openrouter_rs::{
api::chat::{ChatCompletionRequest, Message},
client::OpenRouterClient,
types::{
Role,
typed_tool::{TypedTool, TypedToolParams},
},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct WeatherParams {
pub location: String,
#[serde(default = "default_temperature_unit")]
pub unit: TemperatureUnit,
#[serde(default)]
pub include_forecast: bool,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum TemperatureUnit {
Celsius,
Fahrenheit,
Kelvin,
}
fn default_temperature_unit() -> TemperatureUnit {
TemperatureUnit::Fahrenheit
}
impl TypedTool for WeatherParams {
fn name() -> &'static str {
"get_weather"
}
fn description() -> &'static str {
"Get current weather information for a specific location with temperature unit preferences"
}
}
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct CalculatorParams {
pub operation: ArithmeticOperation,
pub a: f64,
pub b: f64,
#[serde(default = "default_precision")]
pub precision: u32,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum ArithmeticOperation {
Add,
Subtract,
Multiply,
Divide,
Power,
Modulo,
}
fn default_precision() -> u32 {
2
}
impl TypedTool for CalculatorParams {
fn name() -> &'static str {
"calculator"
}
fn description() -> &'static str {
"Perform basic arithmetic operations with configurable precision"
}
}
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct TextAnalysisParams {
pub text: String,
pub analysis_types: Vec<AnalysisType>,
pub language: Option<String>,
#[serde(default)]
pub detailed: bool,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisType {
Sentiment,
WordCount,
LanguageDetection,
KeyPhrases,
Readability,
}
impl TypedTool for TextAnalysisParams {
fn name() -> &'static str {
"analyze_text"
}
fn description() -> &'static str {
"Perform comprehensive text analysis including sentiment, word count, and key phrase extraction"
}
}
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct SearchParams {
pub query: String,
pub search_type: SearchType,
#[serde(default = "default_max_results")]
pub max_results: u32,
pub filters: Option<SearchFilters>,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum SearchType {
Web,
Academic,
News,
Images,
Videos,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct SearchFilters {
pub date_range: Option<u32>,
pub domain: Option<String>,
pub language: Option<String>,
}
fn default_max_results() -> u32 {
10
}
impl TypedTool for SearchParams {
fn name() -> &'static str {
"search"
}
fn description() -> &'static str {
"Search various sources (web, academic, news) with configurable filters and result limits"
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("OPENROUTER_API_KEY")
.expect("OPENROUTER_API_KEY environment variable is required");
let client = OpenRouterClient::builder()
.api_key(&api_key)
.http_referer("https://localhost")
.x_title("Typed Tool Calling Example")
.build()?;
println!("๐ง Typed Tool Calling Example");
println!("==============================\n");
println!("๐ Generated JSON Schemas:");
println!("---------------------------");
println!("Weather Tool Schema:");
println!(
"{}",
serde_json::to_string_pretty(&WeatherParams::get_schema())?
);
println!();
println!("Calculator Tool Schema:");
println!(
"{}",
serde_json::to_string_pretty(&CalculatorParams::get_schema())?
);
println!();
let request = ChatCompletionRequest::builder()
.model("deepseek/deepseek-chat-v3.1:free")
.messages(vec![
Message::new(
Role::System,
"You are a helpful assistant with access to various tools. Use appropriate tools to help answer user questions."
),
Message::new(
Role::User,
"I need to know the weather in New York City in Celsius, and I also want to calculate 15.5 * 3.7 with 3 decimal places of precision."
),
])
.typed_tool::<WeatherParams>() .typed_tool::<CalculatorParams>() .typed_tool::<TextAnalysisParams>() .typed_tool::<SearchParams>() .tool_choice_auto() .build()?;
println!(
"๐ค Sending request with {} typed tools...",
request.tools().map_or(0, |t| t.len())
);
match client.chat().create(&request).await {
Ok(response) => {
println!("โ
Response received!\n");
for (i, choice) in response.choices.iter().enumerate() {
println!("Choice {}: {}", i + 1, "=".repeat(50));
if let Some(content) = choice.content() {
println!("๐ฌ Content: {}", content);
}
if let Some(role) = choice.role() {
println!("๐ญ Role: {}", role);
}
if let Some(tool_calls) = choice.tool_calls() {
println!("๐ง Tool Calls ({}):", tool_calls.len());
for (j, tool_call) in tool_calls.iter().enumerate() {
println!(
" {}. {} (ID: {})",
j + 1,
tool_call.function.name,
tool_call.id
);
println!(" Arguments: {}", tool_call.function.arguments);
match tool_call.name() {
"get_weather" => match tool_call.parse_params::<WeatherParams>() {
Ok(params) => {
println!(" โ
Weather for: {}", params.location);
println!(" Unit: {:?}", params.unit);
println!(
" Include forecast: {}",
params.include_forecast
);
}
Err(e) => println!(" โ Parse error: {}", e),
},
"calculator" => match tool_call.parse_params::<CalculatorParams>() {
Ok(params) => {
println!(" ๐งฎ Calculate: {:?}", params.operation);
println!(
" {} {:?} {} with {} decimal places",
params.a, params.operation, params.b, params.precision
);
}
Err(e) => println!(" โ Parse error: {}", e),
},
_ => {
println!(" โน๏ธ Unknown tool: {}", tool_call.name());
}
}
}
}
if let Some(finish_reason) = choice.finish_reason() {
println!("๐ Finish Reason: {:?}", finish_reason);
}
println!();
}
if let Some(usage) = response.usage {
println!("๐ Token Usage:");
println!(" Prompt tokens: {}", usage.prompt_tokens);
println!(" Completion tokens: {}", usage.completion_tokens);
println!(" Total tokens: {}", usage.total_tokens);
}
}
Err(e) => {
eprintln!("โ Error: {}", e);
}
}
println!("\n๐งช Type Safety Demo:");
println!("---------------------");
let weather_params = WeatherParams {
location: "Tokyo, Japan".to_string(),
unit: TemperatureUnit::Celsius,
include_forecast: true,
};
let calculator_params = CalculatorParams {
operation: ArithmeticOperation::Power,
a: 2.0,
b: 8.0,
precision: 1,
};
println!("Weather params JSON: {}", weather_params.to_json_value()?);
println!(
"Calculator params JSON: {}",
calculator_params.to_json_value()?
);
println!("Weather params validation: {:?}", weather_params.validate());
println!(
"Calculator params validation: {:?}",
calculator_params.validate()
);
Ok(())
}