@@ -250,3 +250,90 @@ def cancel_callback(event):
250250 tru_results = tool_results
251251 exp_results = [exp_events [- 1 ].tool_result ]
252252 assert tru_results == exp_results
253+
254+
255+ @pytest .mark .asyncio
256+ async def test_executor_stream_sets_span_attributes (
257+ executor , agent , tool_results , invocation_state , weather_tool , alist
258+ ):
259+ """Test that span attributes are set correctly when tool_spec is available."""
260+ with unittest .mock .patch ("strands.tools.executors._executor.trace_api" ) as mock_trace_api :
261+ mock_span = unittest .mock .MagicMock ()
262+ mock_trace_api .get_current_span .return_value = mock_span
263+
264+ # Mock tool_spec with inputSchema containing json field
265+ with unittest .mock .patch .object (
266+ type (weather_tool ), "tool_spec" , new_callable = unittest .mock .PropertyMock
267+ ) as mock_tool_spec :
268+ mock_tool_spec .return_value = {
269+ "name" : "weather_tool" ,
270+ "description" : "Get weather information" ,
271+ "inputSchema" : {"json" : {"type" : "object" , "properties" : {}}, "type" : "object" },
272+ }
273+
274+ tool_use : ToolUse = {"name" : "weather_tool" , "toolUseId" : "1" , "input" : {}}
275+ stream = executor ._stream (agent , tool_use , tool_results , invocation_state )
276+
277+ await alist (stream )
278+
279+ # Verify set_attribute was called with correct values
280+ calls = mock_span .set_attribute .call_args_list
281+ assert len (calls ) == 2
282+
283+ # Check description attribute
284+ assert calls [0 ][0 ][0 ] == "gen_ai.tool.description"
285+ assert calls [0 ][0 ][1 ] == "Get weather information"
286+
287+ # Check json_schema attribute
288+ assert calls [1 ][0 ][0 ] == "gen_ai.tool.json_schema"
289+ # The serialize function should have been called on the json field
290+
291+
292+ @pytest .mark .asyncio
293+ async def test_executor_stream_handles_missing_json_in_input_schema (
294+ executor , agent , tool_results , invocation_state , weather_tool , alist
295+ ):
296+ """Test that span attributes handle inputSchema without json field gracefully."""
297+ with unittest .mock .patch ("strands.tools.executors._executor.trace_api" ) as mock_trace_api :
298+ mock_span = unittest .mock .MagicMock ()
299+ mock_trace_api .get_current_span .return_value = mock_span
300+
301+ # Mock tool_spec with inputSchema but no json field
302+ with unittest .mock .patch .object (
303+ type (weather_tool ), "tool_spec" , new_callable = unittest .mock .PropertyMock
304+ ) as mock_tool_spec :
305+ mock_tool_spec .return_value = {
306+ "name" : "weather_tool" ,
307+ "description" : "Get weather information" ,
308+ "inputSchema" : {"type" : "object" , "properties" : {}},
309+ }
310+
311+ tool_use : ToolUse = {"name" : "weather_tool" , "toolUseId" : "1" , "input" : {}}
312+ stream = executor ._stream (agent , tool_use , tool_results , invocation_state )
313+
314+ # Should not raise an error - json_schema attribute just won't be set
315+ await alist (stream )
316+
317+ # Verify only description attribute was set (not json_schema)
318+ calls = mock_span .set_attribute .call_args_list
319+ assert len (calls ) == 1
320+ assert calls [0 ][0 ][0 ] == "gen_ai.tool.description"
321+
322+
323+ @pytest .mark .asyncio
324+ async def test_executor_stream_no_span_attributes_when_no_tool_spec (
325+ executor , agent , tool_results , invocation_state , alist
326+ ):
327+ """Test that no span attributes are set when tool_spec is None."""
328+ with unittest .mock .patch ("strands.tools.executors._executor.trace_api" ) as mock_trace_api :
329+ mock_span = unittest .mock .MagicMock ()
330+ mock_trace_api .get_current_span .return_value = mock_span
331+
332+ # Use unknown tool which will have no tool_spec
333+ tool_use : ToolUse = {"name" : "unknown_tool" , "toolUseId" : "1" , "input" : {}}
334+ stream = executor ._stream (agent , tool_use , tool_results , invocation_state )
335+
336+ await alist (stream )
337+
338+ # Verify set_attribute was not called since tool_spec is None
339+ mock_span .set_attribute .assert_not_called ()
0 commit comments