feat(web_search): add olostep provider

Adds Olostep (https://www.olostep.com) as an optional web_search backend
using the official olostep Python SDK (client.answers.create()).

Changes:
- pyproject.toml: adds olostep>=0.1.0 optional dependency
- schema.py: adds olostep to provider comment in WebSearchConfig
- web.py: adds _search_olostep() with lazy import and provider branching
- docs/configuration.md: documents Olostep setup under web search config
- tests: unit tests for the new provider

Backward compatible: existing users see no behavior change unless they
opt into provider: "olostep". No hard dependency at runtime path.

Co-authored-by: umerkay <umerkk164@gmail.com>
This commit is contained in:
chengyongru
2026-04-28 19:09:38 +08:00
committed by Xubin Ren
co-authored by umerkay
parent 0053e68423
commit 28f9bbff31
5 changed files with 157 additions and 1 deletions
+79
View File
@@ -294,3 +294,82 @@ async def test_duckduckgo_timeout_returns_error(monkeypatch):
result = await tool.execute(query="test")
gate.set()
assert "Error" in result
@pytest.mark.asyncio
async def test_olostep_search_formats_answer_and_sources(monkeypatch):
from types import SimpleNamespace
calls: dict[str, str] = {}
class MockAsyncOlostep:
def __init__(self, api_key: str):
calls["api_key"] = api_key
self.answers = self
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return None
async def create(self, task: str):
calls["task"] = task
return SimpleNamespace(
answer="Mocked Olostep answer",
sources=[SimpleNamespace(title="Example Source", url="https://example.com")],
)
import sys
import types
fake_mod = types.ModuleType("olostep")
fake_mod.AsyncOlostep = MockAsyncOlostep
fake_mod.Olostep_BaseError = Exception
monkeypatch.setitem(sys.modules, "olostep", fake_mod)
tool = _tool(provider="olostep", api_key="olostep-key")
result = await tool.execute(query="test query")
assert calls["api_key"] == "olostep-key"
assert calls["task"] == "test query"
assert "Mocked Olostep answer" in result
assert "Example Source" in result
assert "https://example.com" in result
@pytest.mark.asyncio
async def test_olostep_missing_key_falls_back_to_duckduckgo(monkeypatch):
import sys
import types
from unittest.mock import patch
class MockDDGS:
def __init__(self, **kw):
pass
def text(self, query, max_results=5):
return [{"title": "Fallback", "href": "https://ddg.example", "body": "fallback"}]
fake_mod = types.ModuleType("olostep")
fake_mod.AsyncOlostep = object
fake_mod.Olostep_BaseError = Exception
monkeypatch.setitem(sys.modules, "olostep", fake_mod)
monkeypatch.delenv("OLOSTEP_API_KEY", raising=False)
with patch("ddgs.DDGS", MockDDGS):
tool = _tool(provider="olostep", api_key="")
result = await tool.execute(query="test query")
assert "Fallback" in result
@pytest.mark.asyncio
async def test_olostep_package_missing_returns_install_hint(monkeypatch):
import sys
monkeypatch.delitem(sys.modules, "olostep", raising=False)
monkeypatch.setitem(sys.modules, "olostep", None)
tool = _tool(provider="olostep", api_key="olostep-key")
result = await tool.execute(query="test query")
assert result == "Error: olostep package not installed. Run: pip install olostep"