strands-code-agent 0.1.0__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.
- strands_code_agent/__init__.py +4 -0
- strands_code_agent/code_agent.py +142 -0
- strands_code_agent/document_code.py +63 -0
- strands_code_agent/imports.py +36 -0
- strands_code_agent/python_environments/__init__.py +0 -0
- strands_code_agent/python_environments/base.py +44 -0
- strands_code_agent/python_environments/local_exec.py +28 -0
- strands_code_agent/python_environments/local_sandboxed.py +63 -0
- strands_code_agent/toolkits.py +42 -0
- strands_code_agent/utils.py +7 -0
- strands_code_agent-0.1.0.dist-info/METADATA +163 -0
- strands_code_agent-0.1.0.dist-info/RECORD +14 -0
- strands_code_agent-0.1.0.dist-info/WHEEL +4 -0
- strands_code_agent-0.1.0.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
|
|
3
|
+
from strands import Agent
|
|
4
|
+
from jinja2 import Template
|
|
5
|
+
|
|
6
|
+
from strands_code_agent.document_code import get_documentation
|
|
7
|
+
from strands_code_agent.python_environments.local_sandboxed import SandboxedPythonInterpreter
|
|
8
|
+
from strands_code_agent.imports import get_import_string, extract_imports
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
CODE_AGENT_INSTRUCTIONS = """
|
|
12
|
+
You are a code agent. You solve tasks by writing and executing Python code using the python_repl tool.
|
|
13
|
+
|
|
14
|
+
The Python interpreter state resets completely with each new user message, but it persists across multiple tool invocations within a single response.
|
|
15
|
+
You can perform multi-step computation within a single turn, but do not assume that results from a previous turn are still in memory.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
CODE_PREAMBLE_TEMPLATE = Template("""
|
|
19
|
+
The python environment of the python_repl tool is initialised with the following code (you do not need to rewrite this code):
|
|
20
|
+
```python
|
|
21
|
+
{{CODE_PREAMBLE}}
|
|
22
|
+
```
|
|
23
|
+
""")
|
|
24
|
+
|
|
25
|
+
TEMP_DIR_TEMPLATE = Template("""
|
|
26
|
+
If you need to generate a file use this temporary directory: {{TEMP_DIR}}
|
|
27
|
+
The user has no access to this temporary directory.
|
|
28
|
+
""")
|
|
29
|
+
|
|
30
|
+
DOMAIN_SPECIFIC_DOC_TEMPLATE = Template("""
|
|
31
|
+
You can use the following Domain Specific Code:
|
|
32
|
+
{{SYMBOLS_DOCUMENTATION}}
|
|
33
|
+
""")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CodeAgent(Agent):
|
|
37
|
+
"""A coding agent that extends Strands Agent with a sandboxed Python REPL and domain-specific symbol documentation.
|
|
38
|
+
|
|
39
|
+
CodeAgent wraps a :class:`PythonInterpreter` as a built-in ``python_repl`` tool and
|
|
40
|
+
assembles a system prompt from the provided toolkits. Each :class:`Toolkit` can contribute:
|
|
41
|
+
|
|
42
|
+
- **libraries** – module names authorized for import in the sandboxed interpreter.
|
|
43
|
+
- **initialization_code** – Python code executed at interpreter startup (e.g. imports, config).
|
|
44
|
+
Any modules imported in this code are automatically authorized.
|
|
45
|
+
- **usage_instructions** – free-text guidance appended to the system prompt.
|
|
46
|
+
- **domain_specific_code** – callable symbols whose source and docstrings are documented in
|
|
47
|
+
the system prompt and made available in the interpreter.
|
|
48
|
+
|
|
49
|
+
The interpreter state persists across tool invocations within a single agent turn but
|
|
50
|
+
resets completely between user messages.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
system_prompt: Optional base system prompt prepended before the coding instructions.
|
|
54
|
+
|
|
55
|
+
tools: Additional tools to include alongside the built-in Python REPL.
|
|
56
|
+
|
|
57
|
+
toolkits: :class:`Toolkit` instances that supply libraries, initialization code,
|
|
58
|
+
usage instructions, and domain-specific symbols to the REPL environment.
|
|
59
|
+
|
|
60
|
+
tmp_dir: If ``True`` (default), creates a temporary directory under ``/tmp`` and
|
|
61
|
+
documents its path in the system prompt so the agent can write files there.
|
|
62
|
+
|
|
63
|
+
timeout_seconds: Maximum execution time in seconds for each ``python_repl``
|
|
64
|
+
invocation. Defaults to ``60``.
|
|
65
|
+
|
|
66
|
+
python_interpreter_class: The :class:`PythonInterpreter` subclass to use for
|
|
67
|
+
code execution. Defaults to :class:`ExecPythonInterpreter` (lightweight,
|
|
68
|
+
unrestricted ``exec()``-based). Use :class:`SandboxedPythonInterpreter`
|
|
69
|
+
for import restrictions and sandboxed execution.
|
|
70
|
+
|
|
71
|
+
**kwargs: Additional arguments forwarded to the Strands :class:`Agent` base class
|
|
72
|
+
(e.g. ``model``, ``callback_handler``).
|
|
73
|
+
"""
|
|
74
|
+
def __init__(self,
|
|
75
|
+
system_prompt:str|None=None,
|
|
76
|
+
tools:list|None=None,
|
|
77
|
+
toolkits:list|None=None,
|
|
78
|
+
tmp_dir=True,
|
|
79
|
+
timeout_seconds=60,
|
|
80
|
+
python_interpreter_class=SandboxedPythonInterpreter,
|
|
81
|
+
**kwargs):
|
|
82
|
+
authorized_imports = set()
|
|
83
|
+
initialization_code = []
|
|
84
|
+
usage_instructions = []
|
|
85
|
+
domain_specific_code = []
|
|
86
|
+
if toolkits is not None:
|
|
87
|
+
for toolkit in toolkits:
|
|
88
|
+
if toolkit.libraries is not None:
|
|
89
|
+
authorized_imports.update(toolkit.libraries)
|
|
90
|
+
if toolkit.initialization_code is not None:
|
|
91
|
+
initialization_code.append(toolkit.initialization_code.strip())
|
|
92
|
+
if toolkit.usage_instructions is not None:
|
|
93
|
+
usage_instructions.append(toolkit.usage_instructions.strip())
|
|
94
|
+
if toolkit.domain_specific_code is not None:
|
|
95
|
+
domain_specific_code.extend(toolkit.domain_specific_code)
|
|
96
|
+
|
|
97
|
+
additional_functions = {}
|
|
98
|
+
domain_specific_doc = ""
|
|
99
|
+
if domain_specific_code:
|
|
100
|
+
authorized_imports.update(sym.__module__ for sym in domain_specific_code if sym.__module__ != "__main__")
|
|
101
|
+
initialization_code.append(get_import_string(domain_specific_code))
|
|
102
|
+
sym_doc = "\n".join([get_documentation(sym) for sym in domain_specific_code])
|
|
103
|
+
domain_specific_doc = DOMAIN_SPECIFIC_DOC_TEMPLATE.render(SYMBOLS_DOCUMENTATION=sym_doc)
|
|
104
|
+
additional_functions = {sym.__qualname__.split(".")[0]: sym for sym in domain_specific_code}
|
|
105
|
+
|
|
106
|
+
code_preamble = "\n".join(initialization_code)
|
|
107
|
+
# Auto-authorize any modules imported in initialization code so users
|
|
108
|
+
# don't have to duplicate them in both `libraries` and `initialization_code`.
|
|
109
|
+
authorized_imports.update(extract_imports(code_preamble))
|
|
110
|
+
code_preamble_doc = CODE_PREAMBLE_TEMPLATE.render(CODE_PREAMBLE=code_preamble) if code_preamble else ""
|
|
111
|
+
|
|
112
|
+
tmp_dir_doc = ""
|
|
113
|
+
if tmp_dir:
|
|
114
|
+
self.tmp_dir = tempfile.mkdtemp(dir='/tmp')
|
|
115
|
+
tmp_dir_doc = TEMP_DIR_TEMPLATE.render(TEMP_DIR=self.tmp_dir)
|
|
116
|
+
|
|
117
|
+
system_prompt = '\n'.join([
|
|
118
|
+
system_prompt if system_prompt is not None else "",
|
|
119
|
+
CODE_AGENT_INSTRUCTIONS,
|
|
120
|
+
code_preamble_doc,
|
|
121
|
+
"\n".join(usage_instructions),
|
|
122
|
+
tmp_dir_doc,
|
|
123
|
+
domain_specific_doc
|
|
124
|
+
])
|
|
125
|
+
|
|
126
|
+
self.python_repl = python_interpreter_class(
|
|
127
|
+
code_preamble,
|
|
128
|
+
authorized_imports=authorized_imports,
|
|
129
|
+
additional_functions=additional_functions,
|
|
130
|
+
timeout_seconds=timeout_seconds,
|
|
131
|
+
)
|
|
132
|
+
python_repl_tool = self.python_repl.get_tool()
|
|
133
|
+
if tools is not None:
|
|
134
|
+
tools.append(python_repl_tool)
|
|
135
|
+
else:
|
|
136
|
+
tools = [python_repl_tool]
|
|
137
|
+
|
|
138
|
+
kwargs.update({
|
|
139
|
+
"system_prompt": system_prompt,
|
|
140
|
+
"tools": tools
|
|
141
|
+
})
|
|
142
|
+
super().__init__(**kwargs)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Callable, Type, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def format_function(func, indent: str = "") -> str:
|
|
6
|
+
try:
|
|
7
|
+
sig = inspect.signature(func)
|
|
8
|
+
result = f"{indent}def {func.__name__}{sig}:\n"
|
|
9
|
+
except (ValueError, TypeError):
|
|
10
|
+
result = f"{indent}def {func.__name__}(...):\n"
|
|
11
|
+
|
|
12
|
+
doc = inspect.getdoc(func)
|
|
13
|
+
if doc:
|
|
14
|
+
doc_lines = doc.split('\n')
|
|
15
|
+
result += f'{indent} """\n'
|
|
16
|
+
for line in doc_lines:
|
|
17
|
+
result += f"{indent} {line}\n"
|
|
18
|
+
result += f'{indent} """\n'
|
|
19
|
+
|
|
20
|
+
result += f"{indent} ...\n"
|
|
21
|
+
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_documentation(obj: Union[Callable, Type]) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Extract documentation from a Python function or class.
|
|
27
|
+
Returns formatted text suitable for a coding agent.
|
|
28
|
+
"""
|
|
29
|
+
if inspect.isclass(obj):
|
|
30
|
+
# Class header with constructor signature
|
|
31
|
+
try:
|
|
32
|
+
sig = inspect.signature(obj)
|
|
33
|
+
output = f"class {obj.__name__}{sig}:\n"
|
|
34
|
+
except (ValueError, TypeError):
|
|
35
|
+
output = f"class {obj.__name__}:\n"
|
|
36
|
+
|
|
37
|
+
# Class docstring
|
|
38
|
+
class_doc = inspect.getdoc(obj)
|
|
39
|
+
if class_doc:
|
|
40
|
+
output += ' """\n'
|
|
41
|
+
for line in class_doc.split('\n'):
|
|
42
|
+
output += f" {line}\n"
|
|
43
|
+
output += ' """\n\n'
|
|
44
|
+
|
|
45
|
+
# Methods (public + key dunder methods)
|
|
46
|
+
important_dunders = {
|
|
47
|
+
'__init__', '__call__', '__enter__', '__exit__',
|
|
48
|
+
'__iter__', '__next__', '__getitem__', '__setitem__',
|
|
49
|
+
'__len__', '__contains__', '__repr__', '__str__'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for name, method in inspect.getmembers(obj, predicate=inspect.isfunction):
|
|
53
|
+
if name.startswith('_') and name not in important_dunders:
|
|
54
|
+
continue
|
|
55
|
+
output += format_function(method, indent=" ") + "\n"
|
|
56
|
+
|
|
57
|
+
return output.rstrip() + "\n"
|
|
58
|
+
|
|
59
|
+
elif callable(obj):
|
|
60
|
+
return format_function(obj)
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
raise TypeError(f"Expected a function or class, got {type(obj)}")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from typing import Callable, Type, Union
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def extract_imports(code: str) -> set[str]:
|
|
7
|
+
"""Extract top-level module names from import statements in a code snippet."""
|
|
8
|
+
try:
|
|
9
|
+
tree = ast.parse(code)
|
|
10
|
+
except SyntaxError:
|
|
11
|
+
return set()
|
|
12
|
+
modules = set()
|
|
13
|
+
for node in ast.walk(tree):
|
|
14
|
+
if isinstance(node, ast.Import):
|
|
15
|
+
for alias in node.names:
|
|
16
|
+
modules.add(alias.name)
|
|
17
|
+
elif isinstance(node, ast.ImportFrom):
|
|
18
|
+
if node.module:
|
|
19
|
+
modules.add(node.module)
|
|
20
|
+
for alias in node.names:
|
|
21
|
+
modules.add(f"{node.module}.{alias.name}")
|
|
22
|
+
return modules
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_import_string(symbols: list[Union[Callable, Type]]) -> str:
|
|
26
|
+
"""Generate minimal import statements for a list of symbols."""
|
|
27
|
+
by_module: dict[str, list[str]] = defaultdict(list)
|
|
28
|
+
for sym in symbols:
|
|
29
|
+
if sym.__module__ == "__main__":
|
|
30
|
+
continue
|
|
31
|
+
by_module[sym.__module__].append(sym.__qualname__.split(".")[0])
|
|
32
|
+
|
|
33
|
+
lines = []
|
|
34
|
+
for module, names in sorted(by_module.items()):
|
|
35
|
+
lines.append(f"from {module} import {', '.join(sorted(set(names)))}")
|
|
36
|
+
return "\n".join(lines)
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from strands import tool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
STDOUT_LABEL = "STDOUT:\n"
|
|
7
|
+
STDERR_LABEL = "STDERR:"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PythonInterpreter(ABC):
|
|
11
|
+
def __init__(self, state_initialization=None, stdout_label=STDOUT_LABEL, stderr_label=STDERR_LABEL, authorized_imports=None, additional_functions=None, timeout_seconds=60):
|
|
12
|
+
self.state_initialization = state_initialization
|
|
13
|
+
self.stdout_label = stdout_label
|
|
14
|
+
self.stderr_label = stderr_label
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def clear_state(self):
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def execute_code(self, code) -> tuple[str, str]:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def get_tool(self):
|
|
25
|
+
@tool
|
|
26
|
+
def python_repl(code: str) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Executes Python code in a REPL environment with state persistence.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
code: The Python code to execute
|
|
32
|
+
"""
|
|
33
|
+
stdout_output, stderr_output = self.execute_code(code)
|
|
34
|
+
|
|
35
|
+
observation = []
|
|
36
|
+
if stdout_output:
|
|
37
|
+
observation.append(f"{self.stdout_label}{stdout_output}")
|
|
38
|
+
if stderr_output:
|
|
39
|
+
observation.append(f"{self.stderr_label}{stderr_output}")
|
|
40
|
+
if not observation:
|
|
41
|
+
observation.append("Code executed successfully.")
|
|
42
|
+
return '\n'.join(observation)
|
|
43
|
+
|
|
44
|
+
return python_repl
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import io
|
|
2
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
3
|
+
|
|
4
|
+
from strands_code_agent.python_environments.base import PythonInterpreter, STDOUT_LABEL, STDERR_LABEL
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExecPythonInterpreter(PythonInterpreter):
|
|
8
|
+
def __init__(self, state_initialization=None, stdout_label=STDOUT_LABEL, stderr_label=STDERR_LABEL, **kwargs):
|
|
9
|
+
super().__init__(state_initialization, stdout_label, stderr_label)
|
|
10
|
+
|
|
11
|
+
self.state = {}
|
|
12
|
+
if self.state_initialization:
|
|
13
|
+
self.execute_code(self.state_initialization)
|
|
14
|
+
|
|
15
|
+
def clear_state(self):
|
|
16
|
+
self.state.clear()
|
|
17
|
+
if self.state_initialization:
|
|
18
|
+
self.execute_code(self.state_initialization)
|
|
19
|
+
|
|
20
|
+
def execute_code(self, code):
|
|
21
|
+
stdout_buffer = io.StringIO()
|
|
22
|
+
stderr_buffer = io.StringIO()
|
|
23
|
+
try:
|
|
24
|
+
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
|
|
25
|
+
exec(code, self.state)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
stderr_buffer.write(str(e))
|
|
28
|
+
return stdout_buffer.getvalue().strip(), stderr_buffer.getvalue().strip()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from smolagents.local_python_executor import LocalPythonExecutor
|
|
4
|
+
|
|
5
|
+
from strands_code_agent.python_environments.base import PythonInterpreter, STDOUT_LABEL, STDERR_LABEL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Python builtins that smolagents' executor doesn't allow-list by default
|
|
9
|
+
EXTRA_BUILTINS = {"repr": repr, "ascii": ascii, "ord": ord, "chr": chr, "hex": hex, "oct": oct, "bin": bin}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SandboxedPythonInterpreter(PythonInterpreter):
|
|
13
|
+
def __init__(self, state_initialization=None, stdout_label=STDOUT_LABEL, stderr_label=STDERR_LABEL, authorized_imports=None, additional_functions=None, timeout_seconds=60):
|
|
14
|
+
super().__init__(state_initialization, stdout_label, stderr_label)
|
|
15
|
+
|
|
16
|
+
self.authorized_imports = authorized_imports or []
|
|
17
|
+
self.additional_functions = {**EXTRA_BUILTINS, **(additional_functions or {})}
|
|
18
|
+
self.timeout_seconds = timeout_seconds
|
|
19
|
+
|
|
20
|
+
self._init_executor()
|
|
21
|
+
|
|
22
|
+
def _init_executor(self):
|
|
23
|
+
# smolagents' LocalPythonExecutor introspects all attributes of authorized modules
|
|
24
|
+
# via getattr(), which triggers DeprecationWarnings on access, not use.
|
|
25
|
+
with warnings.catch_warnings():
|
|
26
|
+
warnings.simplefilter("ignore")
|
|
27
|
+
self.executor = LocalPythonExecutor(
|
|
28
|
+
additional_authorized_imports=self.authorized_imports,
|
|
29
|
+
additional_functions=self.additional_functions,
|
|
30
|
+
timeout_seconds=self.timeout_seconds,
|
|
31
|
+
)
|
|
32
|
+
self.executor.send_tools({})
|
|
33
|
+
if self.state_initialization:
|
|
34
|
+
self.executor(self.state_initialization)
|
|
35
|
+
|
|
36
|
+
def clear_state(self):
|
|
37
|
+
self._init_executor()
|
|
38
|
+
|
|
39
|
+
def execute_code(self, code):
|
|
40
|
+
stdout, stderr = "", ""
|
|
41
|
+
try:
|
|
42
|
+
result = self.executor(code)
|
|
43
|
+
stdout = result.logs.strip() if result.logs else ""
|
|
44
|
+
except Exception as e:
|
|
45
|
+
# Salvage any print output captured before the error
|
|
46
|
+
if hasattr(self.executor, "state") and "_print_outputs" in self.executor.state:
|
|
47
|
+
stdout = str(self.executor.state["_print_outputs"]).strip()
|
|
48
|
+
stderr = str(e)
|
|
49
|
+
return stdout, stderr
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
buffer = []
|
|
53
|
+
if self.authorized_imports:
|
|
54
|
+
buffer.append(f"# Authorized Imports: {', '.join(self.authorized_imports)}")
|
|
55
|
+
if self.additional_functions:
|
|
56
|
+
buffer.append(f"# Additional Functions: {', '.join(self.additional_functions.keys())}")
|
|
57
|
+
if self.state_initialization:
|
|
58
|
+
buffer.append(f"""# Init Code:
|
|
59
|
+
```python
|
|
60
|
+
{self.state_initialization}
|
|
61
|
+
```
|
|
62
|
+
""")
|
|
63
|
+
return '\n'.join(buffer) if buffer else "Standard PythonInterpreter"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Toolkit:
|
|
6
|
+
"""A toolkit that bundles libraries, initialization code, usage instructions, and domain-specific
|
|
7
|
+
symbols for use by a CodeAgent's Python REPL environment.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
libraries: Library names to authorize as imports in the Python executor.
|
|
11
|
+
initialization_code: Python code snippet to run when initializing the environment.
|
|
12
|
+
usage_instructions: Extra instructions included in the system prompt about how to use the provided libraries.
|
|
13
|
+
domain_specific_code: Callable symbols to be fully imported and documented in the agent's context.
|
|
14
|
+
"""
|
|
15
|
+
libraries: list[str] | None = None
|
|
16
|
+
initialization_code: str | None = None
|
|
17
|
+
usage_instructions: str | None = None
|
|
18
|
+
domain_specific_code: list | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
VISUALIZATION_TOOLKIT = Toolkit(
|
|
22
|
+
libraries = ['matplotlib.*', 'seaborn.*'],
|
|
23
|
+
initialization_code = """
|
|
24
|
+
# Visualization Libraries
|
|
25
|
+
import matplotlib
|
|
26
|
+
matplotlib.use('Agg') # Use non-interactive backend
|
|
27
|
+
import matplotlib.pyplot as plt
|
|
28
|
+
import seaborn as sns
|
|
29
|
+
""",
|
|
30
|
+
usage_instructions="""
|
|
31
|
+
Do not try to show any matplotlib image: the python_repl tool executes the code in a sub-process without a GUI.
|
|
32
|
+
""")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
DATA_ANALYSIS_TOOLKIT = Toolkit(
|
|
36
|
+
libraries = ['numpy.*', 'pandas.*', 'scipy.*', 'datetime'],
|
|
37
|
+
initialization_code = """
|
|
38
|
+
from datetime import date, datetime, timedelta
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
import pandas as pd
|
|
42
|
+
""")
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strands-code-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A coding agent built on Strands Agents SDK that uses code generation as the primary action interface
|
|
5
|
+
Project-URL: Homepage, https://github.com/aws-samples/sample-strands-code-agent
|
|
6
|
+
Project-URL: Repository, https://github.com/aws-samples/sample-strands-code-agent
|
|
7
|
+
Project-URL: Issues, https://github.com/aws-samples/sample-strands-code-agent/issues
|
|
8
|
+
Author: Emilio Monti
|
|
9
|
+
License-Expression: MIT-0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,code-generation,llm,repl,strands
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: jinja2>=3.0
|
|
23
|
+
Requires-Dist: smolagents>=1.0.0
|
|
24
|
+
Requires-Dist: strands-agents>=0.1.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# strands-code-agent
|
|
28
|
+
|
|
29
|
+
A coding agent built on [Strands Agents SDK](https://github.com/strands-agents/sdk-python) that replaces the tool-calling paradigm with code generation as the agent's primary action interface. Rather than invoking structured tools by name and passing results through the conversation context, the agent writes Python code in a persistent REPL where domain capabilities (database queries, APIs, etc.) are exposed as importable library functions. This keeps intermediate data as native Python objects in memory and lets the agent compose multi-step logic in a single code block instead of orchestrating sequential tool calls. In empirical evaluations on the Data Agent Benchmark, this code-generation paradigm achieves higher accuracy (+7%) while consuming 78% fewer input tokens, completing tasks 56% faster, and requiring 35% fewer reasoning cycles compared to an equivalent tool-calling agent. The library makes it easy to configure the Python environment with the libraries and domain-specific code your agent needs.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install strands-code-agent
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from strands_code_agent import CodeAgent
|
|
41
|
+
|
|
42
|
+
agent = CodeAgent(system_prompt="You are a helpful data analyst.")
|
|
43
|
+
|
|
44
|
+
response = agent("What is 2 ** 10?")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The agent receives a `python_repl` tool automatically and solves tasks by writing and executing Python code.
|
|
48
|
+
|
|
49
|
+
## CodeAgent
|
|
50
|
+
|
|
51
|
+
`CodeAgent` extends the Strands `Agent` with a built-in Python REPL and automatic system-prompt enrichment.
|
|
52
|
+
|
|
53
|
+
| Parameter | Type | Description |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `system_prompt` | `str \| None` | Base system prompt, extended with coding instructions. |
|
|
56
|
+
| `tools` | `list \| None` | Additional tools alongside the built-in Python REPL. |
|
|
57
|
+
| `toolkits` | `list[Toolkit] \| None` | Toolkits that configure the REPL environment (see below). |
|
|
58
|
+
| `tmp_dir` | `bool` | If `True` (default), creates a temp directory and documents its path in the prompt. |
|
|
59
|
+
| `python_interpreter_class` | `type[PythonInterpreter]` | The interpreter backend. Defaults to `SandboxedPythonInterpreter` (import restrictions via allowlist). Use `ExecPythonInterpreter` for lightweight unrestricted `exec()`-based execution. |
|
|
60
|
+
| `**kwargs` | | Forwarded to the Strands `Agent` base class (e.g. `model`, `callback_handler`). |
|
|
61
|
+
|
|
62
|
+
## Toolkit
|
|
63
|
+
|
|
64
|
+
A `Toolkit` bundles everything the REPL needs for a specific domain. Each field influences the `CodeAgent` in a specific way:
|
|
65
|
+
|
|
66
|
+
| Parameter | Type | Effect on `PythonInterpreter` | Effect on System Prompt |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `libraries` | `list[str] \| None` | Added to `authorized_imports` — the REPL will only allow imports from this allowlist. | — |
|
|
69
|
+
| `initialization_code` | `str \| None` | Prepended to `state_initialization` — runs before every Agent snippet. | Documented so the agent knows which symbols are pre-loaded. |
|
|
70
|
+
| `usage_instructions` | `str \| None` | — | Appended as-is, giving the agent guidance on how to use the libraries. |
|
|
71
|
+
| `domain_specific_code` | `list \| None` | Auto-imported in `state_initialization` (modules added to `authorized_imports`). | Full signature + docstring of each symbol is documented so the agent can use them. |
|
|
72
|
+
|
|
73
|
+
### Example
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from strands_code_agent.toolkits import Toolkit
|
|
77
|
+
|
|
78
|
+
VISUALIZATION_TOOLKIT = Toolkit(
|
|
79
|
+
# 1. libraries → PythonInterpreter.authorized_imports
|
|
80
|
+
# Allows the REPL to import these modules.
|
|
81
|
+
# Use "module.*" to allow a module and all its submodules.
|
|
82
|
+
libraries=["matplotlib.*", "seaborn.*"],
|
|
83
|
+
|
|
84
|
+
# 2. initialization_code → PythonInterpreter.state_initialization + System Prompt
|
|
85
|
+
# Runs before user code; also shown in the prompt so the agent
|
|
86
|
+
# knows plt and sns are already available.
|
|
87
|
+
initialization_code="""
|
|
88
|
+
import matplotlib
|
|
89
|
+
matplotlib.use('Agg') # Use non-interactive backend
|
|
90
|
+
import matplotlib.pyplot as plt
|
|
91
|
+
import seaborn as sns
|
|
92
|
+
""",
|
|
93
|
+
|
|
94
|
+
# 3. usage_instructions → System Prompt only
|
|
95
|
+
# Tells the agent how to behave with these libraries.
|
|
96
|
+
usage_instructions="Do not try to show any matplotlib image: the python_repl tool executes the code in a sub-process without a GUI.",
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Built-in Toolkits
|
|
101
|
+
|
|
102
|
+
The library ships with ready-to-use toolkits:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from strands_code_agent.toolkits import (
|
|
106
|
+
VISUALIZATION_TOOLKIT, # matplotlib + seaborn (non-interactive backend)
|
|
107
|
+
DATA_ANALYSIS_TOOLKIT, # numpy + pandas + scipy + datetime
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Domain-Specific Code
|
|
112
|
+
|
|
113
|
+
Pass your own functions or classes via `domain_specific_code`. The `CodeAgent` will:
|
|
114
|
+
|
|
115
|
+
1. **Auto-import** them in `PythonInterpreter.state_initialization` (their modules are added to `authorized_imports`).
|
|
116
|
+
2. **Document** each symbol's full signature and docstring in the **System Prompt**, so the agent knows how to call them.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from strands_code_agent import CodeAgent, Toolkit
|
|
120
|
+
|
|
121
|
+
def calculate_roi(investment: float, returns: float) -> float:
|
|
122
|
+
"""Calculate return on investment as a percentage."""
|
|
123
|
+
return (returns - investment) / investment * 100
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
agent = CodeAgent(
|
|
127
|
+
system_prompt="You are a finance assistant.",
|
|
128
|
+
toolkits=[
|
|
129
|
+
Toolkit(domain_specific_code=[calculate_roi])
|
|
130
|
+
],
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
response = agent("What is the ROI if I invest 1000 and get back 1250?")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Combining Toolkits
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from strands_code_agent import CodeAgent
|
|
140
|
+
from strands_code_agent.toolkits import DATA_ANALYSIS_TOOLKIT, VISUALIZATION_TOOLKIT
|
|
141
|
+
|
|
142
|
+
agent = CodeAgent(
|
|
143
|
+
system_prompt="You are a data analyst.",
|
|
144
|
+
toolkits=[DATA_ANALYSIS_TOOLKIT, VISUALIZATION_TOOLKIT],
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Running Tests
|
|
149
|
+
|
|
150
|
+
The test suite uses [pytest](https://docs.pytest.org/). Install it and run from the project root:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install pytest
|
|
154
|
+
python -m pytest tests/ -v
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Security
|
|
158
|
+
|
|
159
|
+
See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
This library is licensed under the MIT-0 License. See the LICENSE file.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
strands_code_agent/__init__.py,sha256=roto57kfvRbs3o_mug5acxv5o-6I15EV3OePxd6YLcI,136
|
|
2
|
+
strands_code_agent/code_agent.py,sha256=9fVA3PG3KuAczciulhpGxZL2GH7FpqL7t5h8jZqGZtw,6414
|
|
3
|
+
strands_code_agent/document_code.py,sha256=s8VAI7rLk8T-fZ5RGYwJSAE8NhnY0kNdQe6jnRVMCfY,2065
|
|
4
|
+
strands_code_agent/imports.py,sha256=KYVgl7VTq9OQhzMDFEe2OgdKy144tS3NshK5QAOgW7Y,1238
|
|
5
|
+
strands_code_agent/toolkits.py,sha256=eo2apEyQ3NWCWYmisiN45uTLeDacnw61h_sDnfXhSl8,1444
|
|
6
|
+
strands_code_agent/utils.py,sha256=YfRND-jJoNl3jX3hv_O7t5V8RAsDgy8GUtfixq2e2qs,230
|
|
7
|
+
strands_code_agent/python_environments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
strands_code_agent/python_environments/base.py,sha256=S5FAhCKmN4wpxAveRjsb1VQ75vfgCG0_tP_Me_5T1cI,1344
|
|
9
|
+
strands_code_agent/python_environments/local_exec.py,sha256=fK9Xh830NvMDy7gMkSDc617WSzQ3BzsWvsmeOPbBLMA,1092
|
|
10
|
+
strands_code_agent/python_environments/local_sandboxed.py,sha256=wvVY-I5Y0zgMqja7uSi4M6WfQiCW4NAiInimW3ABJP8,2667
|
|
11
|
+
strands_code_agent-0.1.0.dist-info/METADATA,sha256=x5-ea4zYve9xrbuZL2WXlhli34HiZ_549ELr5nICgtc,7033
|
|
12
|
+
strands_code_agent-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
13
|
+
strands_code_agent-0.1.0.dist-info/licenses/LICENSE,sha256=fLdQcTJS79HVeIN7qHhbYTGRCZBvDBC4etq3z0ut_EI,947
|
|
14
|
+
strands_code_agent-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT No Attribution
|
|
2
|
+
|
|
3
|
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
12
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
13
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
14
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
15
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
17
|
+
|