Add Brave web search support

This commit is contained in:
2026-05-18 22:38:54 +02:00
parent 1ef2893345
commit 7147c657ab
10 changed files with 640 additions and 2 deletions

View File

@@ -0,0 +1,67 @@
from __future__ import annotations
from pathlib import Path
import unittest
from unittest.mock import patch
from app.config import _parse_ai_config
def _raw_ai_config() -> dict:
return {
"default_model": "test",
"context_messages": 1,
"prompt": {"root": "prompts/ai"},
"providers": {
"test": {
"api_key": "model-key",
"base_url": "https://example.test/v1",
}
},
"models": {
"test": {
"provider": "test",
"model": "test-model",
}
},
"web_search": {
"api_key": "config-key",
"count": 99,
"safesearch": "strict",
},
}
class ConfigWebSearchTests(unittest.TestCase):
def test_web_search_config_clamps_count(self) -> None:
with patch.dict("os.environ", {}, clear=True):
config = _parse_ai_config(_raw_ai_config(), root=Path.cwd())
assert config is not None
self.assertEqual(config.web_search.api_key, "config-key")
self.assertEqual(config.web_search.count, 20)
self.assertEqual(config.web_search.safesearch, "strict")
def test_web_search_env_overrides(self) -> None:
env = {
"BRAVE_API_KEY": "env-key",
"BRAVE_SEARCH_COUNT": "3",
"BRAVE_SEARCH_COUNTRY": "pl",
"BRAVE_SEARCH_LANG": "pl",
"BRAVE_SEARCH_UI_LANG": "pl-PL",
"BRAVE_SEARCH_SAFESEARCH": "off",
}
with patch.dict("os.environ", env, clear=True):
config = _parse_ai_config(_raw_ai_config(), root=Path.cwd())
assert config is not None
self.assertEqual(config.web_search.api_key, "env-key")
self.assertEqual(config.web_search.count, 3)
self.assertEqual(config.web_search.country, "PL")
self.assertEqual(config.web_search.search_lang, "pl")
self.assertEqual(config.web_search.ui_lang, "pl-PL")
self.assertEqual(config.web_search.safesearch, "off")
if __name__ == "__main__":
unittest.main()

84
tests/test_web_search.py Normal file
View File

@@ -0,0 +1,84 @@
from __future__ import annotations
import unittest
import httpx
from app.ai.web_search import (
_format_error_response,
build_query,
format_results,
parse_response,
)
from app.config import AIWebSearchConfig
class WebSearchTests(unittest.TestCase):
def test_build_query_clamps_and_defaults(self) -> None:
config = AIWebSearchConfig(api_key="key", count=5)
query = build_query(
config,
{
"query": " python web framework ",
"count": 99,
"offset": -1,
"freshness": "pw",
},
)
self.assertEqual(query.query, "python web framework")
self.assertEqual(query.count, 20)
self.assertEqual(query.offset, 0)
self.assertEqual(query.freshness, "pw")
self.assertEqual(query.safesearch, "moderate")
def test_build_query_rejects_invalid_freshness(self) -> None:
with self.assertRaisesRegex(ValueError, "freshness"):
build_query(AIWebSearchConfig(), {"query": "news", "freshness": "yesterday"})
def test_parse_and_format_results(self) -> None:
response = parse_response(
"openai",
{
"query": {"altered": "open ai", "more_results_available": True},
"web": {
"results": [
{
"title": "OpenAI",
"url": "https://openai.com",
"description": "AI research and products.",
"age": "May 18, 2026",
"extra_snippets": ["Models and APIs.", ""],
},
{"title": "", "url": ""},
]
},
},
)
self.assertEqual(len(response.results), 1)
self.assertTrue(response.more_results_available)
formatted = format_results(response)
self.assertIn("Search results for: openai", formatted)
self.assertIn("https://openai.com", formatted)
self.assertIn("Models and APIs.", formatted)
def test_format_empty_results(self) -> None:
formatted = format_results(parse_response("nothing", {"web": {"results": []}}))
self.assertIn("No results found.", formatted)
def test_format_error_response_uses_brave_detail(self) -> None:
request = httpx.Request("GET", "https://api.search.brave.com/res/v1/web/search")
response = httpx.Response(
429,
request=request,
json={"error": {"detail": "Rate limit exceeded"}},
)
self.assertIn("Rate limit exceeded", _format_error_response(response))
if __name__ == "__main__":
unittest.main()