ddgs-haystack 1.0.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ddgs_haystack-2.0.0/CHANGELOG.md +16 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/PKG-INFO +5 -6
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/README.md +1 -2
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/pyproject.toml +3 -2
- ddgs_haystack-2.0.0/src/haystack_integrations/components/websearch/ddgs/ddgs_websearch.py +217 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/tests/test_ddgs_websearch.py +31 -2
- ddgs_haystack-1.0.0/src/haystack_integrations/components/websearch/ddgs/ddgs_websearch.py +0 -161
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/.gitignore +0 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/LICENSE.txt +0 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/pydoc/config_docusaurus.yml +0 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/src/haystack_integrations/components/websearch/ddgs/__init__.py +0 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/src/haystack_integrations/components/websearch/py.typed +0 -0
- {ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [integrations/ddgs-v1.1.0] - 2026-07-22
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Expose per-run parameter overrides on DDGSWebSearch (#3634)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/ddgs-v1.0.0] - 2026-07-21
|
|
11
|
+
|
|
12
|
+
### 🚀 Features
|
|
13
|
+
|
|
14
|
+
- Add ddgs (Dux Distributed Global Search) web search integration (#3623)
|
|
15
|
+
|
|
16
|
+
<!-- generated by git-cliff -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: ddgs-haystack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Haystack integration for ddgs
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/ddgs#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -12,14 +12,14 @@ Keywords: agent tool,ddgs,duckduckgo,haystack,metasearch,web search,websearch
|
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
20
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
21
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
23
|
Requires-Dist: ddgs>=9.14.3
|
|
24
24
|
Requires-Dist: haystack-ai>=2.24.1
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
[](https://pypi.org/project/ddgs-haystack)
|
|
30
30
|
[](https://pypi.org/project/ddgs-haystack)
|
|
31
31
|
|
|
32
|
+
- [Integration page](https://haystack.deepset.ai/integrations/ddgs)
|
|
32
33
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/ddgs/CHANGELOG.md)
|
|
33
34
|
|
|
34
35
|
---
|
|
@@ -36,5 +37,3 @@ Description-Content-Type: text/markdown
|
|
|
36
37
|
## Contributing
|
|
37
38
|
|
|
38
39
|
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
39
|
-
|
|
40
|
-
`ddgs` requires no API key, so the integration tests run without any environment variables.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/ddgs-haystack)
|
|
4
4
|
[](https://pypi.org/project/ddgs-haystack)
|
|
5
5
|
|
|
6
|
+
- [Integration page](https://haystack.deepset.ai/integrations/ddgs)
|
|
6
7
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/ddgs/CHANGELOG.md)
|
|
7
8
|
|
|
8
9
|
---
|
|
@@ -10,5 +11,3 @@
|
|
|
10
11
|
## Contributing
|
|
11
12
|
|
|
12
13
|
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
13
|
-
|
|
14
|
-
`ddgs` requires no API key, so the integration tests run without any environment variables.
|
|
@@ -7,7 +7,7 @@ name = "ddgs-haystack"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Haystack integration for ddgs"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
keywords = ["haystack", "web search", "websearch", "ddgs", "duckduckgo", "metasearch", "agent tool"]
|
|
13
13
|
authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
|
|
@@ -15,11 +15,11 @@ classifiers = [
|
|
|
15
15
|
"License :: OSI Approved :: Apache Software License",
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Programming Language :: Python",
|
|
18
|
-
"Programming Language :: Python :: 3.10",
|
|
19
18
|
"Programming Language :: Python :: 3.11",
|
|
20
19
|
"Programming Language :: Python :: 3.12",
|
|
21
20
|
"Programming Language :: Python :: 3.13",
|
|
22
21
|
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Programming Language :: Python :: 3.15",
|
|
23
23
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
24
24
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
25
25
|
]
|
|
@@ -127,6 +127,7 @@ ignore = [
|
|
|
127
127
|
"PLR0912",
|
|
128
128
|
"PLR0913",
|
|
129
129
|
"PLR0915",
|
|
130
|
+
"PLR0917",
|
|
130
131
|
# Allow `Any` type - used legitimately for dynamic types and SDK boundaries
|
|
131
132
|
"ANN401",
|
|
132
133
|
]
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from haystack import Document, component, logging
|
|
9
|
+
|
|
10
|
+
from ddgs import DDGS
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@component
|
|
16
|
+
class DDGSWebSearch:
|
|
17
|
+
"""
|
|
18
|
+
Searches the web with ddgs (Dux Distributed Global Search) and returns results as Haystack Documents.
|
|
19
|
+
|
|
20
|
+
[ddgs](https://github.com/deedy5/ddgs) is a free, **keyless** metasearch library that aggregates
|
|
21
|
+
results from multiple backends (DuckDuckGo, Google, Bing, Brave, Yahoo, Yandex, Mullvad, and more),
|
|
22
|
+
so no API key is required.
|
|
23
|
+
|
|
24
|
+
### Usage example
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from haystack_integrations.components.websearch.ddgs import DDGSWebSearch
|
|
28
|
+
|
|
29
|
+
websearch = DDGSWebSearch(top_k=5)
|
|
30
|
+
result = websearch.run(query="What is Haystack by deepset?")
|
|
31
|
+
|
|
32
|
+
documents = result["documents"]
|
|
33
|
+
links = result["links"]
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
top_k: int = 10,
|
|
40
|
+
backend: str = "auto",
|
|
41
|
+
region: str = "us-en",
|
|
42
|
+
safesearch: str = "moderate",
|
|
43
|
+
search_params: dict[str, Any] | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Initialize the DDGSWebSearch component.
|
|
47
|
+
|
|
48
|
+
:param top_k:
|
|
49
|
+
Maximum number of results to return.
|
|
50
|
+
:param backend:
|
|
51
|
+
Comma-separated ddgs backends to query, or `"auto"` to let ddgs choose
|
|
52
|
+
(for example `"duckduckgo, google, brave"`). See the ddgs docs for the full list.
|
|
53
|
+
:param region:
|
|
54
|
+
Region/locale for the search, for example `"us-en"`, `"de-de"`, or `"wt-wt"` (no region).
|
|
55
|
+
:param safesearch:
|
|
56
|
+
Safe-search level: `"on"`, `"moderate"`, or `"off"`.
|
|
57
|
+
:param search_params:
|
|
58
|
+
Additional keyword arguments forwarded to `DDGS().text()` (for example `page` or
|
|
59
|
+
`timelimit`). Values here override `backend`, `region`, `safesearch`, and `top_k`
|
|
60
|
+
on conflict.
|
|
61
|
+
"""
|
|
62
|
+
self.top_k = top_k
|
|
63
|
+
self.backend = backend
|
|
64
|
+
self.region = region
|
|
65
|
+
self.safesearch = safesearch
|
|
66
|
+
self.search_params = search_params or {}
|
|
67
|
+
self._client: DDGS | None = None
|
|
68
|
+
|
|
69
|
+
def warm_up(self) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Initialize the ddgs client.
|
|
72
|
+
|
|
73
|
+
Called automatically on first use. Can be called explicitly to avoid cold-start latency.
|
|
74
|
+
"""
|
|
75
|
+
if self._client is None:
|
|
76
|
+
self._client = DDGS()
|
|
77
|
+
|
|
78
|
+
def _search(
|
|
79
|
+
self,
|
|
80
|
+
query: str,
|
|
81
|
+
top_k: int | None = None,
|
|
82
|
+
backend: str | None = None,
|
|
83
|
+
region: str | None = None,
|
|
84
|
+
safesearch: str | None = None,
|
|
85
|
+
search_params: dict[str, Any] | None = None,
|
|
86
|
+
) -> dict[str, list[Document] | list[str]]:
|
|
87
|
+
"""
|
|
88
|
+
Run the blocking ddgs search and normalise the results.
|
|
89
|
+
|
|
90
|
+
:param query:
|
|
91
|
+
Search query.
|
|
92
|
+
:returns:
|
|
93
|
+
A dictionary with `documents` and `links` keys.
|
|
94
|
+
"""
|
|
95
|
+
if self._client is None:
|
|
96
|
+
self.warm_up()
|
|
97
|
+
if self._client is None:
|
|
98
|
+
msg = "DDGSWebSearch client failed to initialize."
|
|
99
|
+
raise RuntimeError(msg)
|
|
100
|
+
|
|
101
|
+
params: dict[str, Any] = {
|
|
102
|
+
"region": region if region is not None else self.region,
|
|
103
|
+
"safesearch": safesearch if safesearch is not None else self.safesearch,
|
|
104
|
+
"backend": backend if backend is not None else self.backend,
|
|
105
|
+
"max_results": top_k if top_k is not None else self.top_k,
|
|
106
|
+
}
|
|
107
|
+
params.update(search_params if search_params is not None else self.search_params)
|
|
108
|
+
|
|
109
|
+
results = self._client.text(query, **params)
|
|
110
|
+
documents, links = self._parse_response(results)
|
|
111
|
+
|
|
112
|
+
logger.debug(
|
|
113
|
+
"ddgs returned {number_documents} documents for the query '{query}'",
|
|
114
|
+
number_documents=len(documents),
|
|
115
|
+
query=query,
|
|
116
|
+
)
|
|
117
|
+
return {"documents": documents, "links": links}
|
|
118
|
+
|
|
119
|
+
@component.output_types(documents=list[Document], links=list[str])
|
|
120
|
+
def run(
|
|
121
|
+
self,
|
|
122
|
+
query: str,
|
|
123
|
+
top_k: int | None = None,
|
|
124
|
+
*,
|
|
125
|
+
backend: str | None = None,
|
|
126
|
+
region: str | None = None,
|
|
127
|
+
safesearch: str | None = None,
|
|
128
|
+
search_params: dict[str, Any] | None = None,
|
|
129
|
+
) -> dict[str, list[Document] | list[str]]:
|
|
130
|
+
"""
|
|
131
|
+
Use ddgs to search the web.
|
|
132
|
+
|
|
133
|
+
:param query:
|
|
134
|
+
Search query.
|
|
135
|
+
:param top_k:
|
|
136
|
+
Optional per-run override of the maximum number of results. If not provided, the
|
|
137
|
+
init-time `top_k` is used.
|
|
138
|
+
:param backend:
|
|
139
|
+
Optional per-run override of the ddgs backends. If not provided, the init-time
|
|
140
|
+
`backend` is used.
|
|
141
|
+
:param region:
|
|
142
|
+
Optional per-run override of the region/locale. If not provided, the init-time
|
|
143
|
+
`region` is used.
|
|
144
|
+
:param safesearch:
|
|
145
|
+
Optional per-run override of the safe-search level. If not provided, the init-time
|
|
146
|
+
`safesearch` is used.
|
|
147
|
+
:param search_params:
|
|
148
|
+
Optional per-run override of the extra `DDGS().text()` arguments. If provided, fully
|
|
149
|
+
replaces the init-time `search_params`.
|
|
150
|
+
:returns:
|
|
151
|
+
A dictionary with the following keys:
|
|
152
|
+
- `documents`: List of documents returned by the search backends.
|
|
153
|
+
- `links`: List of links returned by the search backends.
|
|
154
|
+
"""
|
|
155
|
+
return self._search(query, top_k, backend, region, safesearch, search_params)
|
|
156
|
+
|
|
157
|
+
@component.output_types(documents=list[Document], links=list[str])
|
|
158
|
+
async def run_async(
|
|
159
|
+
self,
|
|
160
|
+
query: str,
|
|
161
|
+
top_k: int | None = None,
|
|
162
|
+
*,
|
|
163
|
+
backend: str | None = None,
|
|
164
|
+
region: str | None = None,
|
|
165
|
+
safesearch: str | None = None,
|
|
166
|
+
search_params: dict[str, Any] | None = None,
|
|
167
|
+
) -> dict[str, list[Document] | list[str]]:
|
|
168
|
+
"""
|
|
169
|
+
Asynchronously use ddgs to search the web.
|
|
170
|
+
|
|
171
|
+
ddgs has no native async API, so the blocking search runs in a worker thread. Same parameters
|
|
172
|
+
and return values as :meth:`run`.
|
|
173
|
+
|
|
174
|
+
:param query:
|
|
175
|
+
Search query.
|
|
176
|
+
:param top_k:
|
|
177
|
+
Optional per-run override of the maximum number of results. If not provided, the
|
|
178
|
+
init-time `top_k` is used.
|
|
179
|
+
:param backend:
|
|
180
|
+
Optional per-run override of the ddgs backends. If not provided, the init-time
|
|
181
|
+
`backend` is used.
|
|
182
|
+
:param region:
|
|
183
|
+
Optional per-run override of the region/locale. If not provided, the init-time
|
|
184
|
+
`region` is used.
|
|
185
|
+
:param safesearch:
|
|
186
|
+
Optional per-run override of the safe-search level. If not provided, the init-time
|
|
187
|
+
`safesearch` is used.
|
|
188
|
+
:param search_params:
|
|
189
|
+
Optional per-run override of the extra `DDGS().text()` arguments. If provided, fully
|
|
190
|
+
replaces the init-time `search_params`.
|
|
191
|
+
:returns:
|
|
192
|
+
A dictionary with `documents` and `links` keys.
|
|
193
|
+
"""
|
|
194
|
+
return await asyncio.to_thread(self._search, query, top_k, backend, region, safesearch, search_params)
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _parse_response(results: list[dict[str, Any]]) -> tuple[list[Document], list[str]]:
|
|
198
|
+
"""
|
|
199
|
+
Convert ddgs text results into Haystack Documents and links.
|
|
200
|
+
|
|
201
|
+
:param results:
|
|
202
|
+
The list of result dictionaries returned by `DDGS().text()`.
|
|
203
|
+
:returns:
|
|
204
|
+
A tuple of `(documents, links)`.
|
|
205
|
+
"""
|
|
206
|
+
documents: list[Document] = []
|
|
207
|
+
links: list[str] = []
|
|
208
|
+
|
|
209
|
+
for result in results:
|
|
210
|
+
url = result.get("href", "")
|
|
211
|
+
title = result.get("title", "")
|
|
212
|
+
content = result.get("body", "")
|
|
213
|
+
documents.append(Document(content=content, meta={"title": title, "url": url}))
|
|
214
|
+
if url:
|
|
215
|
+
links.append(url)
|
|
216
|
+
|
|
217
|
+
return documents, links
|
|
@@ -109,6 +109,25 @@ class TestDDGSWebSearch:
|
|
|
109
109
|
assert kwargs["region"] == "fr-fr"
|
|
110
110
|
assert kwargs["timelimit"] == "d"
|
|
111
111
|
|
|
112
|
+
@patch(DDGS_PATH)
|
|
113
|
+
def test_run_overrides_params_at_runtime(self, mock_ddgs):
|
|
114
|
+
mock_ddgs.return_value.text.return_value = []
|
|
115
|
+
ws = DDGSWebSearch(top_k=10, backend="auto", region="us-en", safesearch="moderate")
|
|
116
|
+
ws.run(
|
|
117
|
+
query="q",
|
|
118
|
+
top_k=3,
|
|
119
|
+
backend="duckduckgo, brave",
|
|
120
|
+
region="de-de",
|
|
121
|
+
safesearch="off",
|
|
122
|
+
search_params={"timelimit": "w"},
|
|
123
|
+
)
|
|
124
|
+
_, kwargs = mock_ddgs.return_value.text.call_args
|
|
125
|
+
assert kwargs["max_results"] == 3
|
|
126
|
+
assert kwargs["backend"] == "duckduckgo, brave"
|
|
127
|
+
assert kwargs["region"] == "de-de"
|
|
128
|
+
assert kwargs["safesearch"] == "off"
|
|
129
|
+
assert kwargs["timelimit"] == "w"
|
|
130
|
+
|
|
112
131
|
@pytest.mark.asyncio
|
|
113
132
|
@patch(DDGS_PATH)
|
|
114
133
|
async def test_run_async(self, mock_ddgs):
|
|
@@ -117,9 +136,19 @@ class TestDDGSWebSearch:
|
|
|
117
136
|
assert len(result["documents"]) == 2
|
|
118
137
|
assert result["links"] == ["https://haystack.deepset.ai", "https://www.deepset.ai"]
|
|
119
138
|
|
|
139
|
+
@pytest.mark.asyncio
|
|
140
|
+
@patch(DDGS_PATH)
|
|
141
|
+
async def test_run_async_overrides_params_at_runtime(self, mock_ddgs):
|
|
142
|
+
mock_ddgs.return_value.text.return_value = []
|
|
143
|
+
ws = DDGSWebSearch(top_k=10, region="us-en")
|
|
144
|
+
await ws.run_async(query="q", top_k=3, region="de-de")
|
|
145
|
+
_, kwargs = mock_ddgs.return_value.text.call_args
|
|
146
|
+
assert kwargs["max_results"] == 3
|
|
147
|
+
assert kwargs["region"] == "de-de"
|
|
148
|
+
|
|
120
149
|
@pytest.mark.integration
|
|
121
150
|
def test_web_search_integration(self):
|
|
122
|
-
result = DDGSWebSearch(top_k=3).run(query="
|
|
151
|
+
result = DDGSWebSearch(top_k=3).run(query="Python programming language")
|
|
123
152
|
assert len(result["documents"]) > 0
|
|
124
153
|
assert all(isinstance(doc, Document) for doc in result["documents"])
|
|
125
154
|
assert all(doc.meta.get("url") for doc in result["documents"])
|
|
@@ -128,6 +157,6 @@ class TestDDGSWebSearch:
|
|
|
128
157
|
@pytest.mark.asyncio
|
|
129
158
|
@pytest.mark.integration
|
|
130
159
|
async def test_web_search_async_integration(self):
|
|
131
|
-
result = await DDGSWebSearch(top_k=3).run_async(query="
|
|
160
|
+
result = await DDGSWebSearch(top_k=3).run_async(query="Python programming language")
|
|
132
161
|
assert len(result["documents"]) > 0
|
|
133
162
|
assert result["links"]
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from haystack import Document, component, logging
|
|
9
|
-
|
|
10
|
-
from ddgs import DDGS
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@component
|
|
16
|
-
class DDGSWebSearch:
|
|
17
|
-
"""
|
|
18
|
-
Searches the web with ddgs (Dux Distributed Global Search) and returns results as Haystack Documents.
|
|
19
|
-
|
|
20
|
-
[ddgs](https://github.com/deedy5/ddgs) is a free, **keyless** metasearch library that aggregates
|
|
21
|
-
results from multiple backends (DuckDuckGo, Google, Bing, Brave, Yahoo, Yandex, Mullvad, and more),
|
|
22
|
-
so no API key is required.
|
|
23
|
-
|
|
24
|
-
### Usage example
|
|
25
|
-
|
|
26
|
-
```python
|
|
27
|
-
from haystack_integrations.components.websearch.ddgs import DDGSWebSearch
|
|
28
|
-
|
|
29
|
-
websearch = DDGSWebSearch(top_k=5)
|
|
30
|
-
result = websearch.run(query="What is Haystack by deepset?")
|
|
31
|
-
|
|
32
|
-
documents = result["documents"]
|
|
33
|
-
links = result["links"]
|
|
34
|
-
```
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def __init__(
|
|
38
|
-
self,
|
|
39
|
-
top_k: int = 10,
|
|
40
|
-
backend: str = "auto",
|
|
41
|
-
region: str = "us-en",
|
|
42
|
-
safesearch: str = "moderate",
|
|
43
|
-
search_params: dict[str, Any] | None = None,
|
|
44
|
-
) -> None:
|
|
45
|
-
"""
|
|
46
|
-
Initialize the DDGSWebSearch component.
|
|
47
|
-
|
|
48
|
-
:param top_k:
|
|
49
|
-
Maximum number of results to return.
|
|
50
|
-
:param backend:
|
|
51
|
-
Comma-separated ddgs backends to query, or ``"auto"`` to let ddgs choose
|
|
52
|
-
(for example ``"duckduckgo, google, brave"``). See the ddgs docs for the full list.
|
|
53
|
-
:param region:
|
|
54
|
-
Region/locale for the search, for example ``"us-en"``, ``"de-de"``, or ``"wt-wt"`` (no region).
|
|
55
|
-
:param safesearch:
|
|
56
|
-
Safe-search level: ``"on"``, ``"moderate"``, or ``"off"``.
|
|
57
|
-
:param search_params:
|
|
58
|
-
Additional keyword arguments forwarded to ``DDGS().text()`` (for example ``page`` or
|
|
59
|
-
``timelimit``). Values here override ``backend``, ``region``, ``safesearch``, and ``top_k``
|
|
60
|
-
on conflict.
|
|
61
|
-
"""
|
|
62
|
-
self.top_k = top_k
|
|
63
|
-
self.backend = backend
|
|
64
|
-
self.region = region
|
|
65
|
-
self.safesearch = safesearch
|
|
66
|
-
self.search_params = search_params or {}
|
|
67
|
-
self._client: DDGS | None = None
|
|
68
|
-
|
|
69
|
-
def warm_up(self) -> None:
|
|
70
|
-
"""
|
|
71
|
-
Initialize the ddgs client.
|
|
72
|
-
|
|
73
|
-
Called automatically on first use. Can be called explicitly to avoid cold-start latency.
|
|
74
|
-
"""
|
|
75
|
-
if self._client is None:
|
|
76
|
-
self._client = DDGS()
|
|
77
|
-
|
|
78
|
-
def _search(self, query: str) -> dict[str, list[Document] | list[str]]:
|
|
79
|
-
"""
|
|
80
|
-
Run the blocking ddgs search and normalise the results.
|
|
81
|
-
|
|
82
|
-
:param query:
|
|
83
|
-
Search query.
|
|
84
|
-
:returns:
|
|
85
|
-
A dictionary with ``documents`` and ``links`` keys.
|
|
86
|
-
"""
|
|
87
|
-
if self._client is None:
|
|
88
|
-
self.warm_up()
|
|
89
|
-
if self._client is None:
|
|
90
|
-
msg = "DDGSWebSearch client failed to initialize."
|
|
91
|
-
raise RuntimeError(msg)
|
|
92
|
-
|
|
93
|
-
params: dict[str, Any] = {
|
|
94
|
-
"region": self.region,
|
|
95
|
-
"safesearch": self.safesearch,
|
|
96
|
-
"backend": self.backend,
|
|
97
|
-
"max_results": self.top_k,
|
|
98
|
-
}
|
|
99
|
-
params.update(self.search_params)
|
|
100
|
-
|
|
101
|
-
results = self._client.text(query, **params)
|
|
102
|
-
documents, links = self._parse_response(results)
|
|
103
|
-
|
|
104
|
-
logger.debug(
|
|
105
|
-
"ddgs returned {number_documents} documents for the query '{query}'",
|
|
106
|
-
number_documents=len(documents),
|
|
107
|
-
query=query,
|
|
108
|
-
)
|
|
109
|
-
return {"documents": documents, "links": links}
|
|
110
|
-
|
|
111
|
-
@component.output_types(documents=list[Document], links=list[str])
|
|
112
|
-
def run(self, query: str) -> dict[str, list[Document] | list[str]]:
|
|
113
|
-
"""
|
|
114
|
-
Use ddgs to search the web.
|
|
115
|
-
|
|
116
|
-
:param query:
|
|
117
|
-
Search query.
|
|
118
|
-
:returns:
|
|
119
|
-
A dictionary with the following keys:
|
|
120
|
-
- ``documents``: List of documents returned by the search backends.
|
|
121
|
-
- ``links``: List of links returned by the search backends.
|
|
122
|
-
"""
|
|
123
|
-
return self._search(query)
|
|
124
|
-
|
|
125
|
-
@component.output_types(documents=list[Document], links=list[str])
|
|
126
|
-
async def run_async(self, query: str) -> dict[str, list[Document] | list[str]]:
|
|
127
|
-
"""
|
|
128
|
-
Asynchronously use ddgs to search the web.
|
|
129
|
-
|
|
130
|
-
ddgs has no native async API, so the blocking search runs in a worker thread. Same parameters
|
|
131
|
-
and return values as :meth:`run`.
|
|
132
|
-
|
|
133
|
-
:param query:
|
|
134
|
-
Search query.
|
|
135
|
-
:returns:
|
|
136
|
-
A dictionary with ``documents`` and ``links`` keys.
|
|
137
|
-
"""
|
|
138
|
-
return await asyncio.to_thread(self._search, query)
|
|
139
|
-
|
|
140
|
-
@staticmethod
|
|
141
|
-
def _parse_response(results: list[dict[str, Any]]) -> tuple[list[Document], list[str]]:
|
|
142
|
-
"""
|
|
143
|
-
Convert ddgs text results into Haystack Documents and links.
|
|
144
|
-
|
|
145
|
-
:param results:
|
|
146
|
-
The list of result dictionaries returned by ``DDGS().text()``.
|
|
147
|
-
:returns:
|
|
148
|
-
A tuple of ``(documents, links)``.
|
|
149
|
-
"""
|
|
150
|
-
documents: list[Document] = []
|
|
151
|
-
links: list[str] = []
|
|
152
|
-
|
|
153
|
-
for result in results:
|
|
154
|
-
url = result.get("href", "")
|
|
155
|
-
title = result.get("title", "")
|
|
156
|
-
content = result.get("body", "")
|
|
157
|
-
documents.append(Document(content=content, meta={"title": title, "url": url}))
|
|
158
|
-
if url:
|
|
159
|
-
links.append(url)
|
|
160
|
-
|
|
161
|
-
return documents, links
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ddgs_haystack-1.0.0 → ddgs_haystack-2.0.0}/src/haystack_integrations/components/websearch/py.typed
RENAMED
|
File without changes
|
|
File without changes
|