Add Exa web search provider
This commit is contained in:
@@ -291,6 +291,71 @@ async def test_kagi_search(monkeypatch):
|
||||
assert "ignored related search" not in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exa_search(monkeypatch):
|
||||
async def mock_post(self, url, **kw):
|
||||
assert url == "https://api.exa.ai/search"
|
||||
assert kw["headers"]["x-api-key"] == "exa-key"
|
||||
assert kw["headers"]["User-Agent"] == "nanobot-search-test"
|
||||
assert kw["json"] == {
|
||||
"query": "test",
|
||||
"numResults": 2,
|
||||
"contents": {"highlights": True},
|
||||
}
|
||||
return _response(json={
|
||||
"results": [
|
||||
{
|
||||
"title": "Exa Result",
|
||||
"url": "https://exa.ai",
|
||||
"highlights": ["Relevant Exa highlight"],
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
|
||||
tool = _tool(provider="exa", api_key="exa-key", user_agent="nanobot-search-test")
|
||||
result = await tool.execute(query="test", count=2)
|
||||
|
||||
assert "Exa Result" in result
|
||||
assert "https://exa.ai" in result
|
||||
assert "Relevant Exa highlight" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exa_search_uses_env_api_key(monkeypatch):
|
||||
async def mock_post(self, url, **kw):
|
||||
assert kw["headers"]["x-api-key"] == "env-exa-key"
|
||||
return _response(json={
|
||||
"results": [
|
||||
{
|
||||
"title": "Env Exa Result",
|
||||
"url": "https://exa.ai/env",
|
||||
"summary": "Summary fallback",
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
|
||||
monkeypatch.setenv("EXA_API_KEY", "env-exa-key")
|
||||
tool = _tool(provider="exa", api_key="")
|
||||
result = await tool.execute(query="test", count=1)
|
||||
|
||||
assert "Env Exa Result" in result
|
||||
assert "Summary fallback" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exa_search_http_error(monkeypatch):
|
||||
async def mock_post(self, url, **kw):
|
||||
return _response(status=401, json={"error": "invalid key"})
|
||||
|
||||
monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
|
||||
tool = _tool(provider="exa", api_key="bad-exa-key")
|
||||
result = await tool.execute(query="test")
|
||||
|
||||
assert "Error: Exa search failed (401)" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unknown_provider():
|
||||
tool = _tool(provider="unknown")
|
||||
@@ -377,6 +442,23 @@ async def test_kagi_fallback_to_duckduckgo_when_no_key(monkeypatch):
|
||||
assert "Fallback" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exa_fallback_to_duckduckgo_when_no_key(monkeypatch):
|
||||
class MockDDGS:
|
||||
def __init__(self, **kw):
|
||||
pass
|
||||
|
||||
def text(self, query, max_results=5):
|
||||
return [{"title": "Fallback", "href": "https://ddg.example", "body": "DuckDuckGo fallback"}]
|
||||
|
||||
monkeypatch.setattr("ddgs.DDGS", MockDDGS)
|
||||
monkeypatch.delenv("EXA_API_KEY", raising=False)
|
||||
|
||||
tool = _tool(provider="exa", api_key="")
|
||||
result = await tool.execute(query="test")
|
||||
assert "Fallback" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_jina_search_uses_path_encoded_query(monkeypatch):
|
||||
calls = {}
|
||||
|
||||
Reference in New Issue
Block a user