use crate::tool::{toolbox, Tool, ToolBox, ToolError, ToolResult};
use anyhow::Context;
use reqwest::Client;
use serde_json::Value;
const BRAVE_API_URL: &str = "https://api.search.brave.com/res/v1/web/search";
pub struct WebSearchToolBox {
client: Client,
api_key: String,
}
#[toolbox]
impl WebSearchToolBox {
pub fn new(api_key: &str) -> Self {
Self {
client: Client::default(),
api_key: api_key.to_string(),
}
}
#[tool]
pub async fn web_search(
&self,
#[doc = "The search terms or keywords to be used by the search engine for retrieving relevant results."]
query: String,
) -> ToolResult {
let params = [
("q", query.as_str()),
("count", "5"),
("result_filter", "web"),
];
let response = self
.client
.get(BRAVE_API_URL)
.query(¶ms)
.header("X-Subscription-Token", self.api_key.clone())
.send()
.await
.map_err(anyhow::Error::new)?;
let json: Value = response.json().await.map_err(anyhow::Error::new)?;
let mut results: Vec<String> = vec![];
let response = json["web"]["results"]
.as_array()
.ok_or(ToolError::ExecutionError)?;
for item in response {
let title = item["title"]
.as_str()
.context("web title is not a string")?;
let description = item["description"]
.as_str()
.context("web description is not a string")?;
let url = item["url"].as_str().context("web url is not a string")?;
results.push(format!(
"Title: {title}\nDescription: {description}\nURL: {url}"
));
}
Ok(results.join("\n\n"))
}
}
pub struct WebFetchToolBox {
client: Client,
}
impl Default for WebFetchToolBox {
fn default() -> Self {
Self::new()
}
}
#[toolbox]
impl WebFetchToolBox {
pub fn new() -> Self {
Self {
client: Client::default(),
}
}
#[allow(rustdoc::bare_urls)]
#[tool]
pub async fn web_fetch(
&self,
#[doc = "The full URL of the web page to fetch, including the protocol (e.g., https://)."]
url: String,
) -> ToolResult {
let response = self
.client
.get(&url)
.send()
.await
.map_err(|e| ToolError::LLMError(format!("Request to {url} failed: {e}")))?;
if !response.status().is_success() {
return Err(ToolError::LLMError(format!(
"Request to {} failed with status: {}",
url,
response.status()
)));
}
let body = response.text().await.map_err(anyhow::Error::new)?;
Ok(body)
}
}