Skip to content

Commit e2ad09d

Browse files
committed
Add test to force exception and exercise _handle_tool_streaming_completion_error.
1 parent 9503558 commit e2ad09d

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed

newrelic/hooks/mlmodel_strands.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ def _handle_tool_streaming_completion_error(self, transaction):
298298
strands_attrs, tool_results, transaction, linking_metadata
299299
)
300300
tool_event_dict.update({"duration": self._nr_ft.duration * 1000})
301+
# Ensure error flag is set to True in case the tool_results did not indicate an error
302+
if "error" not in tool_event_dict:
303+
tool_event_dict.update({"error": True})
304+
301305
transaction.record_custom_event("LlmTool", tool_event_dict)
302306

303307
except Exception:

tests/mlmodel_strands/test_agent.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@
5151
)
5252
]
5353

54+
tool_recorded_event_forced_internal_error = [
55+
(
56+
{"type": "LlmTool"},
57+
{
58+
"id": None,
59+
"run_id": "123",
60+
"name": "add_exclamation",
61+
"agent_name": "my_agent",
62+
"span_id": None,
63+
"trace_id": "trace-id",
64+
"input": "{'message': 'Hello'}",
65+
"vendor": "strands",
66+
"ingest_source": "Python",
67+
"duration": None,
68+
"error": True,
69+
},
70+
)
71+
]
5472

5573
tool_recorded_event_error_coro = [
5674
(
@@ -362,6 +380,43 @@ def test_agent_invoke_tool_agen_runtime_error(set_trace_info, single_tool_model_
362380
assert response.metrics.tool_metrics["throw_exception_agen"].error_count == 1
363381

364382

383+
@reset_core_stats_engine()
384+
@validate_transaction_error_event_count(1)
385+
@validate_error_trace_attributes(callable_name(ValueError), exact_attrs={"agent": {}, "intrinsic": {}, "user": {}})
386+
@validate_custom_events(agent_recorded_event)
387+
@validate_custom_events(tool_recorded_event_forced_internal_error)
388+
@validate_custom_event_count(count=2)
389+
@validate_transaction_metrics(
390+
"test_agent:test_agent_tool_forced_exception",
391+
scoped_metrics=[
392+
("Llm/agent/Strands/strands.agent.agent:Agent.stream_async/my_agent", 1),
393+
("Llm/tool/Strands/strands.tools.executors._executor:ToolExecutor._stream/add_exclamation", 1),
394+
],
395+
rollup_metrics=[
396+
("Llm/agent/Strands/strands.agent.agent:Agent.stream_async/my_agent", 1),
397+
("Llm/tool/Strands/strands.tools.executors._executor:ToolExecutor._stream/add_exclamation", 1),
398+
],
399+
background_task=True,
400+
)
401+
@validate_attributes("agent", ["llm"])
402+
@background_task()
403+
def test_agent_tool_forced_exception(set_trace_info, single_tool_model):
404+
# Add a wrapper to intentionally force an error in the ToolExecutor._stream code to hit the exception path in
405+
# the AsyncGeneratorProxy
406+
@transient_function_wrapper("strands.hooks.events", "BeforeToolCallEvent.__init__")
407+
def _wrap_BeforeToolCallEvent_init(wrapped, instance, args, kwargs):
408+
raise ValueError("Oops")
409+
410+
@_wrap_BeforeToolCallEvent_init
411+
def _test():
412+
set_trace_info()
413+
my_agent = Agent(name="my_agent", model=single_tool_model, tools=[add_exclamation])
414+
my_agent('Add an exclamation to the word "Hello"')
415+
416+
# This will not explicitly raise a ValueError when running the test but we are still able to capture it in the error trace
417+
_test()
418+
419+
365420
@reset_core_stats_engine()
366421
@validate_custom_event_count(count=0)
367422
def test_agent_invoke_outside_txn(single_tool_model):

0 commit comments

Comments
 (0)