feat(chat): implement speculative web search and embeddings commands
- Added speculative search functionality to enhance query processing by detecting when a web search may be needed based on user input patterns. - Integrated commands for managing embeddings, including downloading models, checking availability, calculating relevance scores, generating embeddings, and pruning context. - Optimized chat streaming by buffering tokens to reduce overhead and improve responsiveness. - Implemented lazy initialization for the headless browser to conserve memory until needed. - Enhanced web scraping capabilities with a static scraping method for faster content retrieval.
This commit is contained in:
1 parent
ba03131a78
commit
e53e49c8f8
7 files changed
+1407
-40
No files matched your search
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use headless_chrome::{Browser, LaunchOptions, Tab};
|
||||
use reqwest::header::USER_AGENT;
|
||||
use scraper::{Html, Selector};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
use rand::Rng;
|
||||
@@ -10,6 +10,37 @@ use tokio::sync::Semaphore;
|
||||
use regex::Regex;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Lazy-initialized global browser instance
|
||||
/// Evita criar o browser no startup, economizando ~500MB de RAM até ser necessário
|
||||
static LAZY_BROWSER: OnceLock<Mutex<Option<Arc<Browser>>>> = OnceLock::new();
|
||||
|
||||
/// Obtém ou cria a instância global do browser (lazy initialization)
|
||||
pub fn get_or_create_browser() -> Result<Arc<Browser>> {
|
||||
let mutex = LAZY_BROWSER.get_or_init(|| Mutex::new(None));
|
||||
let mut guard = mutex.lock().map_err(|e| anyhow::anyhow!("Browser mutex poisoned: {}", e))?;
|
||||
|
||||
if guard.is_none() {
|
||||
log::info!("[LazyBrowser] Initializing headless browser on first use...");
|
||||
let browser = create_browser()?;
|
||||
*guard = Some(Arc::new(browser));
|
||||
log::info!("[LazyBrowser] Browser initialized successfully");
|
||||
}
|
||||
|
||||
Ok(guard.as_ref().unwrap().clone())
|
||||
}
|
||||
|
||||
/// Limpa a instância do browser (para liberar memória quando não em uso)
|
||||
pub fn clear_browser() {
|
||||
if let Some(mutex) = LAZY_BROWSER.get() {
|
||||
if let Ok(mut guard) = mutex.lock() {
|
||||
if guard.is_some() {
|
||||
log::info!("[LazyBrowser] Clearing browser instance to free memory");
|
||||
*guard = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resultado da extração de conteúdo de uma URL
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||
pub struct ScrapedContent {
|
||||
@@ -1302,7 +1333,7 @@ pub async fn search_and_scrape_with_config(
|
||||
if connection_closed && !failed_urls.is_empty() {
|
||||
let retry_concurrency = std::cmp::min(3, config.max_concurrent_tabs.max(1));
|
||||
let semaphore = Arc::new(Semaphore::new(retry_concurrency));
|
||||
let browser_new = Arc::new(create_browser()?);
|
||||
let browser_new = get_or_create_browser()?;
|
||||
let mut retry_handles = Vec::new();
|
||||
for url in failed_urls.clone() {
|
||||
let browser_clone = browser_new.clone();
|
||||
@@ -1341,11 +1372,79 @@ pub async fn search_and_scrape_with_config(
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Busca e extrai conteúdo de uma única URL
|
||||
/// Scraping estático usando apenas reqwest (sem headless browser)
|
||||
/// Muito mais rápido (~100ms vs ~3s) e consome menos RAM
|
||||
/// Retorna None se o conteúdo for insuficiente (SPA/JavaScript-heavy)
|
||||
pub async fn scrape_url_static(url: &str) -> Result<Option<ScrapedContent>> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(8))
|
||||
.redirect(reqwest::redirect::Policy::limited(5))
|
||||
.build()?;
|
||||
|
||||
let user_agent = get_random_user_agent();
|
||||
let start_time = Instant::now();
|
||||
|
||||
log::debug!("[StaticScrape] Fetching: {}", url);
|
||||
|
||||
let response = match client
|
||||
.get(url)
|
||||
.header(USER_AGENT, user_agent)
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
.header("Accept-Language", "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
log::debug!("[StaticScrape] Request failed for {}: {}", url, e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
if !response.status().is_success() {
|
||||
log::debug!("[StaticScrape] HTTP {} for {}", response.status(), url);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let html = match response.text().await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
log::debug!("[StaticScrape] Failed to read body for {}: {}", url, e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let duration = start_time.elapsed().as_millis();
|
||||
|
||||
// Usar extract_paragraph_fallback existente
|
||||
let result = extract_paragraph_fallback(url, &html);
|
||||
|
||||
if let Some(ref content) = result {
|
||||
log::info!("[StaticScrape] Success for {} ({} chars, {}ms)",
|
||||
url, content.content.len(), duration);
|
||||
} else {
|
||||
log::debug!("[StaticScrape] Insufficient content for {} ({}ms)", url, duration);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Busca e extrai conteúdo de uma única URL (híbrido: tenta estático primeiro)
|
||||
pub async fn scrape_url(
|
||||
url: &str,
|
||||
browser: Arc<Browser>,
|
||||
) -> Result<ScrapedContent> {
|
||||
// OTIMIZAÇÃO: Tentar scraping estático primeiro (muito mais rápido)
|
||||
if let Ok(Some(content)) = scrape_url_static(url).await {
|
||||
// Se conseguiu conteúdo suficiente (>500 chars), usar resultado estático
|
||||
if content.content.len() > 500 {
|
||||
log::info!("[ScrapeHybrid] Using static result for {} ({} chars)", url, content.content.len());
|
||||
return Ok(content);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: usar headless browser para SPAs/JS-heavy pages
|
||||
log::info!("[ScrapeHybrid] Falling back to headless for {}", url);
|
||||
let browser_clone = browser.clone();
|
||||
let url_str = url.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
|
||||
Reference in new issue
Block a user