Add Brave web search support
This commit is contained in:
@@ -60,6 +60,24 @@ class AIPromptConfig:
|
||||
user_template: str | None = None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class AIWebSearchConfig:
|
||||
enabled: bool = True
|
||||
api_key: str | None = None
|
||||
endpoint: str = "https://api.search.brave.com/res/v1/web/search"
|
||||
count: int = 5
|
||||
country: str = "US"
|
||||
search_lang: str = "en"
|
||||
ui_lang: str = "en-US"
|
||||
safesearch: str = "moderate"
|
||||
timeout: float = 10.0
|
||||
extra_snippets: bool = False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self.enabled and bool(self.api_key)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class AIConfig:
|
||||
default_model: str
|
||||
@@ -69,6 +87,7 @@ class AIConfig:
|
||||
models: dict[str, AIModelConfig] = field(default_factory=dict)
|
||||
workspace_dir: Path = field(default_factory=lambda: Path("workspace"))
|
||||
tool_max_rounds: int = 10
|
||||
web_search: AIWebSearchConfig = field(default_factory=AIWebSearchConfig)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -127,6 +146,20 @@ def _parse_float(value: Any, *, field_name: str) -> float:
|
||||
raise ValueError(f"{field_name} must be a number") from exc
|
||||
|
||||
|
||||
def _parse_bool(value: Any, *, field_name: str) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
normalized = value.strip().lower()
|
||||
if normalized in {"1", "true", "yes", "on"}:
|
||||
return True
|
||||
if normalized in {"0", "false", "no", "off"}:
|
||||
return False
|
||||
if isinstance(value, int):
|
||||
return bool(value)
|
||||
raise ValueError(f"{field_name} must be a boolean")
|
||||
|
||||
|
||||
def _parse_mapping(value: Any, *, field_name: str) -> dict[str, Any]:
|
||||
if value is None:
|
||||
return {}
|
||||
@@ -135,6 +168,69 @@ def _parse_mapping(value: Any, *, field_name: str) -> dict[str, Any]:
|
||||
return value
|
||||
|
||||
|
||||
def _parse_web_search_config(raw_ai: dict[str, Any]) -> AIWebSearchConfig:
|
||||
raw = _parse_mapping(raw_ai.get("web_search"), field_name="ai.web_search")
|
||||
|
||||
enabled = _parse_bool(raw.get("enabled", True), field_name="ai.web_search.enabled")
|
||||
api_key = str(raw.get("api_key") or "").strip() or None
|
||||
endpoint = str(
|
||||
raw.get("endpoint") or "https://api.search.brave.com/res/v1/web/search"
|
||||
).strip()
|
||||
if not endpoint:
|
||||
raise ValueError("ai.web_search.endpoint is required")
|
||||
|
||||
count = _parse_int(raw.get("count", 5), field_name="ai.web_search.count")
|
||||
count = min(max(count, 1), 20)
|
||||
|
||||
country = str(raw.get("country", "US") or "US").strip().upper()
|
||||
search_lang = str(raw.get("search_lang", "en") or "en").strip().lower()
|
||||
ui_lang = str(raw.get("ui_lang", "en-US") or "en-US").strip()
|
||||
safesearch = str(raw.get("safesearch", "moderate") or "moderate").strip().lower()
|
||||
if safesearch not in {"off", "moderate", "strict"}:
|
||||
raise ValueError("ai.web_search.safesearch must be one of: off, moderate, strict")
|
||||
|
||||
timeout = _parse_float(raw.get("timeout", 10.0), field_name="ai.web_search.timeout")
|
||||
if timeout <= 0:
|
||||
raise ValueError("ai.web_search.timeout must be > 0")
|
||||
|
||||
extra_snippets = _parse_bool(
|
||||
raw.get("extra_snippets", False),
|
||||
field_name="ai.web_search.extra_snippets",
|
||||
)
|
||||
|
||||
env_api_key = os.environ.get("BRAVE_API_KEY")
|
||||
if env_api_key is not None:
|
||||
api_key = env_api_key.strip() or None
|
||||
if os.environ.get("BRAVE_SEARCH_COUNT") is not None:
|
||||
count = min(
|
||||
max(_parse_int(os.environ["BRAVE_SEARCH_COUNT"], field_name="BRAVE_SEARCH_COUNT"), 1),
|
||||
20,
|
||||
)
|
||||
if os.environ.get("BRAVE_SEARCH_COUNTRY") is not None:
|
||||
country = os.environ["BRAVE_SEARCH_COUNTRY"].strip().upper() or country
|
||||
if os.environ.get("BRAVE_SEARCH_LANG") is not None:
|
||||
search_lang = os.environ["BRAVE_SEARCH_LANG"].strip().lower() or search_lang
|
||||
if os.environ.get("BRAVE_SEARCH_UI_LANG") is not None:
|
||||
ui_lang = os.environ["BRAVE_SEARCH_UI_LANG"].strip() or ui_lang
|
||||
if os.environ.get("BRAVE_SEARCH_SAFESEARCH") is not None:
|
||||
safesearch = os.environ["BRAVE_SEARCH_SAFESEARCH"].strip().lower()
|
||||
if safesearch not in {"off", "moderate", "strict"}:
|
||||
raise ValueError("BRAVE_SEARCH_SAFESEARCH must be one of: off, moderate, strict")
|
||||
|
||||
return AIWebSearchConfig(
|
||||
enabled=enabled,
|
||||
api_key=api_key,
|
||||
endpoint=endpoint,
|
||||
count=count,
|
||||
country=country,
|
||||
search_lang=search_lang,
|
||||
ui_lang=ui_lang,
|
||||
safesearch=safesearch,
|
||||
timeout=timeout,
|
||||
extra_snippets=extra_snippets,
|
||||
)
|
||||
|
||||
|
||||
def _parse_accounts(raw_accounts: Any, *, default_api_id: int | None, default_api_hash: str | None, default_session_dir: Path) -> list[AccountConfig]:
|
||||
if raw_accounts is None:
|
||||
return []
|
||||
@@ -287,6 +383,8 @@ def _parse_ai_config(raw_ai: Any, *, root: Path) -> AIConfig | None:
|
||||
if tool_max_rounds < 1:
|
||||
raise ValueError("ai.tool_max_rounds must be >= 1")
|
||||
|
||||
web_search = _parse_web_search_config(raw_ai)
|
||||
|
||||
return AIConfig(
|
||||
default_model=default_model,
|
||||
context_messages=context_messages,
|
||||
@@ -295,6 +393,7 @@ def _parse_ai_config(raw_ai: Any, *, root: Path) -> AIConfig | None:
|
||||
models=models,
|
||||
workspace_dir=workspace_dir,
|
||||
tool_max_rounds=tool_max_rounds,
|
||||
web_search=web_search,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user