deep-db-agents 0.1.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.
- deep_db_agents-0.1.0/.gitignore +26 -0
- deep_db_agents-0.1.0/LICENSE +21 -0
- deep_db_agents-0.1.0/PKG-INFO +449 -0
- deep_db_agents-0.1.0/README.md +386 -0
- deep_db_agents-0.1.0/examples/async_quickstart.py +45 -0
- deep_db_agents-0.1.0/examples/multi_db_orchestrator.py +82 -0
- deep_db_agents-0.1.0/examples/mysql_quickstart.py +42 -0
- deep_db_agents-0.1.0/pyproject.toml +80 -0
- deep_db_agents-0.1.0/src/deep_db_agents/__init__.py +36 -0
- deep_db_agents-0.1.0/src/deep_db_agents/backend_registry.py +96 -0
- deep_db_agents-0.1.0/src/deep_db_agents/base.py +55 -0
- deep_db_agents-0.1.0/src/deep_db_agents/connection.py +57 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/__init__.py +31 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/duckdb/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/duckdb/dialect.py +138 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/duckdb/prompt.py +34 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/duckdb/tools.py +135 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/elasticsearch/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/elasticsearch/dialect.py +51 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/elasticsearch/prompt.py +28 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/elasticsearch/tools.py +53 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mariadb/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mariadb/dialect.py +39 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mariadb/prompt.py +33 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mongodb/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mongodb/dialect.py +283 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mongodb/prompt.py +21 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mongodb/tools.py +184 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mysql/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mysql/dialect.py +97 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mysql/prompt.py +33 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/mysql/tools.py +69 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/neo4j/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/neo4j/dialect.py +251 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/neo4j/prompt.py +21 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/neo4j/tools.py +119 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/opensearch/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/opensearch/dialect.py +51 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/opensearch/prompt.py +27 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/opensearch/tools.py +51 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/postgres/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/postgres/dialect.py +96 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/postgres/prompt.py +32 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/postgres/tools.py +76 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/search_base.py +519 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/sql_base.py +511 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/sqlite/__init__.py +7 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/sqlite/dialect.py +108 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/sqlite/prompt.py +32 -0
- deep_db_agents-0.1.0/src/deep_db_agents/dialects/sqlite/tools.py +33 -0
- deep_db_agents-0.1.0/src/deep_db_agents/exceptions.py +41 -0
- deep_db_agents-0.1.0/src/deep_db_agents/factory.py +287 -0
- deep_db_agents-0.1.0/src/deep_db_agents/guardrails.py +122 -0
- deep_db_agents-0.1.0/src/deep_db_agents/observability.py +117 -0
- deep_db_agents-0.1.0/src/deep_db_agents/pooling.py +54 -0
- deep_db_agents-0.1.0/src/deep_db_agents/prompts/__init__.py +6 -0
- deep_db_agents-0.1.0/src/deep_db_agents/prompts/orchestrator.py +45 -0
- deep_db_agents-0.1.0/src/deep_db_agents/prompts/shared.py +39 -0
- deep_db_agents-0.1.0/src/deep_db_agents/py.typed +0 -0
- deep_db_agents-0.1.0/src/deep_db_agents/query_errors.py +88 -0
- deep_db_agents-0.1.0/src/deep_db_agents/registry.py +75 -0
- deep_db_agents-0.1.0/src/deep_db_agents/tabular.py +66 -0
- deep_db_agents-0.1.0/src/deep_db_agents/url.py +112 -0
- deep_db_agents-0.1.0/src/deep_db_agents/workspace.py +158 -0
- deep_db_agents-0.1.0/tests/conftest.py +62 -0
- deep_db_agents-0.1.0/tests/dialects/test_duckdb.py +110 -0
- deep_db_agents-0.1.0/tests/dialects/test_elasticsearch.py +265 -0
- deep_db_agents-0.1.0/tests/dialects/test_mariadb.py +36 -0
- deep_db_agents-0.1.0/tests/dialects/test_mongodb.py +167 -0
- deep_db_agents-0.1.0/tests/dialects/test_mysql.py +101 -0
- deep_db_agents-0.1.0/tests/dialects/test_neo4j.py +121 -0
- deep_db_agents-0.1.0/tests/dialects/test_opensearch.py +133 -0
- deep_db_agents-0.1.0/tests/dialects/test_postgres.py +50 -0
- deep_db_agents-0.1.0/tests/dialects/test_sqlite.py +135 -0
- deep_db_agents-0.1.0/tests/test_async.py +49 -0
- deep_db_agents-0.1.0/tests/test_backend_registry.py +75 -0
- deep_db_agents-0.1.0/tests/test_factory.py +168 -0
- deep_db_agents-0.1.0/tests/test_guardrails.py +37 -0
- deep_db_agents-0.1.0/tests/test_observability.py +61 -0
- deep_db_agents-0.1.0/tests/test_pooling.py +108 -0
- deep_db_agents-0.1.0/tests/test_query_errors.py +40 -0
- deep_db_agents-0.1.0/tests/test_registry.py +28 -0
- deep_db_agents-0.1.0/tests/test_url.py +60 -0
- deep_db_agents-0.1.0/tests/test_workspace.py +25 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# macOS
|
|
10
|
+
.DS_Store
|
|
11
|
+
|
|
12
|
+
# Virtualenv / tooling
|
|
13
|
+
.venv/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
|
|
17
|
+
# Env
|
|
18
|
+
.env
|
|
19
|
+
|
|
20
|
+
# Materialized query results / workspace
|
|
21
|
+
workspace/
|
|
22
|
+
*.parquet
|
|
23
|
+
|
|
24
|
+
#.vscode
|
|
25
|
+
snippets
|
|
26
|
+
RELEASE_CHECKLIST.md
|
|
@@ -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,449 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deep-db-agents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Factory to create Deep Agents (LangChain) specialized on different databases (MySQL, Postgres, MongoDB, Neo4j, SQLite, DuckDB).
|
|
5
|
+
Project-URL: Homepage, https://github.com/giurlanda/deep-db-agents
|
|
6
|
+
Project-URL: Repository, https://github.com/giurlanda/deep-db-agents
|
|
7
|
+
Project-URL: Issues, https://github.com/giurlanda/deep-db-agents/issues
|
|
8
|
+
Author: Hawk
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,database,deepagents,langchain,mysql,postgres,sql
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: deepagents<0.7,>=0.6
|
|
18
|
+
Requires-Dist: langchain-anthropic<2.0,>=1.0
|
|
19
|
+
Requires-Dist: langchain-core<2.0,>=1.0
|
|
20
|
+
Requires-Dist: langchain<2.0,>=1.0
|
|
21
|
+
Provides-Extra: all
|
|
22
|
+
Requires-Dist: duckdb>=1.0; extra == 'all'
|
|
23
|
+
Requires-Dist: elasticsearch>=8.0; extra == 'all'
|
|
24
|
+
Requires-Dist: langchain-quickjs<0.4,>=0.3; extra == 'all'
|
|
25
|
+
Requires-Dist: neo4j>=5.0; extra == 'all'
|
|
26
|
+
Requires-Dist: opensearch-py>=2.0; extra == 'all'
|
|
27
|
+
Requires-Dist: pandas>=2.0; extra == 'all'
|
|
28
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
|
|
29
|
+
Requires-Dist: pyarrow>=15.0; extra == 'all'
|
|
30
|
+
Requires-Dist: pymongo>=4.6; extra == 'all'
|
|
31
|
+
Requires-Dist: pymysql>=1.1; extra == 'all'
|
|
32
|
+
Provides-Extra: analysis
|
|
33
|
+
Requires-Dist: pandas>=2.0; extra == 'analysis'
|
|
34
|
+
Requires-Dist: pyarrow>=15.0; extra == 'analysis'
|
|
35
|
+
Provides-Extra: code-interpreter
|
|
36
|
+
Requires-Dist: langchain-quickjs<0.4,>=0.3; extra == 'code-interpreter'
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: langchain-openai; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: rich>=15.0.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
42
|
+
Provides-Extra: docs
|
|
43
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
44
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
45
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
46
|
+
Provides-Extra: duckdb
|
|
47
|
+
Requires-Dist: duckdb>=1.0; extra == 'duckdb'
|
|
48
|
+
Provides-Extra: elasticsearch
|
|
49
|
+
Requires-Dist: elasticsearch>=8.0; extra == 'elasticsearch'
|
|
50
|
+
Provides-Extra: mariadb
|
|
51
|
+
Requires-Dist: pymysql>=1.1; extra == 'mariadb'
|
|
52
|
+
Provides-Extra: mongodb
|
|
53
|
+
Requires-Dist: pymongo>=4.6; extra == 'mongodb'
|
|
54
|
+
Provides-Extra: mysql
|
|
55
|
+
Requires-Dist: pymysql>=1.1; extra == 'mysql'
|
|
56
|
+
Provides-Extra: neo4j
|
|
57
|
+
Requires-Dist: neo4j>=5.0; extra == 'neo4j'
|
|
58
|
+
Provides-Extra: opensearch
|
|
59
|
+
Requires-Dist: opensearch-py>=2.0; extra == 'opensearch'
|
|
60
|
+
Provides-Extra: postgres
|
|
61
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
|
|
62
|
+
Description-Content-Type: text/markdown
|
|
63
|
+
|
|
64
|
+
# deep-db-agents
|
|
65
|
+
|
|
66
|
+
Simplified creation of **Deep Agents** ([LangChain](https://github.com/langchain-ai/deepagents))
|
|
67
|
+
—generic or specialized on a specific database (MySQL, MariaDB, Postgres, MongoDB, Neo4j,
|
|
68
|
+
SQLite, DuckDB, Elasticsearch, OpenSearch)—through a single factory function.
|
|
69
|
+
|
|
70
|
+
📖 Full API reference: **[giurlanda.github.io/deep-db-agents](https://giurlanda.github.io/deep-db-agents/)**
|
|
71
|
+
|
|
72
|
+
## Idea
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from deep_db_agents import create_deep_db_agents
|
|
76
|
+
|
|
77
|
+
agent = create_deep_db_agents(
|
|
78
|
+
db_url="mysql://localhost:3306",
|
|
79
|
+
credential={"user": "user", "password": "my_password", "database": "shop"},
|
|
80
|
+
system="The `shop` database contains orders and customers. The orders table has millions of rows.",
|
|
81
|
+
model="claude-sonnet-4-5-20250929",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
result = agent.invoke({
|
|
85
|
+
"messages": [{"role": "user", "content": "How many orders in 2025, by region?"}]
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The factory:
|
|
90
|
+
|
|
91
|
+
1. reads the **scheme** of the URL (`mysql`, `postgres`, `mongodb`, `neo4j`, `sqlite`,
|
|
92
|
+
`duckdb`…) to pick the dialect the agent should specialize on;
|
|
93
|
+
2. builds the **tools** suited to that database, injecting the credentials (which stay in the
|
|
94
|
+
tools' closures, never in the prompt);
|
|
95
|
+
3. concatenates the dialect's **generic system prompt** — which encodes context-management
|
|
96
|
+
principles — with the `system` prompt passed by the user;
|
|
97
|
+
4. delegates to `create_deep_agent(tools=..., system_prompt=..., **kwargs)`.
|
|
98
|
+
|
|
99
|
+
All extra arguments (`model`, `subagents`, `checkpointer`, …) are forwarded as-is to
|
|
100
|
+
`create_deep_agent`.
|
|
101
|
+
|
|
102
|
+
The library also lets you query **several databases at once**, through
|
|
103
|
+
`create_deep_db_multi_agent` (see [Multi-database agents](#multi-database-agents)), and offers a
|
|
104
|
+
lighter, non-Deep-Agent alternative through `create_db_agent` (see
|
|
105
|
+
[`create_db_agent`: a lighter alternative](#create_db_agent-a-lighter-alternative)).
|
|
106
|
+
|
|
107
|
+
## Multi-database agents
|
|
108
|
+
|
|
109
|
+
`create_deep_db_multi_agent` builds an **orchestrator** agent that never queries a database
|
|
110
|
+
directly: it delegates each sub-question to the sub-agent specialized on the relevant database
|
|
111
|
+
(through the `task` tool) and combines the results. This is how you answer questions that span
|
|
112
|
+
multiple data sources.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from deep_db_agents import create_deep_db_agents, create_deep_db_multi_agent
|
|
116
|
+
|
|
117
|
+
orders_agent = create_deep_db_agents(
|
|
118
|
+
"postgres://localhost:5432",
|
|
119
|
+
credential={"user": "reader", "password": "secret", "database": "orders"},
|
|
120
|
+
)
|
|
121
|
+
events_agent = create_deep_db_agents(
|
|
122
|
+
"mongodb://localhost:27017",
|
|
123
|
+
credential={"database": "events"},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
orchestrator = create_deep_db_multi_agent(
|
|
127
|
+
db_agents={
|
|
128
|
+
"orders": {"description": "Orders and customers (Postgres)", "agent": orders_agent},
|
|
129
|
+
"events": {"description": "Raw event log (MongoDB)", "agent": events_agent},
|
|
130
|
+
},
|
|
131
|
+
system="The two databases share the `customer_id` field; join results in memory.",
|
|
132
|
+
model="claude-sonnet-4-5-20250929",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
result = orchestrator.invoke(
|
|
136
|
+
{"messages": [{"role": "user", "content": "Compare orders vs. events last week."}]}
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Each sub-agent keeps its own tools and credentials in its own closures — the orchestrator only
|
|
141
|
+
ever sees the sub-agents' descriptions and their final answers, never raw rows. `db_agents` values
|
|
142
|
+
must be agents already built by `create_deep_db_agents` (compiled, with a working `.invoke`).
|
|
143
|
+
|
|
144
|
+
If sub-agents share a materialization backend (see below), register it once and pass the same
|
|
145
|
+
`be_uuid` to every agent, including the orchestrator, and forward it through `config` on
|
|
146
|
+
`invoke`/`ainvoke` — see the full example in
|
|
147
|
+
[Materializing results: the backend registry](#materializing-results-the-backend-registry).
|
|
148
|
+
|
|
149
|
+
## `create_db_agent`: a lighter alternative
|
|
150
|
+
|
|
151
|
+
`create_db_agent` builds a plain LangChain agent (`langchain.agents.create_agent`) instead of a
|
|
152
|
+
Deep Agent. It goes through the same dialect resolution and tool/prompt construction as
|
|
153
|
+
`create_deep_db_agents`, so credentials, guardrails and error feedback all behave identically —
|
|
154
|
+
what differs is the surrounding harness:
|
|
155
|
+
|
|
156
|
+
- **No `materialize_*` tools.** They require a deepagents filesystem backend to write to; the
|
|
157
|
+
plain agent has none, so large-result materialization (see below) is unavailable — only the
|
|
158
|
+
guardrail-limited `run_query`/`sample_rows`-style tools are exposed.
|
|
159
|
+
- **No Deep Agent scaffolding.** No built-in planning/`TodoList`, no subagent delegation, no
|
|
160
|
+
virtual filesystem — just a single agent with tools and a system prompt.
|
|
161
|
+
- Same signature otherwise (`db_url`, `credential`, `system`, `guardrails`, `metrics`, `**kwargs`
|
|
162
|
+
forwarded to `create_agent`).
|
|
163
|
+
|
|
164
|
+
Use it when the questions are simple enough that you don't need multi-step planning or
|
|
165
|
+
file-backed results — e.g. quick lookups, dashboards, or a lightweight assistant embedded in
|
|
166
|
+
another application.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from deep_db_agents import create_db_agent
|
|
170
|
+
|
|
171
|
+
agent = create_db_agent(
|
|
172
|
+
"sqlite:///./data/app.db",
|
|
173
|
+
system="Answer briefly, cite the exact table and column names.",
|
|
174
|
+
)
|
|
175
|
+
result = agent.invoke({"messages": [{"role": "user", "content": "List all tables."}]})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Code interpreter (experimental)
|
|
179
|
+
|
|
180
|
+
`create_deep_db_agents(..., enable_code_interpreter=True)` attaches a `CodeInterpreterMiddleware`
|
|
181
|
+
(from the optional `langchain-quickjs` package, extra `code-interpreter`) to the agent. This gives
|
|
182
|
+
the model a sandboxed JavaScript execution tool that can also call the dialect's own DB tools
|
|
183
|
+
(`ptc`, "pass-through tools"), so it can fetch data and post-process it — reshape, aggregate,
|
|
184
|
+
compute statistics — in one code-execution step instead of many separate tool calls, saving
|
|
185
|
+
context.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install "deep-db-agents[code-interpreter]" # from PyPI
|
|
189
|
+
uv pip install -e ".[code-interpreter]" # from source
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
agent = create_deep_db_agents(
|
|
194
|
+
"postgres://localhost:5432",
|
|
195
|
+
credential={"user": "reader", "password": "secret", "database": "shop"},
|
|
196
|
+
enable_code_interpreter=True,
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This is **experimental**: the middleware and its interaction with the guardrails are newer and
|
|
201
|
+
less battle-tested than the rest of the library, and its interface may still change. It does not
|
|
202
|
+
bypass the guardrails (the code interpreter can only call the same wrapped tools the agent
|
|
203
|
+
already has), but it does give the model a more general execution capability — enable it only if
|
|
204
|
+
you need the extra data-manipulation power.
|
|
205
|
+
|
|
206
|
+
## Context-management principles
|
|
207
|
+
|
|
208
|
+
The tools and generic prompts enforce this defense hierarchy:
|
|
209
|
+
|
|
210
|
+
> **aggregate in the DB → limit and paginate → explore before extracting → materialize to file → summarize → hard guardrails**
|
|
211
|
+
|
|
212
|
+
Guardrails are enforced by the tool wrapper (not by the agent): a non-bypassable maximum
|
|
213
|
+
`LIMIT`, query timeouts, row estimation via `EXPLAIN`, a `SELECT`-only whitelist, and a
|
|
214
|
+
per-session row/token budget. Large datasets are **materialized to file** (Parquet/CSV), and
|
|
215
|
+
only metadata and previews are passed back to the agent.
|
|
216
|
+
|
|
217
|
+
`query_timeout_s` is enforced as a **client-side** timeout in addition to the server-side one,
|
|
218
|
+
so a query (or socket) that hangs gets interrupted instead of blocking the agent — especially
|
|
219
|
+
when the model issues several tool calls in parallel. For SQLite/DuckDB, which lack a native
|
|
220
|
+
`statement_timeout`, the limit is enforced by a watchdog calling `interrupt()`.
|
|
221
|
+
|
|
222
|
+
### Errors become feedback, not dead ends
|
|
223
|
+
|
|
224
|
+
When a query fails — bad syntax, a non-existent table/column/field, an incompatible operator or
|
|
225
|
+
type — the driver exception is **not** propagated raw to interrupt the agent's turn. It is turned
|
|
226
|
+
into a structured message (`query_errors.format_query_error`) that is returned as the tool's
|
|
227
|
+
output: error type, driver detail, the offending query (truncated, and with any credentials in
|
|
228
|
+
the message redacted), and a hint to fix and retry, exploring the schema first if needed. This
|
|
229
|
+
lets the model self-correct within the same conversation instead of failing the whole run.
|
|
230
|
+
|
|
231
|
+
The same applies to whitelist/scope violations (e.g. a write statement, or an index outside the
|
|
232
|
+
configured `credential["index"]` pattern on Elasticsearch/OpenSearch): the operation is blocked
|
|
233
|
+
*before* reaching the driver, but reported back as corrective feedback rather than a hard error.
|
|
234
|
+
Only the session-level guardrails (row budget exhausted, EXPLAIN row-estimate threshold exceeded)
|
|
235
|
+
remain hard exceptions — those signal a limit the agent must not be allowed to work around.
|
|
236
|
+
|
|
237
|
+
## Materializing results: the backend registry
|
|
238
|
+
|
|
239
|
+
The `materialize_query` tool (Deep Agent only, see [`create_db_agent`: a lighter
|
|
240
|
+
alternative](#create_db_agent-a-lighter-alternative)) writes large results to a file (CSV or
|
|
241
|
+
Parquet) and returns only metadata, a preview and numeric statistics to the agent — see
|
|
242
|
+
[`MaterializedResult`](src/deep_db_agents/workspace.py). To do that it needs a **deepagents
|
|
243
|
+
backend** (a `BackendProtocol` implementation, e.g. `FilesystemBackend`) to write to, and the
|
|
244
|
+
tool resolves it at call time, not at agent-construction time.
|
|
245
|
+
|
|
246
|
+
That indirection exists because the backend can't simply live in the tool's closure like the
|
|
247
|
+
credentials do: the same backend is often shared across several agents (e.g. all sub-agents of a
|
|
248
|
+
multi-database orchestrator), and passing the live object around risks leaking a reference that
|
|
249
|
+
outlives the session. Instead:
|
|
250
|
+
|
|
251
|
+
1. **Register** the backend once in the process-wide `BERegistry` singleton — `add()` returns an
|
|
252
|
+
opaque UUID.
|
|
253
|
+
2. Pass that **UUID** (not the backend) to every agent that should be able to use it, via
|
|
254
|
+
`config={"configurable": {"be_uuid": ...}}` at `invoke`/`ainvoke` time.
|
|
255
|
+
3. Inside the tool, the dialect looks up `runtime.config["configurable"]["be_uuid"]` and resolves
|
|
256
|
+
it back to the backend with `BERegistry().get(be_uuid)`. If the key is missing or unregistered,
|
|
257
|
+
the tool returns an error message to the agent instead of raising.
|
|
258
|
+
4. **Remove** the backend when you're done with the session (`BERegistry().remove(be_uuid)`) —
|
|
259
|
+
whoever calls `add()` is responsible for the matching `remove()`, otherwise the instance stays
|
|
260
|
+
referenced (and the workspace files reachable) for the whole process lifetime.
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from deepagents.backends import FilesystemBackend
|
|
264
|
+
from deep_db_agents import create_deep_db_agents
|
|
265
|
+
from deep_db_agents.backend_registry import BERegistry
|
|
266
|
+
|
|
267
|
+
ber = BERegistry()
|
|
268
|
+
be_uuid = ber.add(FilesystemBackend(root_dir="./workspace", virtual_mode=True))
|
|
269
|
+
|
|
270
|
+
agent = create_deep_db_agents(
|
|
271
|
+
"duckdb:///warehouse/dw.duckdb",
|
|
272
|
+
backend=ber.get(be_uuid), # forwarded to create_deep_agent, gives the agent its filesystem
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
result = agent.invoke(
|
|
276
|
+
{"messages": [{"role": "user", "content": "Export all 2025 orders to a file."}]},
|
|
277
|
+
config={"configurable": {"thread_id": "session-1", "be_uuid": be_uuid}},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
ber.remove(be_uuid) # when the session ends
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The `backend=` kwarg (forwarded to `create_deep_agent`) is what gives the agent its virtual
|
|
284
|
+
filesystem in the first place; `be_uuid` in `config` is what lets the `materialize_query` tool
|
|
285
|
+
find that same backend again when it runs. Both must point at the same registered instance.
|
|
286
|
+
|
|
287
|
+
## Metrics
|
|
288
|
+
|
|
289
|
+
Pass a `SessionMetrics` instance (from `deep_db_agents.observability`) to `metrics=` on either
|
|
290
|
+
factory function to get thread-safe counters for the whole session, readable after `invoke`:
|
|
291
|
+
|
|
292
|
+
- `queries_run` / `rows_returned` — successful executions and total rows returned.
|
|
293
|
+
- `estimate_blocked` — queries rejected by the `EXPLAIN` row-estimate guardrail before running.
|
|
294
|
+
- `budget_exhausted` — times the per-session row budget was hit.
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from deep_db_agents import create_deep_db_agents
|
|
298
|
+
from deep_db_agents.observability import SessionMetrics
|
|
299
|
+
|
|
300
|
+
metrics = SessionMetrics()
|
|
301
|
+
agent = create_deep_db_agents("postgres://localhost:5432", metrics=metrics, credential={...})
|
|
302
|
+
agent.invoke({"messages": [{"role": "user", "content": "How many orders last week?"}]})
|
|
303
|
+
|
|
304
|
+
print(metrics.summary()) # "queries run=3, rows returned=142, blocked by estimate=0, ..."
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
It's optional and created by the caller (one instance per agent/session) — the tools only update
|
|
308
|
+
the counters, they never create or reset the object.
|
|
309
|
+
|
|
310
|
+
## Installation
|
|
311
|
+
|
|
312
|
+
### From PyPI
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
pip install "deep-db-agents[mysql,postgres,analysis]" # install only the extras you need
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### From source (development)
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
uv venv
|
|
322
|
+
uv pip install -e ".[mysql,postgres,analysis,dev]" # install only the extras you need
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Available extras:
|
|
326
|
+
|
|
327
|
+
| Extra | Installs | Purpose |
|
|
328
|
+
|---|---|---|
|
|
329
|
+
| `mysql` | `pymysql` | MySQL dialect driver |
|
|
330
|
+
| `mariadb` | `pymysql` | MariaDB dialect driver (reuses the MySQL driver) |
|
|
331
|
+
| `postgres` | `psycopg[binary]` | Postgres dialect driver |
|
|
332
|
+
| `mongodb` | `pymongo` | MongoDB dialect driver |
|
|
333
|
+
| `neo4j` | `neo4j` | Neo4j dialect driver |
|
|
334
|
+
| `duckdb` | `duckdb` | DuckDB dialect driver |
|
|
335
|
+
| `elasticsearch` | `elasticsearch` | Elasticsearch dialect driver |
|
|
336
|
+
| `opensearch` | `opensearch-py` | OpenSearch dialect driver |
|
|
337
|
+
| `analysis` | `pandas`, `pyarrow` | Parquet support for `materialize_query` (large-result materialization) |
|
|
338
|
+
| `code-interpreter` | `langchain-quickjs` | Sandboxed JS execution tool (see [Code interpreter](#code-interpreter-experimental)) |
|
|
339
|
+
| `all` | every extra above | Every dialect driver + `analysis` + `code-interpreter` |
|
|
340
|
+
| `dev` | `pytest`, `ruff`, `langchain-openai`, `rich` | Test/lint tooling for contributing to the library |
|
|
341
|
+
| `docs` | `mkdocs`, `mkdocs-material`, `mkdocstrings[python]` | Build the documentation site locally |
|
|
342
|
+
|
|
343
|
+
SQLite needs no extra (it uses the stdlib `sqlite3`). Extras can be combined, e.g.
|
|
344
|
+
`pip install "deep-db-agents[postgres,mongodb,analysis]"`.
|
|
345
|
+
|
|
346
|
+
## Dialect status
|
|
347
|
+
|
|
348
|
+
| Database | URL scheme | Status |
|
|
349
|
+
|----------|---------------------------|-----------|
|
|
350
|
+
| MySQL | `mysql` | Complete |
|
|
351
|
+
| MariaDB | `mariadb` | Complete |
|
|
352
|
+
| Postgres | `postgres`/`postgresql` | Complete |
|
|
353
|
+
| MongoDB | `mongodb` | Complete |
|
|
354
|
+
| Neo4j | `neo4j` | Complete |
|
|
355
|
+
| SQLite | `sqlite` | Complete |
|
|
356
|
+
| DuckDB | `duckdb` | Complete |
|
|
357
|
+
| Elasticsearch | `elasticsearch` | Complete |
|
|
358
|
+
| OpenSearch | `opensearch` | Complete |
|
|
359
|
+
|
|
360
|
+
### Credentials by dialect
|
|
361
|
+
|
|
362
|
+
`host`/`port` always come from the URL (`<scheme>://host:port`), never from `credential`; file-
|
|
363
|
+
based dialects (SQLite, DuckDB) use the URL's `path` instead and read no connection credentials.
|
|
364
|
+
Everything else — auth, target database, driver timeouts — is read from the `credential` dict,
|
|
365
|
+
whose expected keys depend on the dialect:
|
|
366
|
+
|
|
367
|
+
| Dialect | `credential` keys | Notes |
|
|
368
|
+
|---|---|---|
|
|
369
|
+
| MySQL / MariaDB | `user`, `password`, `database` (or `db`), `connect_timeout`, `read_timeout` | MariaDB reuses MySQL's connection logic unchanged. |
|
|
370
|
+
| Postgres | `user`, `password`, `database` (or `db`), `connect_timeout` | `database`/`db` maps to `dbname`. |
|
|
371
|
+
| MongoDB | `user`, `password`, `authSource` (or `auth_source`), `connect_timeout` | `database` selects the target DB (via `ConnectionConfig.database`). |
|
|
372
|
+
| Neo4j | `user`, `password`, `database`, `connect_timeout` | If `user` is omitted, the driver connects **unauthenticated**. |
|
|
373
|
+
| SQLite | `connect_timeout` | Path comes from the URL; always opened read-only (except `:memory:`). |
|
|
374
|
+
| DuckDB | *(none)* | Path comes from the URL; read-only unless `:memory:`; a folder path enables data-lake mode. |
|
|
375
|
+
| Elasticsearch | `use_ssl`, `verify_certs`, `ca_certs`, `api_key` **or** `user`/`password`, `index` | `api_key` takes priority over `user`/`password` if both are set. |
|
|
376
|
+
| OpenSearch | `use_ssl`, `verify_certs`, `ca_certs`, `user`/`password`, `index` | No `api_key` option, unlike Elasticsearch. |
|
|
377
|
+
|
|
378
|
+
`index` (Elasticsearch/OpenSearch only) restricts the agent to a single index name, a CSV list, or
|
|
379
|
+
a `*` wildcard pattern; every search tool validates the requested index against it and rejects
|
|
380
|
+
out-of-scope access as [corrective feedback](#errors-become-feedback-not-dead-ends), not a crash.
|
|
381
|
+
|
|
382
|
+
### File-based databases (SQLite, DuckDB)
|
|
383
|
+
|
|
384
|
+
For file-based DBs, the URL carries a **path** instead of host:port, following the SQLAlchemy
|
|
385
|
+
convention (the path is relative to the application's working directory):
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
create_deep_db_agents(db_url="sqlite:///data/app.db") # relative: ./data/app.db
|
|
389
|
+
create_deep_db_agents(db_url="sqlite:////var/lib/app.db") # absolute: /var/lib/app.db
|
|
390
|
+
create_deep_db_agents(db_url="duckdb:///warehouse/dw.duckdb")
|
|
391
|
+
create_deep_db_agents(db_url="duckdb:///lake/") # data lake: see below
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- **DuckDB data lake**: if the path is a **folder** (trailing slash), the `parquet`/`csv`/`json`
|
|
395
|
+
files inside it are exposed as queryable tables via SQL.
|
|
396
|
+
- DuckDB files are opened **read-only**, so multiple parallel tool calls can connect to the
|
|
397
|
+
same file.
|
|
398
|
+
- `:memory:` is supported (`sqlite://:memory:`), but with SQLite it does not share state across
|
|
399
|
+
tool calls (each call opens a new connection): suitable only for ephemeral/test usage.
|
|
400
|
+
|
|
401
|
+
## Fully local example (local database + local LLM)
|
|
402
|
+
|
|
403
|
+
Nothing in the factory is Anthropic-specific: `model`/`kwargs` are forwarded as-is to
|
|
404
|
+
`create_deep_agent`/`create_agent`, so any LangChain chat model works. Pairing a file-based
|
|
405
|
+
dialect (no server to run) with a local model server (e.g. [LM Studio](https://lmstudio.ai) or
|
|
406
|
+
Ollama exposing an OpenAI-compatible endpoint) gives you a fully offline agent:
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
from langchain_openai import ChatOpenAI
|
|
410
|
+
from deep_db_agents import create_deep_db_agents
|
|
411
|
+
|
|
412
|
+
local_model = ChatOpenAI(
|
|
413
|
+
model="qwen3-coder-30b", # whatever model is loaded in LM Studio/Ollama
|
|
414
|
+
base_url="http://127.0.0.1:1234/v1",
|
|
415
|
+
api_key="not-needed", # required by the client, ignored by the local server
|
|
416
|
+
temperature=0.1,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
agent = create_deep_db_agents(
|
|
420
|
+
"sqlite:///./chinook.db",
|
|
421
|
+
system="The `chinook` database is a digital music store (artists, albums, tracks, invoices).",
|
|
422
|
+
model=local_model,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
result = agent.invoke(
|
|
426
|
+
{"messages": [{"role": "user", "content": "Which genre has the most tracks?"}]}
|
|
427
|
+
)
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
No `credential` is needed for a local SQLite file, no network egress is required for either the
|
|
431
|
+
database or the model, and the same pattern works with DuckDB.
|
|
432
|
+
|
|
433
|
+
## Development
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
ruff check src tests
|
|
437
|
+
pytest
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Disclaimer
|
|
441
|
+
|
|
442
|
+
This library grants an LLM agent the ability to connect to and query real databases. While
|
|
443
|
+
guardrails (statement whitelisting, timeouts, row limits, EXPLAIN thresholds) are enforced in
|
|
444
|
+
code and are not bypassable by the agent's prompt, no safeguard eliminates all risk: model
|
|
445
|
+
behavior can be unpredictable, and misconfiguration (e.g. overly broad credentials) is outside
|
|
446
|
+
the library's control. Always point it at credentials scoped to the minimum required
|
|
447
|
+
privileges, prefer read-only accounts for exploratory use, and test against non-production data
|
|
448
|
+
before running it against anything that matters. Use of this library, and any consequences
|
|
449
|
+
arising from it, is entirely at the user's own risk and responsibility.
|