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.
- deep_search_agent-0.4.1/.gitignore +53 -0
- deep_search_agent-0.4.1/CHANGELOG.md +327 -0
- deep_search_agent-0.4.1/LICENSE +21 -0
- deep_search_agent-0.4.1/PKG-INFO +217 -0
- deep_search_agent-0.4.1/README.md +180 -0
- deep_search_agent-0.4.1/pyproject.toml +100 -0
- deep_search_agent-0.4.1/src/deep_search_agent/__init__.py +64 -0
- deep_search_agent-0.4.1/src/deep_search_agent/factory.py +385 -0
- deep_search_agent-0.4.1/src/deep_search_agent/metrics.py +434 -0
- deep_search_agent-0.4.1/src/deep_search_agent/middleware.py +257 -0
- deep_search_agent-0.4.1/src/deep_search_agent/prompts.py +304 -0
- deep_search_agent-0.4.1/src/deep_search_agent/py.typed +0 -0
- deep_search_agent-0.4.1/src/deep_search_agent/subagents.py +188 -0
- deep_search_agent-0.4.1/src/deep_search_agent/tools/__init__.py +6 -0
- deep_search_agent-0.4.1/src/deep_search_agent/tools/fetch.py +299 -0
- deep_search_agent-0.4.1/src/deep_search_agent/tools/search.py +286 -0
- deep_search_agent-0.4.1/tests/__init__.py +0 -0
- deep_search_agent-0.4.1/tests/conftest.py +89 -0
- deep_search_agent-0.4.1/tests/e2e/__init__.py +0 -0
- deep_search_agent-0.4.1/tests/e2e/conftest.py +31 -0
- deep_search_agent-0.4.1/tests/e2e/test_browser_smoke.py +19 -0
- deep_search_agent-0.4.1/tests/e2e/test_data/js_only_page.html +19 -0
- deep_search_agent-0.4.1/tests/e2e/test_data/sample_page.html +16 -0
- deep_search_agent-0.4.1/tests/e2e/test_js_render_fallback.py +31 -0
- deep_search_agent-0.4.1/tests/test_factory.py +577 -0
- deep_search_agent-0.4.1/tests/test_fetch_tool.py +396 -0
- deep_search_agent-0.4.1/tests/test_metrics.py +323 -0
- deep_search_agent-0.4.1/tests/test_middleware.py +143 -0
- 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)
|
|
41
|
+
[](https://github.com/giurlanda/deep-search-agent/tags)
|
|
42
|
+
[](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
|
+
```
|