use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Message { pub role: String, pub content: Option, #[serde(skip_serializing_if = "Option::is_none")] pub tool_calls: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ToolCall { pub id: String, #[serde(rename = "type")] pub call_type: String, pub function: FunctionCall, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FunctionCall { pub name: String, pub arguments: String, } #[derive(Serialize, Debug)] pub struct ChatRequest { pub model: String, pub messages: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub tools: Option>, } #[derive(Deserialize, Debug)] pub struct ChatResponse { pub choices: Vec, } #[derive(Deserialize, Debug)] pub struct Choice { pub message: Message, } #[derive(Serialize, Debug, Clone)] pub struct Tool { #[serde(rename = "type")] pub tool_type: String, pub function: FunctionDefinition, } #[derive(Serialize, Debug, Clone)] pub struct FunctionDefinition { pub name: String, pub description: String, pub parameters: serde_json::Value, } #[derive(Deserialize, Debug)] struct TavilyResponse { results: Vec, } #[derive(Deserialize, Debug)] struct TavilyResult { title: String, url: String, content: String, } pub async fn perform_search( query: &str, api_key: &str, ) -> Result> { tracing::info!(query = %query, "Performing Tavily web search"); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build()?; let response = client .post("https://api.tavily.com/search") .json(&serde_json::json!({ "api_key": api_key, "query": query, "search_depth": "basic", "include_answer": false, "include_images": false, "include_raw_content": false, "max_results": 5, })) .send() .await?; if !response.status().is_success() { return Err(format!("Tavily API error: {}", response.status()).into()); } let search_data: TavilyResponse = response.json().await?; tracing::info!("Web search yielded {} results", search_data.results.len()); let mut results_text = String::new(); for (i, result) in search_data.results.iter().enumerate() { results_text.push_str(&format!( "{}. {} ({})\nSnippet: {}\n\n", i + 1, result.title, result.url, result.content )); } if results_text.is_empty() { Ok("No results found.".to_string()) } else { Ok(results_text) } }