Skip to content

Commit 7444ff9

Browse files
committed
fix(code_executors): Fix JSON field mismatches in AgentEngineSandboxCodeExecutor
- Fix input file payload keys: contents -> content, mimeType -> mime_type - Fix response parsing: read msg_out/msg_err with fallback to stdout/stderr - Add defensive type check for JSON response to prevent AttributeError Fixes #3690
1 parent 324796b commit 7444ff9

File tree

2 files changed

+448
-24
lines changed

2 files changed

+448
-24
lines changed

src/google/adk/code_executors/agent_engine_sandbox_code_executor.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from .code_execution_utils import CodeExecutionResult
2929
from .code_execution_utils import File
3030

31-
logger = logging.getLogger('google_adk.' + __name__)
31+
logger = logging.getLogger("google_adk." + __name__)
3232

3333

3434
class AgentEngineSandboxCodeExecutor(BaseCodeExecutor):
@@ -63,8 +63,8 @@ def __init__(
6363
**data: Additional keyword arguments to be passed to the base class.
6464
"""
6565
super().__init__(**data)
66-
sandbox_resource_name_pattern = r'^projects/([a-zA-Z0-9-_]+)/locations/([a-zA-Z0-9-_]+)/reasoningEngines/(\d+)/sandboxEnvironments/(\d+)$'
67-
agent_engine_resource_name_pattern = r'^projects/([a-zA-Z0-9-_]+)/locations/([a-zA-Z0-9-_]+)/reasoningEngines/(\d+)$'
66+
sandbox_resource_name_pattern = r"^projects/([a-zA-Z0-9-_]+)/locations/([a-zA-Z0-9-_]+)/reasoningEngines/(\d+)/sandboxEnvironments/(\d+)$"
67+
agent_engine_resource_name_pattern = r"^projects/([a-zA-Z0-9-_]+)/locations/([a-zA-Z0-9-_]+)/reasoningEngines/(\d+)$"
6868

6969
if sandbox_resource_name is not None:
7070
self.sandbox_resource_name = sandbox_resource_name
@@ -84,17 +84,17 @@ def __init__(
8484
# @TODO - Add TTL for sandbox creation after it is available
8585
# in SDK.
8686
operation = self._get_api_client().agent_engines.sandboxes.create(
87-
spec={'code_execution_environment': {}},
87+
spec={"code_execution_environment": {}},
8888
name=agent_engine_resource_name,
8989
config=types.CreateAgentEngineSandboxConfig(
90-
display_name='default_sandbox'
90+
display_name="default_sandbox"
9191
),
9292
)
9393
self.sandbox_resource_name = operation.response.name
9494
else:
9595
raise ValueError(
96-
'Either sandbox_resource_name or agent_engine_resource_name must be'
97-
' set.'
96+
"Either sandbox_resource_name or agent_engine_resource_name must be"
97+
" set."
9898
)
9999

100100
@override
@@ -105,14 +105,14 @@ def execute_code(
105105
) -> CodeExecutionResult:
106106
# Execute the code.
107107
input_data = {
108-
'code': code_execution_input.code,
108+
"code": code_execution_input.code,
109109
}
110110
if code_execution_input.input_files:
111-
input_data['files'] = [
111+
input_data["files"] = [
112112
{
113-
'name': f.name,
114-
'contents': f.content,
115-
'mimeType': f.mime_type,
113+
"name": f.name,
114+
"content": f.content,
115+
"mime_type": f.mime_type,
116116
}
117117
for f in code_execution_input.input_files
118118
]
@@ -123,27 +123,39 @@ def execute_code(
123123
input_data=input_data,
124124
)
125125
)
126-
logger.debug('Executed code:\n```\n%s\n```', code_execution_input.code)
126+
logger.debug("Executed code:\n```\n%s\n```", code_execution_input.code)
127127
saved_files = []
128-
stdout = ''
129-
stderr = ''
128+
stdout = ""
129+
stderr = ""
130130
for output in code_execution_response.outputs:
131-
if output.mime_type == 'application/json' and (
131+
if output.mime_type == "application/json" and (
132132
output.metadata is None
133133
or output.metadata.attributes is None
134-
or 'file_name' not in output.metadata.attributes
134+
or "file_name" not in output.metadata.attributes
135135
):
136-
json_output_data = json.loads(output.data.decode('utf-8'))
137-
stdout = json_output_data.get('stdout', '')
138-
stderr = json_output_data.get('stderr', '')
136+
json_output_data = json.loads(output.data.decode("utf-8"))
137+
if isinstance(json_output_data, dict):
138+
# Primary fields returned by the API are msg_out/msg_err.
139+
# Fall back to stdout/stderr for backward compatibility.
140+
stdout = json_output_data.get(
141+
"msg_out", json_output_data.get("stdout", "")
142+
)
143+
stderr = json_output_data.get(
144+
"msg_err", json_output_data.get("stderr", "")
145+
)
146+
else:
147+
logger.warning(
148+
"Received non-dict JSON output from sandbox: %s",
149+
json_output_data,
150+
)
139151
else:
140-
file_name = ''
152+
file_name = ""
141153
if (
142154
output.metadata is not None
143155
and output.metadata.attributes is not None
144156
):
145-
file_name = output.metadata.attributes.get('file_name', b'').decode(
146-
'utf-8'
157+
file_name = output.metadata.attributes.get("file_name", b"").decode(
158+
"utf-8"
147159
)
148160
mime_type = output.mime_type
149161
if not mime_type:
@@ -183,6 +195,6 @@ def _get_project_id_and_location_from_resource_name(
183195
match = re.fullmatch(pattern, resource_name)
184196

185197
if not match:
186-
raise ValueError(f'resource name {resource_name} is not valid.')
198+
raise ValueError(f"resource name {resource_name} is not valid.")
187199

188200
return match.groups()[0], match.groups()[1]

0 commit comments

Comments
 (0)