deepagents 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
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.
- deepagents/__init__.py +9 -1
- deepagents/backends/composite.py +136 -46
- deepagents/backends/state.py +50 -1
- deepagents/graph.py +68 -29
- deepagents/middleware/__init__.py +4 -0
- deepagents/middleware/memory.py +369 -0
- deepagents/middleware/skills.py +695 -0
- {deepagents-0.3.1.dist-info → deepagents-0.3.2.dist-info}/METADATA +2 -2
- {deepagents-0.3.1.dist-info → deepagents-0.3.2.dist-info}/RECORD +11 -9
- {deepagents-0.3.1.dist-info → deepagents-0.3.2.dist-info}/WHEEL +0 -0
- {deepagents-0.3.1.dist-info → deepagents-0.3.2.dist-info}/top_level.txt +0 -0
deepagents/__init__.py
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from deepagents.graph import create_deep_agent
|
|
4
4
|
from deepagents.middleware.filesystem import FilesystemMiddleware
|
|
5
|
+
from deepagents.middleware.memory import MemoryMiddleware
|
|
5
6
|
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
|
|
6
7
|
|
|
7
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"CompiledSubAgent",
|
|
10
|
+
"FilesystemMiddleware",
|
|
11
|
+
"MemoryMiddleware",
|
|
12
|
+
"SubAgent",
|
|
13
|
+
"SubAgentMiddleware",
|
|
14
|
+
"create_deep_agent",
|
|
15
|
+
]
|
deepagents/backends/composite.py
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Composite backend that routes file operations by path prefix.
|
|
2
|
+
|
|
3
|
+
Routes operations to different backends based on path prefixes. Use this when you
|
|
4
|
+
need different storage strategies for different paths (e.g., state for temp files,
|
|
5
|
+
persistent store for memories).
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
```python
|
|
9
|
+
from deepagents.backends.composite import CompositeBackend
|
|
10
|
+
from deepagents.backends.state import StateBackend
|
|
11
|
+
from deepagents.backends.store import StoreBackend
|
|
12
|
+
|
|
13
|
+
runtime = make_runtime()
|
|
14
|
+
composite = CompositeBackend(default=StateBackend(runtime), routes={"/memories/": StoreBackend(runtime)})
|
|
15
|
+
|
|
16
|
+
composite.write("/temp.txt", "ephemeral")
|
|
17
|
+
composite.write("/memories/note.md", "persistent")
|
|
18
|
+
```
|
|
19
|
+
"""
|
|
2
20
|
|
|
3
21
|
from collections import defaultdict
|
|
4
22
|
|
|
@@ -16,12 +34,38 @@ from deepagents.backends.protocol import (
|
|
|
16
34
|
from deepagents.backends.state import StateBackend
|
|
17
35
|
|
|
18
36
|
|
|
19
|
-
class CompositeBackend:
|
|
37
|
+
class CompositeBackend(BackendProtocol):
|
|
38
|
+
"""Routes file operations to different backends by path prefix.
|
|
39
|
+
|
|
40
|
+
Matches paths against route prefixes (longest first) and delegates to the
|
|
41
|
+
corresponding backend. Unmatched paths use the default backend.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
default: Backend for paths that don't match any route.
|
|
45
|
+
routes: Map of path prefixes to backends (e.g., {"/memories/": store_backend}).
|
|
46
|
+
sorted_routes: Routes sorted by length (longest first) for correct matching.
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
```python
|
|
50
|
+
composite = CompositeBackend(default=StateBackend(runtime), routes={"/memories/": StoreBackend(runtime), "/cache/": StoreBackend(runtime)})
|
|
51
|
+
|
|
52
|
+
composite.write("/temp.txt", "data")
|
|
53
|
+
composite.write("/memories/note.txt", "data")
|
|
54
|
+
```
|
|
55
|
+
"""
|
|
56
|
+
|
|
20
57
|
def __init__(
|
|
21
58
|
self,
|
|
22
59
|
default: BackendProtocol | StateBackend,
|
|
23
60
|
routes: dict[str, BackendProtocol],
|
|
24
61
|
) -> None:
|
|
62
|
+
"""Initialize composite backend.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
default: Backend for paths that don't match any route.
|
|
66
|
+
routes: Map of path prefixes to backends. Prefixes must start with "/"
|
|
67
|
+
and should end with "/" (e.g., "/memories/").
|
|
68
|
+
"""
|
|
25
69
|
# Default backend
|
|
26
70
|
self.default = default
|
|
27
71
|
|
|
@@ -32,14 +76,14 @@ class CompositeBackend:
|
|
|
32
76
|
self.sorted_routes = sorted(routes.items(), key=lambda x: len(x[0]), reverse=True)
|
|
33
77
|
|
|
34
78
|
def _get_backend_and_key(self, key: str) -> tuple[BackendProtocol, str]:
|
|
35
|
-
"""
|
|
79
|
+
"""Get backend for path and strip route prefix.
|
|
36
80
|
|
|
37
81
|
Args:
|
|
38
|
-
key:
|
|
82
|
+
key: File path to route.
|
|
39
83
|
|
|
40
84
|
Returns:
|
|
41
|
-
Tuple of (backend,
|
|
42
|
-
prefix removed
|
|
85
|
+
Tuple of (backend, stripped_path). The stripped path has the route
|
|
86
|
+
prefix removed but keeps the leading slash.
|
|
43
87
|
"""
|
|
44
88
|
# Check routes in order of length (longest first)
|
|
45
89
|
for prefix, backend in self.sorted_routes:
|
|
@@ -53,14 +97,23 @@ class CompositeBackend:
|
|
|
53
97
|
return self.default, key
|
|
54
98
|
|
|
55
99
|
def ls_info(self, path: str) -> list[FileInfo]:
|
|
56
|
-
"""List
|
|
100
|
+
"""List directory contents (non-recursive).
|
|
101
|
+
|
|
102
|
+
If path matches a route, lists only that backend. If path is "/", aggregates
|
|
103
|
+
default backend plus virtual route directories. Otherwise lists default backend.
|
|
57
104
|
|
|
58
105
|
Args:
|
|
59
|
-
path: Absolute path
|
|
106
|
+
path: Absolute directory path starting with "/".
|
|
60
107
|
|
|
61
108
|
Returns:
|
|
62
|
-
List of FileInfo
|
|
63
|
-
|
|
109
|
+
List of FileInfo dicts. Directories have trailing "/" and is_dir=True.
|
|
110
|
+
Route prefixes are restored in returned paths.
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
```python
|
|
114
|
+
infos = composite.ls_info("/")
|
|
115
|
+
infos = composite.ls_info("/memories/")
|
|
116
|
+
```
|
|
64
117
|
"""
|
|
65
118
|
# Check if path matches a specific route
|
|
66
119
|
for route_prefix, backend in self.sorted_routes:
|
|
@@ -169,6 +222,28 @@ class CompositeBackend:
|
|
|
169
222
|
path: str | None = None,
|
|
170
223
|
glob: str | None = None,
|
|
171
224
|
) -> list[GrepMatch] | str:
|
|
225
|
+
"""Search files for regex pattern.
|
|
226
|
+
|
|
227
|
+
Routes to backends based on path: specific route searches one backend,
|
|
228
|
+
"/" or None searches all backends, otherwise searches default backend.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
pattern: Regex pattern to search for.
|
|
232
|
+
path: Directory to search. None searches all backends.
|
|
233
|
+
glob: Glob pattern to filter files (e.g., "*.py", "**/*.txt").
|
|
234
|
+
Filters by filename, not content.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
List of GrepMatch dicts with path (route prefix restored), line
|
|
238
|
+
(1-indexed), and text. Returns error string on failure.
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
```python
|
|
242
|
+
matches = composite.grep_raw("TODO", path="/memories/")
|
|
243
|
+
matches = composite.grep_raw("error", path="/")
|
|
244
|
+
matches = composite.grep_raw("import", path="/", glob="*.py")
|
|
245
|
+
```
|
|
246
|
+
"""
|
|
172
247
|
# If path targets a specific route, search only that backend
|
|
173
248
|
for route_prefix, backend in self.sorted_routes:
|
|
174
249
|
if path is not None and path.startswith(route_prefix.rstrip("/")):
|
|
@@ -178,22 +253,26 @@ class CompositeBackend:
|
|
|
178
253
|
return raw
|
|
179
254
|
return [{**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw]
|
|
180
255
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
all_matches.extend(raw_default)
|
|
188
|
-
|
|
189
|
-
for route_prefix, backend in self.routes.items():
|
|
190
|
-
raw = backend.grep_raw(pattern, "/", glob)
|
|
191
|
-
if isinstance(raw, str):
|
|
256
|
+
# If path is None or "/", search default and all routed backends and merge
|
|
257
|
+
# Otherwise, search only the default backend
|
|
258
|
+
if path is None or path == "/":
|
|
259
|
+
all_matches: list[GrepMatch] = []
|
|
260
|
+
raw_default = self.default.grep_raw(pattern, path, glob) # type: ignore[attr-defined]
|
|
261
|
+
if isinstance(raw_default, str):
|
|
192
262
|
# This happens if error occurs
|
|
193
|
-
return
|
|
194
|
-
all_matches.extend(
|
|
263
|
+
return raw_default
|
|
264
|
+
all_matches.extend(raw_default)
|
|
195
265
|
|
|
196
|
-
|
|
266
|
+
for route_prefix, backend in self.routes.items():
|
|
267
|
+
raw = backend.grep_raw(pattern, "/", glob)
|
|
268
|
+
if isinstance(raw, str):
|
|
269
|
+
# This happens if error occurs
|
|
270
|
+
return raw
|
|
271
|
+
all_matches.extend({**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw)
|
|
272
|
+
|
|
273
|
+
return all_matches
|
|
274
|
+
# Path specified but doesn't match a route - search only default
|
|
275
|
+
return self.default.grep_raw(pattern, path, glob) # type: ignore[attr-defined]
|
|
197
276
|
|
|
198
277
|
async def agrep_raw(
|
|
199
278
|
self,
|
|
@@ -201,7 +280,10 @@ class CompositeBackend:
|
|
|
201
280
|
path: str | None = None,
|
|
202
281
|
glob: str | None = None,
|
|
203
282
|
) -> list[GrepMatch] | str:
|
|
204
|
-
"""Async version of grep_raw.
|
|
283
|
+
"""Async version of grep_raw.
|
|
284
|
+
|
|
285
|
+
See grep_raw() for detailed documentation on routing behavior and parameters.
|
|
286
|
+
"""
|
|
205
287
|
# If path targets a specific route, search only that backend
|
|
206
288
|
for route_prefix, backend in self.sorted_routes:
|
|
207
289
|
if path is not None and path.startswith(route_prefix.rstrip("/")):
|
|
@@ -211,22 +293,26 @@ class CompositeBackend:
|
|
|
211
293
|
return raw
|
|
212
294
|
return [{**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw]
|
|
213
295
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
all_matches.extend(raw_default)
|
|
221
|
-
|
|
222
|
-
for route_prefix, backend in self.routes.items():
|
|
223
|
-
raw = await backend.agrep_raw(pattern, "/", glob)
|
|
224
|
-
if isinstance(raw, str):
|
|
296
|
+
# If path is None or "/", search default and all routed backends and merge
|
|
297
|
+
# Otherwise, search only the default backend
|
|
298
|
+
if path is None or path == "/":
|
|
299
|
+
all_matches: list[GrepMatch] = []
|
|
300
|
+
raw_default = await self.default.agrep_raw(pattern, path, glob) # type: ignore[attr-defined]
|
|
301
|
+
if isinstance(raw_default, str):
|
|
225
302
|
# This happens if error occurs
|
|
226
|
-
return
|
|
227
|
-
all_matches.extend(
|
|
303
|
+
return raw_default
|
|
304
|
+
all_matches.extend(raw_default)
|
|
305
|
+
|
|
306
|
+
for route_prefix, backend in self.routes.items():
|
|
307
|
+
raw = await backend.agrep_raw(pattern, "/", glob)
|
|
308
|
+
if isinstance(raw, str):
|
|
309
|
+
# This happens if error occurs
|
|
310
|
+
return raw
|
|
311
|
+
all_matches.extend({**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw)
|
|
228
312
|
|
|
229
|
-
|
|
313
|
+
return all_matches
|
|
314
|
+
# Path specified but doesn't match a route - search only default
|
|
315
|
+
return await self.default.agrep_raw(pattern, path, glob) # type: ignore[attr-defined]
|
|
230
316
|
|
|
231
317
|
def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
|
|
232
318
|
results: list[FileInfo] = []
|
|
@@ -379,19 +465,23 @@ class CompositeBackend:
|
|
|
379
465
|
self,
|
|
380
466
|
command: str,
|
|
381
467
|
) -> ExecuteResponse:
|
|
382
|
-
"""Execute
|
|
383
|
-
|
|
384
|
-
Execution is not path-specific, so it always delegates to the default backend.
|
|
385
|
-
The default backend must implement SandboxBackendProtocol for this to work.
|
|
468
|
+
"""Execute shell command via default backend.
|
|
386
469
|
|
|
387
470
|
Args:
|
|
388
|
-
command:
|
|
471
|
+
command: Shell command to execute.
|
|
389
472
|
|
|
390
473
|
Returns:
|
|
391
|
-
ExecuteResponse with
|
|
474
|
+
ExecuteResponse with output, exit code, and truncation flag.
|
|
392
475
|
|
|
393
476
|
Raises:
|
|
394
|
-
NotImplementedError: If default backend doesn't
|
|
477
|
+
NotImplementedError: If default backend doesn't implement SandboxBackendProtocol.
|
|
478
|
+
|
|
479
|
+
Examples:
|
|
480
|
+
```python
|
|
481
|
+
composite = CompositeBackend(default=FilesystemBackend(root_dir="/tmp"), routes={"/memories/": StoreBackend(runtime)})
|
|
482
|
+
|
|
483
|
+
result = composite.execute("ls -la")
|
|
484
|
+
```
|
|
395
485
|
"""
|
|
396
486
|
if isinstance(self.default, SandboxBackendProtocol):
|
|
397
487
|
return self.default.execute(command)
|
deepagents/backends/state.py
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from deepagents.backends.protocol import
|
|
5
|
+
from deepagents.backends.protocol import (
|
|
6
|
+
BackendProtocol,
|
|
7
|
+
EditResult,
|
|
8
|
+
FileDownloadResponse,
|
|
9
|
+
FileInfo,
|
|
10
|
+
FileUploadResponse,
|
|
11
|
+
GrepMatch,
|
|
12
|
+
WriteResult,
|
|
13
|
+
)
|
|
6
14
|
from deepagents.backends.utils import (
|
|
7
15
|
_glob_search_files,
|
|
8
16
|
create_file_data,
|
|
@@ -185,3 +193,44 @@ class StateBackend(BackendProtocol):
|
|
|
185
193
|
}
|
|
186
194
|
)
|
|
187
195
|
return infos
|
|
196
|
+
|
|
197
|
+
def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
|
|
198
|
+
"""Upload multiple files to state.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
files: List of (path, content) tuples to upload
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
List of FileUploadResponse objects, one per input file
|
|
205
|
+
"""
|
|
206
|
+
raise NotImplementedError(
|
|
207
|
+
"StateBackend does not support upload_files yet. You can upload files "
|
|
208
|
+
"directly by passing them in invoke if you're storing files in the memory."
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
|
|
212
|
+
"""Download multiple files from state.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
paths: List of file paths to download
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
List of FileDownloadResponse objects, one per input path
|
|
219
|
+
"""
|
|
220
|
+
state_files = self.runtime.state.get("files", {})
|
|
221
|
+
responses: list[FileDownloadResponse] = []
|
|
222
|
+
|
|
223
|
+
for path in paths:
|
|
224
|
+
file_data = state_files.get(path)
|
|
225
|
+
|
|
226
|
+
if file_data is None:
|
|
227
|
+
responses.append(FileDownloadResponse(path=path, content=None, error="file_not_found"))
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
# Convert file data to bytes
|
|
231
|
+
content_str = file_data_to_string(file_data)
|
|
232
|
+
content_bytes = content_str.encode("utf-8")
|
|
233
|
+
|
|
234
|
+
responses.append(FileDownloadResponse(path=path, content=content_bytes, error=None))
|
|
235
|
+
|
|
236
|
+
return responses
|
deepagents/graph.py
CHANGED
|
@@ -8,6 +8,7 @@ from langchain.agents.middleware import HumanInTheLoopMiddleware, InterruptOnCon
|
|
|
8
8
|
from langchain.agents.middleware.summarization import SummarizationMiddleware
|
|
9
9
|
from langchain.agents.middleware.types import AgentMiddleware
|
|
10
10
|
from langchain.agents.structured_output import ResponseFormat
|
|
11
|
+
from langchain.chat_models import init_chat_model
|
|
11
12
|
from langchain_anthropic import ChatAnthropic
|
|
12
13
|
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
|
|
13
14
|
from langchain_core.language_models import BaseChatModel
|
|
@@ -17,9 +18,12 @@ from langgraph.graph.state import CompiledStateGraph
|
|
|
17
18
|
from langgraph.store.base import BaseStore
|
|
18
19
|
from langgraph.types import Checkpointer
|
|
19
20
|
|
|
21
|
+
from deepagents.backends import StateBackend
|
|
20
22
|
from deepagents.backends.protocol import BackendFactory, BackendProtocol
|
|
21
23
|
from deepagents.middleware.filesystem import FilesystemMiddleware
|
|
24
|
+
from deepagents.middleware.memory import MemoryMiddleware
|
|
22
25
|
from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
|
|
26
|
+
from deepagents.middleware.skills import SkillsMiddleware
|
|
23
27
|
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
|
|
24
28
|
|
|
25
29
|
BASE_AGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools."
|
|
@@ -44,6 +48,8 @@ def create_deep_agent(
|
|
|
44
48
|
system_prompt: str | None = None,
|
|
45
49
|
middleware: Sequence[AgentMiddleware] = (),
|
|
46
50
|
subagents: list[SubAgent | CompiledSubAgent] | None = None,
|
|
51
|
+
skills: list[str] | None = None,
|
|
52
|
+
memory: list[str] | None = None,
|
|
47
53
|
response_format: ResponseFormat | None = None,
|
|
48
54
|
context_schema: type[Any] | None = None,
|
|
49
55
|
checkpointer: Checkpointer | None = None,
|
|
@@ -79,6 +85,16 @@ def create_deep_agent(
|
|
|
79
85
|
- (optional) `model` (either a LanguageModelLike instance or dict
|
|
80
86
|
settings)
|
|
81
87
|
- (optional) `middleware` (list of AgentMiddleware)
|
|
88
|
+
skills: Optional list of skill source paths (e.g., ["/skills/user/", "/skills/project/"]).
|
|
89
|
+
Paths must be specified using POSIX conventions (forward slashes) and are relative
|
|
90
|
+
to the backend's root. When using StateBackend (default), provide skill files via
|
|
91
|
+
`invoke(files={...})`. With FilesystemBackend, skills are loaded from disk relative
|
|
92
|
+
to the backend's root_dir. Later sources override earlier ones for skills with the
|
|
93
|
+
same name (last one wins).
|
|
94
|
+
memory: Optional list of memory file paths (AGENTS.md files) to load
|
|
95
|
+
(e.g., ["/memory/AGENTS.md"]). Display names
|
|
96
|
+
are automatically derived from paths. Memory is loaded at agent startup and
|
|
97
|
+
added into the system prompt.
|
|
82
98
|
response_format: A structured output response format to use for the agent.
|
|
83
99
|
context_schema: The schema of the deep agent.
|
|
84
100
|
checkpointer: Optional checkpointer for persisting agent state between runs.
|
|
@@ -97,6 +113,8 @@ def create_deep_agent(
|
|
|
97
113
|
"""
|
|
98
114
|
if model is None:
|
|
99
115
|
model = get_default_model()
|
|
116
|
+
elif isinstance(model, str):
|
|
117
|
+
model = init_chat_model(model)
|
|
100
118
|
|
|
101
119
|
if (
|
|
102
120
|
model.profile is not None
|
|
@@ -110,37 +128,58 @@ def create_deep_agent(
|
|
|
110
128
|
trigger = ("tokens", 170000)
|
|
111
129
|
keep = ("messages", 6)
|
|
112
130
|
|
|
113
|
-
|
|
131
|
+
# Build middleware stack for subagents (includes skills if provided)
|
|
132
|
+
subagent_middleware: list[AgentMiddleware] = [
|
|
114
133
|
TodoListMiddleware(),
|
|
115
|
-
FilesystemMiddleware(backend=backend),
|
|
116
|
-
SubAgentMiddleware(
|
|
117
|
-
default_model=model,
|
|
118
|
-
default_tools=tools,
|
|
119
|
-
subagents=subagents if subagents is not None else [],
|
|
120
|
-
default_middleware=[
|
|
121
|
-
TodoListMiddleware(),
|
|
122
|
-
FilesystemMiddleware(backend=backend),
|
|
123
|
-
SummarizationMiddleware(
|
|
124
|
-
model=model,
|
|
125
|
-
trigger=trigger,
|
|
126
|
-
keep=keep,
|
|
127
|
-
trim_tokens_to_summarize=None,
|
|
128
|
-
),
|
|
129
|
-
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
130
|
-
PatchToolCallsMiddleware(),
|
|
131
|
-
],
|
|
132
|
-
default_interrupt_on=interrupt_on,
|
|
133
|
-
general_purpose_agent=True,
|
|
134
|
-
),
|
|
135
|
-
SummarizationMiddleware(
|
|
136
|
-
model=model,
|
|
137
|
-
trigger=trigger,
|
|
138
|
-
keep=keep,
|
|
139
|
-
trim_tokens_to_summarize=None,
|
|
140
|
-
),
|
|
141
|
-
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
142
|
-
PatchToolCallsMiddleware(),
|
|
143
134
|
]
|
|
135
|
+
|
|
136
|
+
backend = backend if backend is not None else (lambda rt: StateBackend(rt))
|
|
137
|
+
|
|
138
|
+
if skills is not None:
|
|
139
|
+
subagent_middleware.append(SkillsMiddleware(backend=backend, sources=skills))
|
|
140
|
+
subagent_middleware.extend(
|
|
141
|
+
[
|
|
142
|
+
FilesystemMiddleware(backend=backend),
|
|
143
|
+
SummarizationMiddleware(
|
|
144
|
+
model=model,
|
|
145
|
+
trigger=trigger,
|
|
146
|
+
keep=keep,
|
|
147
|
+
trim_tokens_to_summarize=None,
|
|
148
|
+
),
|
|
149
|
+
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
150
|
+
PatchToolCallsMiddleware(),
|
|
151
|
+
]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Build main agent middleware stack
|
|
155
|
+
deepagent_middleware: list[AgentMiddleware] = [
|
|
156
|
+
TodoListMiddleware(),
|
|
157
|
+
]
|
|
158
|
+
if memory is not None:
|
|
159
|
+
deepagent_middleware.append(MemoryMiddleware(backend=backend, sources=memory))
|
|
160
|
+
if skills is not None:
|
|
161
|
+
deepagent_middleware.append(SkillsMiddleware(backend=backend, sources=skills))
|
|
162
|
+
deepagent_middleware.extend(
|
|
163
|
+
[
|
|
164
|
+
FilesystemMiddleware(backend=backend),
|
|
165
|
+
SubAgentMiddleware(
|
|
166
|
+
default_model=model,
|
|
167
|
+
default_tools=tools,
|
|
168
|
+
subagents=subagents if subagents is not None else [],
|
|
169
|
+
default_middleware=subagent_middleware,
|
|
170
|
+
default_interrupt_on=interrupt_on,
|
|
171
|
+
general_purpose_agent=True,
|
|
172
|
+
),
|
|
173
|
+
SummarizationMiddleware(
|
|
174
|
+
model=model,
|
|
175
|
+
trigger=trigger,
|
|
176
|
+
keep=keep,
|
|
177
|
+
trim_tokens_to_summarize=None,
|
|
178
|
+
),
|
|
179
|
+
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
180
|
+
PatchToolCallsMiddleware(),
|
|
181
|
+
]
|
|
182
|
+
)
|
|
144
183
|
if middleware:
|
|
145
184
|
deepagent_middleware.extend(middleware)
|
|
146
185
|
if interrupt_on is not None:
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"""Middleware for the DeepAgent."""
|
|
2
2
|
|
|
3
3
|
from deepagents.middleware.filesystem import FilesystemMiddleware
|
|
4
|
+
from deepagents.middleware.memory import MemoryMiddleware
|
|
5
|
+
from deepagents.middleware.skills import SkillsMiddleware
|
|
4
6
|
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
|
|
5
7
|
|
|
6
8
|
__all__ = [
|
|
7
9
|
"CompiledSubAgent",
|
|
8
10
|
"FilesystemMiddleware",
|
|
11
|
+
"MemoryMiddleware",
|
|
12
|
+
"SkillsMiddleware",
|
|
9
13
|
"SubAgent",
|
|
10
14
|
"SubAgentMiddleware",
|
|
11
15
|
]
|