116 lines
2.9 KiB
Rust
116 lines
2.9 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct Message {
|
|
pub role: String,
|
|
pub content: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_calls: Option<Vec<ToolCall>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_call_id: Option<String>,
|
|
}
|
|
|
|
#[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<Message>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tools: Option<Vec<Tool>>,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct ChatResponse {
|
|
pub choices: Vec<Choice>,
|
|
}
|
|
|
|
#[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<TavilyResult>,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
struct TavilyResult {
|
|
title: String,
|
|
url: String,
|
|
content: String,
|
|
}
|
|
|
|
pub async fn perform_search(
|
|
query: &str,
|
|
api_key: &str,
|
|
) -> Result<String, Box<dyn std::error::Error>> {
|
|
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)
|
|
}
|
|
}
|