Compare commits

...

4 Commits

Author SHA1 Message Date
Patrick Buckley b3934a2d14 bump: v0.8.6
Hotfix: DDG web search returning empty results.

- Switch dependency from duckduckgo-search (deprecated shim, empty
  results) to ddgs>=9.0 (actively maintained successor)
- Include ddg extra in Docker image for free web search fallback
- Update all user-facing install instructions to reference ddgs
2026-03-23 16:17:28 -07:00
Patrick Buckley 3d02cf66b4 review: update stale duckduckgo-search references to ddgs 2026-03-23 16:09:03 -07:00
Patrick Buckley 7631b88792 fix: switch DDG dependency from duckduckgo-search to ddgs
duckduckgo-search 8.x is a deprecated shim that returns empty results
(upstream temporarily disabled HTML/Lite backends, Bing backend broken).
The package was renamed to ddgs in v9.x which works correctly.
2026-03-23 16:09:03 -07:00
Patrick Buckley a07172b0c0 fix: include ddg extra in Docker image for free web search fallback 2026-03-23 15:23:12 -07:00
7 changed files with 30 additions and 30 deletions
+2 -2
View File
@@ -25,12 +25,12 @@ ENV UV_COMPILE_BYTECODE=1
# Install dependencies first (cached layer — only re-runs when deps change)
COPY pyproject.toml uv.lock README.md LICENSE ./
RUN uv sync --frozen --no-install-project --no-dev \
--extra mq --extra console --extra sim --extra postgres --extra discord --extra anthropic
--extra mq --extra console --extra sim --extra postgres --extra discord --extra anthropic --extra ddg
# Install the project itself
COPY turnstone/ turnstone/
RUN uv sync --frozen --no-dev \
--extra mq --extra console --extra sim --extra postgres --extra discord --extra anthropic
--extra mq --extra console --extra sim --extra postgres --extra discord --extra anthropic --extra ddg
# Add venv to PATH so entry points are found
ENV PATH="/app/.venv/bin:$PATH"
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "turnstone"
version = "0.8.5"
version = "0.8.6"
description = "Multi-node AI orchestration platform with tool use, agent routing, and cluster simulation."
readme = "README.md"
license = "BUSL-1.1"
@@ -51,7 +51,7 @@ console = ["redis>=7.2", "croniter>=3.0"]
sim = ["redis>=7.2"]
anthropic = ["anthropic>=0.39"]
postgres = ["psycopg[binary]>=3.2"]
ddg = ["duckduckgo-search>=8.0"]
ddg = ["ddgs>=9.0"]
discord = ["discord.py>=2.4", "redis>=7.2"]
all = ["turnstone[mq,console,sim,anthropic,postgres,discord,ddg]"]
+2 -2
View File
@@ -84,7 +84,7 @@ class TestTavilyClient:
class TestDuckDuckGoClient:
def test_integration_via_mock_ddgs(self):
"""Patch the duckduckgo_search import inside DuckDuckGoClient.search."""
"""Patch the ddgs import inside DuckDuckGoClient.search."""
mock_ddgs = MagicMock()
mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
mock_ddgs.__exit__ = MagicMock(return_value=False)
@@ -93,7 +93,7 @@ class TestDuckDuckGoClient:
]
mock_module = MagicMock()
mock_module.DDGS.return_value = mock_ddgs
with patch.dict("sys.modules", {"duckduckgo_search": mock_module}):
with patch.dict("sys.modules", {"ddgs": mock_module}):
client = DuckDuckGoClient(timeout=10)
result = client.search("test query", max_results=3)
mock_ddgs.text.assert_called_once_with("test query", max_results=3)
+1 -1
View File
@@ -1,3 +1,3 @@
"""turnstone - Multi-node AI orchestration platform with tool use, agent routing, and cluster simulation."""
__version__ = "0.8.5"
__version__ = "0.8.6"
+1 -1
View File
@@ -2803,7 +2803,7 @@ class ChatSession:
"needs_approval": False,
"error": (
"Error: No web search backend available. "
"Install duckduckgo-search (`pip install duckduckgo-search`), "
"Install the ddg extra (`pip install turnstone[ddg]`), "
"configure a Tavily API key, or set tools.web_search_backend."
),
}
+4 -4
View File
@@ -3,7 +3,7 @@
``web_search`` is an abstract capability with swappable clients:
* **TavilyClient** — paid, high quality, requires API key
* **DuckDuckGoClient** — free, no API key, uses ``duckduckgo-search``
* **DuckDuckGoClient** — free, no API key, uses ``ddgs``
Auto-detection (default): Tavily if key present, else DDG if installed,
else ``None`` (tool removed from tool list).
@@ -63,7 +63,7 @@ class DuckDuckGoClient:
self._timeout = timeout
def search(self, query: str, max_results: int = 5, **kwargs: Any) -> str:
from duckduckgo_search import DDGS # type: ignore[import-not-found]
from ddgs import DDGS # type: ignore[import-not-found]
with DDGS(timeout=int(self._timeout)) as ddgs:
raw = list(ddgs.text(query, max_results=max_results))
@@ -130,9 +130,9 @@ def _format_ddg(results: list[dict[str, Any]], query: str) -> str:
def _ddg_available() -> bool:
"""Check if duckduckgo-search is installed."""
"""Check if the ddgs package is installed."""
try:
import duckduckgo_search # noqa: F401
import ddgs # noqa: F401
return True
except ImportError:
Generated
+18 -18
View File
@@ -604,6 +604,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" },
]
[[package]]
name = "ddgs"
version = "9.11.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "lxml" },
{ name = "primp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d7/3c/0ea1d5685bb0cd3607556d76da30e64c158e5d86bb16a3555f232fe0a33f/ddgs-9.11.4.tar.gz", hash = "sha256:445e22d3ffa16f893bfb0a717593bb0f34d1ceb1df68b6ff4ec27b1a3cdf6941", size = 34798, upload-time = "2026-03-14T18:15:18.086Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/46/f9412fafdebd4eaba366b5336ee4987995ccc3db1bc450e865a34db5fb63/ddgs-9.11.4-py3-none-any.whl", hash = "sha256:62d4d05b25db5d225a727c0a2771ef40258c1d894582c73dad24c88bf90f918b", size = 43681, upload-time = "2026-03-14T18:15:17.063Z" },
]
[[package]]
name = "discord-py"
version = "2.7.1"
@@ -635,20 +649,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
]
[[package]]
name = "duckduckgo-search"
version = "8.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "lxml" },
{ name = "primp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/ef/07791a05751e6cc9de1dd49fb12730259ee109b18e6d097e25e6c32d5617/duckduckgo_search-8.1.1.tar.gz", hash = "sha256:9da91c9eb26a17e016ea1da26235d40404b46b0565ea86d75a9f78cc9441f935", size = 22868, upload-time = "2025-07-06T15:30:59.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/72/c027b3b488b1010cf71670032fcf7e681d44b81829d484bb04e31a949a8d/duckduckgo_search-8.1.1-py3-none-any.whl", hash = "sha256:f48adbb06626ee05918f7e0cef3a45639e9939805c4fc179e68c48a12f1b5062", size = 18932, upload-time = "2025-07-06T15:30:58.339Z" },
]
[[package]]
name = "frozenlist"
version = "1.8.0"
@@ -2322,7 +2322,7 @@ wheels = [
[[package]]
name = "turnstone"
version = "0.8.5"
version = "0.8.6"
source = { editable = "." }
dependencies = [
{ name = "alembic" },
@@ -2345,8 +2345,8 @@ dependencies = [
all = [
{ name = "anthropic" },
{ name = "croniter" },
{ name = "ddgs" },
{ name = "discord-py" },
{ name = "duckduckgo-search" },
{ name = "psycopg", extra = ["binary"] },
{ name = "redis" },
]
@@ -2358,7 +2358,7 @@ console = [
{ name = "redis" },
]
ddg = [
{ name = "duckduckgo-search" },
{ name = "ddgs" },
]
dev = [
{ name = "mypy" },
@@ -2391,8 +2391,8 @@ requires-dist = [
{ name = "bcrypt", specifier = ">=4.0" },
{ name = "croniter", marker = "extra == 'console'", specifier = ">=3.0" },
{ name = "croniter", marker = "extra == 'test'", specifier = ">=3.0" },
{ name = "ddgs", marker = "extra == 'ddg'", specifier = ">=9.0" },
{ name = "discord-py", marker = "extra == 'discord'", specifier = ">=2.4" },
{ name = "duckduckgo-search", marker = "extra == 'ddg'", specifier = ">=8.0" },
{ name = "httpx", specifier = ">=0.28" },
{ name = "httpx-sse", specifier = ">=0.4" },
{ name = "mcp", specifier = ">=1.6" },