deep-search-agent 0.4.1__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.
Files changed (29) hide show
  1. deep_search_agent-0.4.1/.gitignore +53 -0
  2. deep_search_agent-0.4.1/CHANGELOG.md +327 -0
  3. deep_search_agent-0.4.1/LICENSE +21 -0
  4. deep_search_agent-0.4.1/PKG-INFO +217 -0
  5. deep_search_agent-0.4.1/README.md +180 -0
  6. deep_search_agent-0.4.1/pyproject.toml +100 -0
  7. deep_search_agent-0.4.1/src/deep_search_agent/__init__.py +64 -0
  8. deep_search_agent-0.4.1/src/deep_search_agent/factory.py +385 -0
  9. deep_search_agent-0.4.1/src/deep_search_agent/metrics.py +434 -0
  10. deep_search_agent-0.4.1/src/deep_search_agent/middleware.py +257 -0
  11. deep_search_agent-0.4.1/src/deep_search_agent/prompts.py +304 -0
  12. deep_search_agent-0.4.1/src/deep_search_agent/py.typed +0 -0
  13. deep_search_agent-0.4.1/src/deep_search_agent/subagents.py +188 -0
  14. deep_search_agent-0.4.1/src/deep_search_agent/tools/__init__.py +6 -0
  15. deep_search_agent-0.4.1/src/deep_search_agent/tools/fetch.py +299 -0
  16. deep_search_agent-0.4.1/src/deep_search_agent/tools/search.py +286 -0
  17. deep_search_agent-0.4.1/tests/__init__.py +0 -0
  18. deep_search_agent-0.4.1/tests/conftest.py +89 -0
  19. deep_search_agent-0.4.1/tests/e2e/__init__.py +0 -0
  20. deep_search_agent-0.4.1/tests/e2e/conftest.py +31 -0
  21. deep_search_agent-0.4.1/tests/e2e/test_browser_smoke.py +19 -0
  22. deep_search_agent-0.4.1/tests/e2e/test_data/js_only_page.html +19 -0
  23. deep_search_agent-0.4.1/tests/e2e/test_data/sample_page.html +16 -0
  24. deep_search_agent-0.4.1/tests/e2e/test_js_render_fallback.py +31 -0
  25. deep_search_agent-0.4.1/tests/test_factory.py +577 -0
  26. deep_search_agent-0.4.1/tests/test_fetch_tool.py +396 -0
  27. deep_search_agent-0.4.1/tests/test_metrics.py +323 -0
  28. deep_search_agent-0.4.1/tests/test_middleware.py +143 -0
  29. deep_search_agent-0.4.1/tests/test_search_tool.py +238 -0
@@ -0,0 +1,53 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Distribution / packaging
8
+ build/
9
+ dist/
10
+ wheels/
11
+ *.egg-info/
12
+ *.egg
13
+ .eggs/
14
+
15
+ # Virtualenv / tooling
16
+ .venv/
17
+ venv/
18
+ .test-venv/
19
+ .ruff_cache/
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .pytype/
23
+ .coverage
24
+ .coverage.*
25
+ htmlcov/
26
+ coverage.xml
27
+
28
+ # Docs (mkdocs build output)
29
+ site/
30
+ public/
31
+
32
+ # Env
33
+ .env
34
+ .env.*
35
+
36
+ # Editors / OS
37
+ .DS_Store
38
+ .idea/
39
+ .vscode/
40
+
41
+ # Materialized query results / workspace
42
+ workspace/
43
+
44
+
45
+ # Manual test scripts against real databases
46
+ snippets/
47
+
48
+ # codebase
49
+ graphify-out
50
+
51
+ # Playwright
52
+ test-results/
53
+ playwright-report/
@@ -0,0 +1,327 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.1] - 2026-07-22
11
+
12
+ ### Added
13
+
14
+ - GitHub Actions `publish` workflow that builds, tests, and uploads the package
15
+ to PyPI on `v*` tag pushes via PyPI Trusted Publishing (OIDC) — no stored API
16
+ token. See the "Publishing" section of the README for the one-time PyPI
17
+ trusted-publisher setup.
18
+
19
+ ### Changed
20
+
21
+ - Trimmed the source distribution: it now ships only the package, its tests, and
22
+ `README`/`CHANGELOG`/`LICENSE`, excluding dev tooling (`uv.lock`,
23
+ `.python-version`), CI config, the docs site, and the benchmark harness.
24
+
25
+ ## [0.4.0] - 2026-07-17
26
+
27
+ ### Added
28
+
29
+ - Opt-in headless-rendering fallback for `fetch_url`. When static extraction
30
+ finds no main content — the usual outcome for JavaScript-only pages and bot
31
+ walls, which previously cost the agent otherwise valid sources — the page is
32
+ re-fetched through a headless Chromium and extracted again. Enable it with
33
+ `create_deep_search_agent(enable_js_render_fallback=True)`; `js_render_timeout`
34
+ (default `30.0` s) bounds the wait for a page to settle ([#23]).
35
+
36
+ The fallback is off by default and needs the new `js-render` extra plus the
37
+ browser binary (`pip install deep-search-agent[js-render]` and
38
+ `playwright install chromium`). When either is missing, or rendering fails,
39
+ the tool returns an `ERROR: ...` string as usual instead of raising, so the
40
+ agent reroutes to another source. Rendering runs on a dedicated worker
41
+ thread, keeping Playwright's sync API off any live asyncio loop.
42
+
43
+ ## [0.3.2] - 2026-07-17
44
+
45
+ ### Changed
46
+
47
+ - `fetch_url` now truncates long documents head+tail instead of keeping only
48
+ the opening. Previously the first `max_content_chars` were kept and the rest
49
+ discarded, which threw away exactly the sections that carry conclusions,
50
+ references and the most recent updates. The tool now keeps 60% of the budget
51
+ from the start and 40% from the end, joined by an explicit marker signalling
52
+ the omitted middle section. Both cuts snap to the nearest paragraph or
53
+ sentence boundary in the outer half of their slice, falling back to a hard
54
+ cut when no boundary qualifies, so neither end reads as a fragment ([#25]).
55
+
56
+ The truncation marker changed from a trailing `...(content truncated)` to a
57
+ mid-document `...(middle section omitted for length)...`. Code matching on
58
+ the old marker string needs updating.
59
+
60
+ ## [0.3.1] - 2026-07-17
61
+
62
+ ### Fixed
63
+
64
+ - The rubric grader no longer flags a phantom "incomplete/truncated" gap on
65
+ long final reports. `deepagents`' `RubricMiddleware` truncates every
66
+ transcript message it sends to the grader at 4,000 chars, which cut off the
67
+ tail of the orchestrator's synthesized answer (the "Gaps & limitations"
68
+ section and the numbered bibliography) and made the grader report a
69
+ completeness gap that did not exist. The factory now wires a new
70
+ `DeepSearchRubricMiddleware` — a thin subclass of `RubricMiddleware` that
71
+ passes the final answer to the grader untruncated while keeping earlier
72
+ transcript messages (e.g. large tool outputs) bounded ([#22]).
73
+
74
+ ### Added
75
+
76
+ - `DeepSearchRubricMiddleware` is exported from the package `__all__`.
77
+ - New `on_evaluation` factory parameter that forwards a callback to the
78
+ evaluator middleware; it is invoked with each `RubricEvaluation` after the
79
+ grader scores a research cycle (e.g. to log per-criterion verdicts or stream
80
+ progress). Exceptions it raises are logged and suppressed.
81
+
82
+ ## [0.3.0] - 2026-07-16
83
+
84
+ ### Added
85
+
86
+ - New `perspective-agent` sub-agent (`build_perspective_subagent` in
87
+ `subagents.py`), modeled on STORM-style perspective-guided question asking:
88
+ given the research topic, it runs 1-2 exploratory searches and writes 3-6
89
+ distinct perspectives (analysis axes, stakeholder viewpoints, dimensions of
90
+ the problem), each with 2-4 targeted questions, to
91
+ `/research/perspectives.md`.
92
+ - New `enable_perspectives` factory parameter (default `True`). When enabled,
93
+ the orchestrator prompt gets a new step 0 that delegates to
94
+ `perspective-agent` before decomposition and builds todos as
95
+ perspective → questions pairs instead of a flat sub-question list. Set to
96
+ `False` to skip the extra delegation cycle for simple, single-axis queries.
97
+ - New `PERSPECTIVE_AGENT_PROMPT` prompt template, exported from the package
98
+ `__all__` alongside the other sub-agent prompts.
99
+ - `"perspective-agent"` is now a reserved sub-agent name (alongside
100
+ `search-agent`, `fetch-agent`, `fact-check-agent`): a caller-supplied
101
+ sub-agent with that name raises `ValueError`, even when
102
+ `enable_perspectives=False` ([#5]).
103
+
104
+ ### Changed
105
+
106
+ - **Behavior change on the default path**: `enable_perspectives=True` by
107
+ default adds one extra delegation cycle (and its token cost) to every run,
108
+ without changing any function signature. Pass `enable_perspectives=False`
109
+ to restore the previous single-axis decomposition behavior.
110
+
111
+ ## [0.2.4] - 2026-07-16
112
+
113
+ ### Added
114
+
115
+ - New `max_query_variants` factory parameter (default `3`, validated as a
116
+ positive integer). It is threaded into `SEARCH_AGENT_PROMPT_TEMPLATE` the
117
+ same way `max_search_results_per_query` is, and quoted in the search agent's
118
+ instructions.
119
+
120
+ ### Changed
121
+
122
+ - `search-agent` now generates several query variants per sub-question on the
123
+ happy path — synonyms, broader/narrower terms, an English reformulation, or a
124
+ different angle — and issues them as parallel `internet_search` calls in the
125
+ same turn, then deduplicates by URL and keeps the best results across all
126
+ variants. Previously it ran a single query and only reformulated after an
127
+ outright failure, so recall depended on the orchestrator's first phrasing
128
+ ([#6]).
129
+
130
+ ## [0.2.3] - 2026-07-16
131
+
132
+ ### Changed
133
+
134
+ - The orchestrator's final synthesis is now outline-first. The single
135
+ free-form SYNTHESIZE step in `ORCHESTRATOR_PROMPT_TEMPLATE` is split into
136
+ three explicit steps: **outline** (write `report/outline.md` with sections
137
+ derived from the researched perspectives/sub-questions, depth scaled to the
138
+ question's complexity), **section-by-section synthesis** with numbered `[n]`
139
+ citations, and **assembly** into an executive summary, the sections, a
140
+ required "Gaps & limitations" section, and a numbered "Sources"
141
+ bibliography. Refinement cycles update the outline and keep the citations and
142
+ bibliography consistent. `DEEP_SEARCH_RUBRIC` gains criteria grading the
143
+ executive summary, per-section coverage of every planned
144
+ perspective/sub-question, the explicit gaps section, and a numbered
145
+ bibliography consistent with in-text citations. Prompt-only change; no public
146
+ API change ([#3]).
147
+
148
+ ## [0.2.2] - 2026-07-16
149
+
150
+ ### Added
151
+
152
+ - `internet_search` (the SearxNG-backed tool built by
153
+ `create_searxng_search_tool`) now accepts optional per-call `category` and
154
+ `time_range` arguments, forwarded to the SearxNG JSON API as `categories`
155
+ and `time_range`. `time_range` is validated against `day`/`week`/`month`/
156
+ `year` and an invalid value returns an `ERROR:` string without hitting the
157
+ network. The `search-agent` prompt now instructs the agent to set
158
+ `time_range` for time-sensitive sub-questions and `category="science"` for
159
+ academic/research ones, so different sub-questions can target recent or
160
+ scholarly sources instead of the same undifferentiated web search ([#4]).
161
+
162
+ ### Changed
163
+
164
+ - `README.md` now shows license, version, and Python-version badges and links
165
+ to the published documentation on GitHub Pages.
166
+
167
+ ## [0.2.1] - 2026-07-16
168
+
169
+ ### Changed
170
+
171
+ - The orchestrator, `search-agent`, and `fetch-agent` prompts now maintain and
172
+ consult a shared source index at `findings/_sources.md`: one line per URL
173
+ recording its status (`saved` / `failed` / `discarded`) and the associated
174
+ `findings/<source-slug>.md` file. Sub-agents check it before searching or
175
+ fetching and append their outcomes, and the orchestrator uses it to avoid
176
+ re-running queries or re-fetching pages — on refinement cycles it explicitly
177
+ instructs sub-agents to diversify domains relative to what is already indexed
178
+ ([#2]). Prompt-only change; no public API change.
179
+
180
+ ## [0.2.0] - 2026-07-16
181
+
182
+ ### Added
183
+
184
+ - `SessionMetrics`, a thread-safe collector of observability metrics, and an
185
+ optional `metrics` parameter on `create_deep_search_agent` to wire it in
186
+ ([#15]). When a `SessionMetrics` instance is passed, observation-only
187
+ middleware injected into the orchestrator and each built-in sub-agent record,
188
+ over the whole session:
189
+ - per research cycle: the orchestrator's tool-call counts, each sub-agent's
190
+ invocation count, and each sub-agent's tool-call counts;
191
+ - globally: total tool-call counts (orchestrator + sub-agents), total
192
+ sub-agent invocations, per-sub-agent execution time (average/min/max), and
193
+ the overall execution time.
194
+ - Metrics accumulate for the lifetime of the object (across research cycles
195
+ and successive invocations of a reused agent) until `reset()` is called;
196
+ read them through typed properties (`cycles`, `global_tool_calls`,
197
+ `subagent_stats`, ...) or as a JSON-serializable mapping via `to_dict()`.
198
+ - New public symbols `SessionMetrics`, `SubagentStats`, and `CycleMetrics`.
199
+ The sub-agent delegation tool (`task`) is tracked as a sub-agent invocation
200
+ rather than as an orchestrator tool. The `metrics` parameter defaults to
201
+ `None` (disabled), so behavior is backward compatible.
202
+
203
+ ## [0.1.4] - 2026-07-15
204
+
205
+ ### Added
206
+
207
+ - `searxng_rate_limit` and `searxng_budget` parameters on
208
+ `create_deep_search_agent` ([#13]) to throttle SearxNG usage when sub-agents
209
+ run searches concurrently through deepagents' thread pool:
210
+ - `searxng_rate_limit` sets the minimum number of seconds between two SearxNG
211
+ requests, enforced by a thread-safe min-interval limiter shared by the
212
+ built-in search tool. If a request would wait longer than `request_timeout`
213
+ for a free slot, the tool returns an `ERROR:` string instead of performing
214
+ it.
215
+ - `searxng_budget` caps the number of SearxNG searches per research cycle;
216
+ once exhausted the tool returns an `ERROR:` string telling the model no
217
+ budget is left. The counter is reset at each research-cycle boundary by the
218
+ new `SearchBudgetResetMiddleware`.
219
+ - New public symbols `SearchBudget` and `SearchBudgetResetMiddleware`; the
220
+ `create_searxng_search_tool` factory gains `min_request_interval` and
221
+ `budget` parameters. Both new factory parameters default to off/unlimited,
222
+ so behavior is backward compatible.
223
+
224
+ ## [0.1.3] - 2026-07-15
225
+
226
+ ### Added
227
+
228
+ - `subagents_middleware` parameter on `create_deep_search_agent` ([#11]):
229
+ extra middleware (e.g. logging, rate limiting) injected into each built-in
230
+ sub-agent (`search-agent`, `fetch-agent`, `fact-check-agent`) via their
231
+ `SubAgent.middleware` field. Sub-agents passed via `subagents` are
232
+ caller-owned and left untouched.
233
+
234
+ ## [0.1.2] - 2026-07-15
235
+
236
+ ### Added
237
+
238
+ - Explicit `backend` parameter on `create_deep_search_agent` ([#9]): the
239
+ factory now resolves a single filesystem backend (defaulting to a shared
240
+ `StateBackend` when none is given, otherwise propagating the caller's
241
+ instance) and hands it to `create_deep_agent`, so the orchestrator and every
242
+ sub-agent provably operate on the same virtual filesystem and
243
+ `findings/<source-slug>.md` files flow back to the orchestrator. Behavior is
244
+ backward compatible; `backend` is no longer an undocumented pass-through
245
+ kwarg.
246
+
247
+ ## [0.1.1] - 2026-07-15
248
+
249
+ ### Added
250
+
251
+ - Opt-in, real end-to-end `benchmark/` suite: runs the agent on five
252
+ deliberately complex research questions via a live LLM (OpenRouter) and a
253
+ live SearxNG instance, then grades each answer with an independent
254
+ LLM-as-a-judge on four 0-5 metrics. Lives outside `src/` and is never
255
+ shipped with the library; adds a `benchmark` extra (`langchain-openai`).
256
+ - MkDocs (Material + mkdocstrings) documentation site, published to GitHub
257
+ Pages via the `docs` workflow: home, installation, quickstart, architecture
258
+ and extending guides, and an auto-generated API reference.
259
+ - `[project.urls]` metadata (Homepage, Documentation, Repository, Issues).
260
+ - `ruff` in the dev dependency group; the codebase is now linted and formatted
261
+ with it.
262
+
263
+ ### Changed
264
+
265
+ - Gap-driven refinement cycles ([#1]): on re-entry after a failed rubric
266
+ grading, the orchestrator prompt now instructs the agent to map the grading
267
+ feedback to concrete gaps (recorded in `research/gaps.md`), add targeted
268
+ todos for those gaps only, and re-synthesize reusing the findings already
269
+ collected — instead of restarting the decomposition from scratch.
270
+
271
+ ### Fixed
272
+
273
+ - Package build: `dynamic = ["version"]` and the correct `[tool.hatch.version]`
274
+ path (`src/deep_search_agent/__init__.py`), so `uv build` and `twine check`
275
+ succeed.
276
+ - Classifiers aligned with `requires-python >= 3.12` (dropped 3.11, added 3.13).
277
+
278
+ ## [0.1.0] - 2026-07-14
279
+
280
+ Initial release.
281
+
282
+ ### Added
283
+
284
+ - `create_deep_search_agent` factory: orchestrator + specialized sub-agents
285
+ (`search-agent`, `fetch-agent`, `fact-check-agent`) + rubric-graded
286
+ refinement loop, built on LangChain `deepagents`.
287
+ - `internet_search` tool over the SearxNG JSON API
288
+ (`create_searxng_search_tool`).
289
+ - URL fetch tool extracting HTML with `trafilatura` and PDF with `pypdf`
290
+ (`create_fetch_url_tool`).
291
+ - `DefaultRubricMiddleware` for automatic rubric injection, and the default
292
+ `DEEP_SEARCH_RUBRIC`.
293
+ - Configurable research budgets, custom rubrics, extra search tools, and extra
294
+ sub-agents; all unrecognized keyword arguments pass through to
295
+ `create_deep_agent`.
296
+ - Typed package (`py.typed`), unit test suite (mocked HTTP/LLM), and opt-in
297
+ Playwright end-to-end tests.
298
+
299
+ [#1]: https://github.com/giurlanda/deep-search-agent/issues/1
300
+ [#2]: https://github.com/giurlanda/deep-search-agent/issues/2
301
+ [#3]: https://github.com/giurlanda/deep-search-agent/issues/3
302
+ [#4]: https://github.com/giurlanda/deep-search-agent/issues/4
303
+ [#5]: https://github.com/giurlanda/deep-search-agent/issues/5
304
+ [#6]: https://github.com/giurlanda/deep-search-agent/issues/6
305
+ [#9]: https://github.com/giurlanda/deep-search-agent/issues/9
306
+ [#11]: https://github.com/giurlanda/deep-search-agent/issues/11
307
+ [#13]: https://github.com/giurlanda/deep-search-agent/issues/13
308
+ [#15]: https://github.com/giurlanda/deep-search-agent/issues/15
309
+ [#22]: https://github.com/giurlanda/deep-search-agent/issues/22
310
+ [#23]: https://github.com/giurlanda/deep-search-agent/issues/23
311
+ [#25]: https://github.com/giurlanda/deep-search-agent/issues/25
312
+ [Unreleased]: https://github.com/giurlanda/deep-search-agent/compare/v0.4.1...HEAD
313
+ [0.4.1]: https://github.com/giurlanda/deep-search-agent/compare/v0.4.0...v0.4.1
314
+ [0.4.0]: https://github.com/giurlanda/deep-search-agent/compare/v0.3.2...v0.4.0
315
+ [0.3.2]: https://github.com/giurlanda/deep-search-agent/compare/v0.3.1...v0.3.2
316
+ [0.3.1]: https://github.com/giurlanda/deep-search-agent/compare/v0.3.0...v0.3.1
317
+ [0.3.0]: https://github.com/giurlanda/deep-search-agent/compare/v0.2.4...v0.3.0
318
+ [0.2.4]: https://github.com/giurlanda/deep-search-agent/compare/v0.2.3...v0.2.4
319
+ [0.2.3]: https://github.com/giurlanda/deep-search-agent/compare/v0.2.2...v0.2.3
320
+ [0.2.2]: https://github.com/giurlanda/deep-search-agent/compare/v0.2.1...v0.2.2
321
+ [0.2.1]: https://github.com/giurlanda/deep-search-agent/compare/v0.2.0...v0.2.1
322
+ [0.2.0]: https://github.com/giurlanda/deep-search-agent/compare/v0.1.4...v0.2.0
323
+ [0.1.4]: https://github.com/giurlanda/deep-search-agent/compare/v0.1.3...v0.1.4
324
+ [0.1.3]: https://github.com/giurlanda/deep-search-agent/compare/v0.1.2...v0.1.3
325
+ [0.1.2]: https://github.com/giurlanda/deep-search-agent/compare/v0.1.1...v0.1.2
326
+ [0.1.1]: https://github.com/giurlanda/deep-search-agent/compare/v0.1.0...v0.1.1
327
+ [0.1.0]: https://github.com/giurlanda/deep-search-agent/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Francesco Giurlanda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: deep-search-agent
3
+ Version: 0.4.1
4
+ Summary: Deep web search agent library built on LangChain deepagents: orchestrator + specialized subagents + iterative rubric-graded refinement.
5
+ Project-URL: Homepage, https://github.com/giurlanda/deep-search-agent
6
+ Project-URL: Documentation, https://giurlanda.github.io/deep-search-agent/
7
+ Project-URL: Repository, https://github.com/giurlanda/deep-search-agent
8
+ Project-URL: Issues, https://github.com/giurlanda/deep-search-agent/issues
9
+ Author-email: Hawk <francesco.giurlanda@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,deep-search,deepagents,langchain,llm,research,search
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: deepagents>=0.6.5
23
+ Requires-Dist: httpx>=0.28.1
24
+ Requires-Dist: langchain-core<2.0,>=1.0
25
+ Requires-Dist: langchain<2.0,>=1.0
26
+ Requires-Dist: pypdf>=6.14.2
27
+ Requires-Dist: trafilatura>=2.1.0
28
+ Provides-Extra: benchmark
29
+ Requires-Dist: langchain-openai<2.0,>=1.0; extra == 'benchmark'
30
+ Provides-Extra: docs
31
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
32
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
33
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
34
+ Provides-Extra: js-render
35
+ Requires-Dist: playwright>=1.49; extra == 'js-render'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # deep-search-agent
39
+
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
41
+ [![Version](https://img.shields.io/github/v/tag/giurlanda/deep-search-agent?sort=semver&label=version)](https://github.com/giurlanda/deep-search-agent/tags)
42
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/)
43
+
44
+ A Python library for deep internet searches (deep search), similar to the deep
45
+ research features of ChatGPT and Claude. Built on top of LangChain's
46
+ [deepagents](https://docs.langchain.com/oss/python/deepagents).
47
+
48
+ 📖 **Documentation:** <https://giurlanda.github.io/deep-search-agent/>
49
+
50
+ ## Architecture
51
+
52
+ The `create_deep_search_agent` factory returns a deep agent configured with the
53
+ **orchestrator + specialized sub-agents + evaluation loop** pattern:
54
+
55
+ | Component | Implementation |
56
+ |---|---|
57
+ | Orchestrator | Main agent (`create_deep_agent`): decomposes the query with `write_todos`, delegates, synthesizes with citations |
58
+ | `perspective-agent` | Explores the topic from 3-6 distinct angles (analysis axes, stakeholder viewpoints, dimensions of the problem) before decomposition, saved to `/research/perspectives.md`; enabled by default, toggle with `enable_perspectives=False` |
59
+ | `search-agent` | Web search via SearxNG (+ optional additional search tools), reformulates queries, saves results with their source |
60
+ | `fetch-agent` | Downloads and extracts content from URLs: clean HTML with `trafilatura`, PDFs read with `pypdf`, User-Agent from real browsers |
61
+ | `fact-check-agent` | Verifies claims against multiple independent sources (has both search and fetch) |
62
+ | Shared memory | deepagents virtual filesystem: each sub-agent writes `/findings/<source-slug>.md` with URL, date, and claims |
63
+ | Shared source index | `/findings/_sources.md`: one line per URL (`saved` / `failed` / `discarded`) that every agent consults and appends to, so searches and fetches are not duplicated across cycles |
64
+ | Evaluator/critic | `RubricMiddleware` (beta): an LLM grader evaluates the answer against a rubric and re-runs the orchestrator up to `max_research_cycles` cycles |
65
+
66
+ Each sub-agent runs with an isolated context: raw page content does not pollute
67
+ the orchestrator's memory; only the synthetic reports and the files in
68
+ `findings/` bubble up.
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ uv sync # from the repository
74
+ # or, as a dependency:
75
+ uv add deep-search-agent
76
+ ```
77
+
78
+ Requires Python ≥ 3.12. The search tool needs a reachable
79
+ [SearxNG](https://docs.searxng.org/) instance with the JSON format enabled
80
+ (default: `http://localhost:8888`).
81
+
82
+ ## Quickstart
83
+
84
+ ```python
85
+ from deep_search_agent import create_deep_search_agent
86
+
87
+ agent = create_deep_search_agent(
88
+ model="anthropic:claude-sonnet-4-6",
89
+ searxng_base_url="http://localhost:8888",
90
+ max_research_cycles=3,
91
+ )
92
+
93
+ result = agent.invoke(
94
+ {"messages": [{"role": "user", "content": "State of the art in quantum error correction in 2026?"}]},
95
+ config={"configurable": {"thread_id": "research-1"}},
96
+ )
97
+ print(result["messages"][-1].content)
98
+ ```
99
+
100
+ The default evaluation rubric (`DEEP_SEARCH_RUBRIC`) is injected automatically:
101
+ the refinement loop works with no configuration. For an ad-hoc rubric, pass it
102
+ in the invoke state (`{"rubric": "- ..."}`) or to the factory
103
+ (`rubric="- ..."`).
104
+
105
+ ## Factory parameters
106
+
107
+ Deep-search-specific parameters:
108
+
109
+ | Parameter | Default | Description |
110
+ |---|---|---|
111
+ | `model` | — (required) | Orchestrator model; inherited by sub-agents and the rubric grader |
112
+ | `max_research_cycles` | `3` | Maximum refinement cycles of the evaluator loop (and budget cited in the orchestrator prompt) |
113
+ | `max_query_variants` | `3` | Number of parallel query variants the search agent issues per sub-question (synonyms, broader/narrower terms, English variants) to widen recall |
114
+ | `max_search_results_per_query` | `5` | Maximum results per search query |
115
+ | `max_urls_to_scrape_per_cycle` | `3` | Maximum URLs to fetch per research cycle |
116
+ | `searxng_base_url` | `http://localhost:8888` | URL of the SearxNG instance |
117
+ | `searxng_engines` | `None` | List of SearxNG engines to restrict the search to |
118
+ | `searxng_rate_limit` | `None` | Minimum seconds between SearxNG requests (thread-safe, shared across concurrent searches); `None` disables rate limiting |
119
+ | `searxng_budget` | `None` | Maximum SearxNG searches per research cycle; when exhausted the tool returns an `ERROR:` telling the model no budget is left. `None` means unlimited |
120
+ | `request_timeout` | `15.0` | HTTP timeout (s) for search and fetch |
121
+ | `max_content_chars_per_page` | `20000` | Truncation of extracted content per page |
122
+ | `enable_js_render_fallback` | `False` | Re-fetch pages whose static HTML yields no content through a headless Chromium, recovering JavaScript-only pages and bot walls. Requires the `js-render` extra plus `playwright install chromium` |
123
+ | `js_render_timeout` | `30.0` | Seconds the headless renderer waits for a page to settle; ignored unless the fallback is enabled |
124
+ | `search_tools` | `None` | Additional search tools for search-agent, fact-check-agent, and perspective-agent (e.g. Tavily, RAG retrieval) |
125
+ | `enable_perspectives` | `True` | Adds `perspective-agent` and instructs the orchestrator to delegate to it before decomposing the query. Set `False` for simple queries where a single-axis decomposition is sufficient |
126
+ | `rubric` | `DEEP_SEARCH_RUBRIC` | Custom evaluation rubric |
127
+ | `auto_rubric` | `True` | Auto-inject the rubric into the state on every invoke |
128
+ | `on_evaluation` | `None` | Callback invoked with each `RubricEvaluation` after the grader scores a cycle (e.g. to log verdicts); exceptions are logged and suppressed |
129
+ | `subagents_middleware` | `()` | Extra middleware injected into each built-in sub-agent (perspective-agent, search-agent, fetch-agent, fact-check-agent) |
130
+ | `subagents` | `None` | Extra sub-agents, added to the built-in ones |
131
+ | `backend` | `StateBackend()` | Filesystem backend shared by the orchestrator and every sub-agent |
132
+
133
+ All other keyword arguments (`tools`, `checkpointer`, `store`, `skills`,
134
+ `interrupt_on`, ...) are passed through unchanged to `create_deep_agent`.
135
+
136
+ ## Extensions
137
+
138
+ ### Adding a retrieval agent (RAG) over the internal knowledge base
139
+
140
+ ```python
141
+ rag_agent = {
142
+ "name": "rag-agent",
143
+ "description": "Retrieval over the internal company knowledge base",
144
+ "system_prompt": "Search the vector store and save results to findings/.",
145
+ "tools": [my_vector_store_tool],
146
+ }
147
+
148
+ agent = create_deep_search_agent(
149
+ model="anthropic:claude-sonnet-4-6",
150
+ subagents=[rag_agent],
151
+ )
152
+ ```
153
+
154
+ ### Adding search engines
155
+
156
+ ```python
157
+ from langchain_tavily import TavilySearch
158
+
159
+ agent = create_deep_search_agent(
160
+ model="anthropic:claude-sonnet-4-6",
161
+ search_tools=[TavilySearch(max_results=5)],
162
+ )
163
+ ```
164
+
165
+ ### Persistent backend for findings
166
+
167
+ ```python
168
+ from deepagents.backends import FilesystemBackend
169
+
170
+ agent = create_deep_search_agent(
171
+ model="anthropic:claude-sonnet-4-6",
172
+ backend=FilesystemBackend(root_dir="./research", virtual_mode=True),
173
+ )
174
+ ```
175
+
176
+ ## Testing
177
+
178
+ ```bash
179
+ uv run pytest
180
+ ```
181
+
182
+ The unit tests require neither network nor API keys: HTTP and LLM are simulated.
183
+
184
+ ## Publishing
185
+
186
+ Releases are published to [PyPI](https://pypi.org/project/deep-search-agent/) by
187
+ the [`publish` workflow](.github/workflows/publish.yml), triggered on pushing a
188
+ `v*` tag. It uses **PyPI Trusted Publishing** (OIDC), so no API token is stored.
189
+
190
+ One-time setup on PyPI (Account → Publishing → *Add a pending publisher*):
191
+
192
+ | Field | Value |
193
+ |---|---|
194
+ | PyPI project name | `deep-search-agent` |
195
+ | Owner | `giurlanda` |
196
+ | Repository name | `deep-search-agent` |
197
+ | Workflow name | `publish.yml` |
198
+ | Environment name | `pypi` |
199
+
200
+ To cut a release: bump `__version__` in
201
+ [`src/deep_search_agent/__init__.py`](src/deep_search_agent/__init__.py), update
202
+ the `CHANGELOG`, then push a matching tag (e.g. `git tag v0.4.1 && git push
203
+ origin v0.4.1`). Build locally with `uv build` and validate with
204
+ `uvx twine check dist/*`.
205
+
206
+ ## Package structure
207
+
208
+ ```
209
+ src/deep_search_agent/
210
+ ├── factory.py # create_deep_search_agent
211
+ ├── prompts.py # orchestrator/sub-agent prompts + default rubric
212
+ ├── middleware.py # DefaultRubricMiddleware (rubric auto-injection)
213
+ ├── subagents.py # perspective/search/fetch/fact-check agent definitions
214
+ └── tools/
215
+ ├── search.py # SearxNG tool
216
+ └── fetch.py # URL fetch tool (trafilatura + pypdf)
217
+ ```