strands-code-agent 0.1.0__tar.gz → 0.2.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.
Files changed (28) hide show
  1. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/PKG-INFO +11 -3
  2. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/README.md +9 -2
  3. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/pyproject.toml +2 -1
  4. strands_code_agent-0.2.0/strands_code_agent/callback_handler.py +80 -0
  5. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/code_agent.py +8 -3
  6. strands_code_agent-0.2.0/strands_code_agent/utils.py +30 -0
  7. strands_code_agent-0.1.0/strands_code_agent/utils.py +0 -7
  8. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/.github/workflows/publish.yml +0 -0
  9. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/.gitignore +0 -0
  10. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/CODE_OF_CONDUCT.md +0 -0
  11. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/CONTRIBUTING.md +0 -0
  12. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/LICENSE +0 -0
  13. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/__init__.py +0 -0
  14. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/document_code.py +0 -0
  15. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/imports.py +0 -0
  16. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/python_environments/__init__.py +0 -0
  17. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/python_environments/base.py +0 -0
  18. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/python_environments/local_exec.py +0 -0
  19. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/python_environments/local_sandboxed.py +0 -0
  20. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/strands_code_agent/toolkits.py +0 -0
  21. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/__init__.py +0 -0
  22. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_code_agent.py +0 -0
  23. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_document_code.py +0 -0
  24. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_exec_python_interpreter.py +0 -0
  25. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_readme_examples.py +0 -0
  26. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_sandboxed_python_interpreter.py +0 -0
  27. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_toolkits.py +0 -0
  28. {strands_code_agent-0.1.0 → strands_code_agent-0.2.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strands-code-agent
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A coding agent built on Strands Agents SDK that uses code generation as the primary action interface
5
5
  Project-URL: Homepage, https://github.com/aws-samples/sample-strands-code-agent
6
6
  Project-URL: Repository, https://github.com/aws-samples/sample-strands-code-agent
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
21
  Requires-Python: >=3.10
22
22
  Requires-Dist: jinja2>=3.0
23
+ Requires-Dist: rich>=13.0
23
24
  Requires-Dist: smolagents>=1.0.0
24
25
  Requires-Dist: strands-agents>=0.1.0
25
26
  Description-Content-Type: text/markdown
26
27
 
27
28
  # strands-code-agent
28
29
 
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
+ 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, 67% fewer output 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
 
31
32
  ## Installation
32
33
 
@@ -39,7 +40,7 @@ pip install strands-code-agent
39
40
  ```python
40
41
  from strands_code_agent import CodeAgent
41
42
 
42
- agent = CodeAgent(system_prompt="You are a helpful data analyst.")
43
+ agent = CodeAgent()
43
44
 
44
45
  response = agent("What is 2 ** 10?")
45
46
  ```
@@ -161,3 +162,10 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform
161
162
  ## License
162
163
 
163
164
  This library is licensed under the MIT-0 License. See the LICENSE file.
165
+
166
+ ## Example Agents
167
+
168
+ Example agents built with this library:
169
+
170
+ - [Data Analyst Agent](https://github.com/aws-solutions-library-samples/guidance-for-agentic-data-analyst-using-amazon-bedrock-agentcore-on-aws/blob/main/agent/aws_data_analyst/data_analyst_agent.py#L92)
171
+ - [Geospatial Agent](https://github.com/aws-samples/sample-geospatial-code-agent/blob/main/agent/geospatial_agent/agent.py#L93)
@@ -1,6 +1,6 @@
1
1
  # strands-code-agent
2
2
 
3
- 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.
3
+ 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, 67% fewer output 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.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,7 +13,7 @@ pip install strands-code-agent
13
13
  ```python
14
14
  from strands_code_agent import CodeAgent
15
15
 
16
- agent = CodeAgent(system_prompt="You are a helpful data analyst.")
16
+ agent = CodeAgent()
17
17
 
18
18
  response = agent("What is 2 ** 10?")
19
19
  ```
@@ -135,3 +135,10 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform
135
135
  ## License
136
136
 
137
137
  This library is licensed under the MIT-0 License. See the LICENSE file.
138
+
139
+ ## Example Agents
140
+
141
+ Example agents built with this library:
142
+
143
+ - [Data Analyst Agent](https://github.com/aws-solutions-library-samples/guidance-for-agentic-data-analyst-using-amazon-bedrock-agentcore-on-aws/blob/main/agent/aws_data_analyst/data_analyst_agent.py#L92)
144
+ - [Geospatial Agent](https://github.com/aws-samples/sample-geospatial-code-agent/blob/main/agent/geospatial_agent/agent.py#L93)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "strands-code-agent"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "A coding agent built on Strands Agents SDK that uses code generation as the primary action interface"
9
9
  readme = "README.md"
10
10
  license = "MIT-0"
@@ -28,6 +28,7 @@ dependencies = [
28
28
  "strands-agents>=0.1.0",
29
29
  "smolagents>=1.0.0",
30
30
  "jinja2>=3.0",
31
+ "rich>=13.0",
31
32
  ]
32
33
 
33
34
  [project.urls]
@@ -0,0 +1,80 @@
1
+ from typing import Any
2
+ import ast
3
+
4
+ from rich.syntax import Syntax
5
+ from rich.json import JSON
6
+ from rich.markdown import Markdown
7
+ from rich.pretty import Pretty
8
+ from rich.console import Console
9
+
10
+
11
+ def format_message(text):
12
+ # Python data-structure
13
+ try:
14
+ return Pretty(ast.literal_eval(text))
15
+ except (ValueError, SyntaxError):
16
+ pass
17
+
18
+ # JSON?
19
+ try:
20
+ return JSON(text, indent=4)
21
+ except ValueError:
22
+ pass
23
+
24
+ # Markdown? (rough heuristic)
25
+ import re
26
+ if re.search(r"(^#{1,6} |\*\*|__|\[.+\]\(.+\)|```)", text, re.MULTILINE):
27
+ return Markdown(text)
28
+
29
+ return text
30
+
31
+
32
+ class CodeAgentCallbackHandler:
33
+ def __init__(self, code_tools=None, output_prefix="STDOUT:", format_text=True, **kwargs) -> None:
34
+ self.console = Console()
35
+ if code_tools is None:
36
+ code_tools = {
37
+ 'python_repl': 'python'
38
+ }
39
+ self.code_tools = code_tools
40
+ self.output_prefix = output_prefix
41
+ if format_text:
42
+ self.format_text = format_message
43
+ else:
44
+ self.format_text = lambda x: x
45
+
46
+ def __call__(self, **kwargs: Any) -> None:
47
+ if 'message' not in kwargs:
48
+ return
49
+
50
+ message = kwargs['message']
51
+ role = message['role']
52
+ for content_item in message['content']:
53
+ if 'text' in content_item:
54
+ self.console.print(f"\n[{role.title()}]", end=" ")
55
+ self.console.print(self.format_text(content_item['text'].strip()), end="\n\n")
56
+
57
+ if 'toolUse' in content_item:
58
+ tool_use = content_item['toolUse']
59
+ name = tool_use['name']
60
+ self.console.print(f"\n[Tool] {name}")
61
+ if name in self.code_tools:
62
+ language = self.code_tools[name]
63
+ syntax = Syntax(tool_use['input']['code'], language)
64
+ self.console.print(syntax)
65
+ else:
66
+ for var, value in tool_use['input'].items():
67
+ self.console.print(f"\t- {var}: {value}")
68
+
69
+ if 'toolResult' in content_item:
70
+ tool_result = content_item['toolResult']
71
+ self.console.print(f"\n[Tool Result] Status: {tool_result['status']}")
72
+ for tool_item in tool_result['content']:
73
+ if 'text' in tool_item:
74
+ text = tool_item['text'].strip()
75
+ if text.startswith(self.output_prefix):
76
+ body = text.removeprefix(self.output_prefix).strip()
77
+ self.console.print(self.output_prefix)
78
+ self.console.print(self.format_text(body))
79
+ else:
80
+ self.console.print(text)
@@ -6,6 +6,7 @@ from jinja2 import Template
6
6
  from strands_code_agent.document_code import get_documentation
7
7
  from strands_code_agent.python_environments.local_sandboxed import SandboxedPythonInterpreter
8
8
  from strands_code_agent.imports import get_import_string, extract_imports
9
+ from strands_code_agent.callback_handler import CodeAgentCallbackHandler
9
10
 
10
11
 
11
12
  CODE_AGENT_INSTRUCTIONS = """
@@ -32,6 +33,8 @@ You can use the following Domain Specific Code:
32
33
  {{SYMBOLS_DOCUMENTATION}}
33
34
  """)
34
35
 
36
+ DEFAULT_CODE_AGENT_CALLBACK_HANDLER = CodeAgentCallbackHandler()
37
+
35
38
 
36
39
  class CodeAgent(Agent):
37
40
  """A coding agent that extends Strands Agent with a sandboxed Python REPL and domain-specific symbol documentation.
@@ -76,8 +79,9 @@ class CodeAgent(Agent):
76
79
  tools:list|None=None,
77
80
  toolkits:list|None=None,
78
81
  tmp_dir=True,
79
- timeout_seconds=60,
82
+ timeout_seconds=180,
80
83
  python_interpreter_class=SandboxedPythonInterpreter,
84
+ callback_handler=DEFAULT_CODE_AGENT_CALLBACK_HANDLER,
81
85
  **kwargs):
82
86
  authorized_imports = set()
83
87
  initialization_code = []
@@ -134,9 +138,10 @@ class CodeAgent(Agent):
134
138
  tools.append(python_repl_tool)
135
139
  else:
136
140
  tools = [python_repl_tool]
137
-
141
+
138
142
  kwargs.update({
139
143
  "system_prompt": system_prompt,
140
- "tools": tools
144
+ "tools": tools,
145
+ "callback_handler": callback_handler
141
146
  })
142
147
  super().__init__(**kwargs)
@@ -0,0 +1,30 @@
1
+ import base64
2
+
3
+
4
+ def image_to_base64(image_path):
5
+ """Read an image file and return its contents as a base64-encoded string."""
6
+ with open(image_path, 'rb') as f:
7
+ return base64.b64encode(f.read()).decode('utf-8')
8
+
9
+
10
+ def get_response_metrics(
11
+ response,
12
+ price_1M_input_tokens=None,
13
+ price_1M_output_tokens=None):
14
+ """
15
+ For the latest pricing see: https://aws.amazon.com/bedrock/pricing
16
+ """
17
+ summary = response.metrics.get_summary()
18
+ inputTokens = summary['accumulated_usage']['inputTokens']
19
+ outputTokens = summary['accumulated_usage']['outputTokens']
20
+ metrics = {
21
+ 'total_cycles': summary['total_cycles'],
22
+ 'total_duration': summary['total_duration'],
23
+ 'input_tokens': inputTokens,
24
+ 'output_tokens': outputTokens,
25
+ }
26
+
27
+ if price_1M_input_tokens and price_1M_output_tokens:
28
+ metrics['cost'] = (inputTokens * price_1M_input_tokens / 1_000_000) + (outputTokens * price_1M_output_tokens / 1_000_000)
29
+
30
+ return metrics
@@ -1,7 +0,0 @@
1
- import base64
2
-
3
-
4
- def image_to_base64(image_path):
5
- """Read an image file and return its contents as a base64-encoded string."""
6
- with open(image_path, 'rb') as f:
7
- return base64.b64encode(f.read()).decode('utf-8')