from __future__ import annotations from datetime import UTC, datetime from typing import Any from pydantic import BaseModel, Field class SearchEvidence(BaseModel): title: str = "" url: str = "" snippet: str = "" content: str = "" score: float | None = None source_provider: str = "" retrieved_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) metadata: dict[str, Any] = Field(default_factory=dict) def compact_text(self, limit: int = 700) -> str: text = " ".join((self.content or self.snippet or "").split()) return text[:limit] class FetchedEvidence(BaseModel): url: str final_url: str = "" title: str = "" text: str = "" content_hash: str = "" extractor: str = "basic_html" retrieved_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) metadata: dict[str, Any] = Field(default_factory=dict) class WebSearchProviderConfig(BaseModel): provider: str = "tavily" base_url: str = "" api_key: str = "" max_results: int = Field(default=5, ge=1, le=20) timeout_seconds: int = Field(default=20, ge=3, le=120) endpoint_path: str = "" search_depth: str = "basic" engine: str = "google" include_answer: bool = False include_raw_content: bool = False include_text: bool = False categories: str = "general" engines: list[str] = Field(default_factory=list) search_path: str = "" scrape_path: str = "" scrape_formats: list[str] = Field(default_factory=lambda: ["markdown"]) class WebSearchConfig(BaseModel): enabled: bool = False default_provider: str = "tavily" provider: str = "tavily" providers: dict[str, WebSearchProviderConfig] = Field(default_factory=dict) @property def active_provider_config(self) -> WebSearchProviderConfig: return self.providers.get(self.default_provider) or self.providers.get(self.provider) or WebSearchProviderConfig(provider=self.default_provider or self.provider)