Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
174abaa
Add Strands subcomponent attrs.
umaannamalai Feb 10, 2026
9b4d87e
Update tests.
umaannamalai Feb 10, 2026
c8e1a7f
Cleanup.
umaannamalai Feb 11, 2026
4a011c6
Add subcomponent attrs.
umaannamalai Feb 3, 2026
62fc928
Add validator to tool tests.
umaannamalai Feb 10, 2026
bd2daf7
Cleanup tests.
umaannamalai Feb 11, 2026
7f522fd
Add subcomponent attribute to MCP instrumentation.
umaannamalai Feb 10, 2026
132df90
[MegaLinter] Apply linters fixes
umaannamalai Feb 17, 2026
11db62f
Initial commit.
umaannamalai Oct 9, 2025
330c547
Add attrs to spans.
umaannamalai Oct 10, 2025
ba5670c
Update validators in tests.
umaannamalai Oct 14, 2025
3992300
Swap out subcomponent attribute names.
umaannamalai Oct 16, 2025
a69780e
Update tests.
umaannamalai Feb 10, 2026
f05d9ba
[MegaLinter] Apply linters fixes
umaannamalai Feb 17, 2026
8ec37d7
Merge branch 'develop-agentic-subcomponent-attrs' into mcp-subcompone…
umaannamalai Feb 17, 2026
b596a6a
Merge branch 'develop-agentic-subcomponent-attrs' into strands-subcom…
mergify[bot] Feb 17, 2026
4894692
Merge branch 'develop-agentic-subcomponent-attrs' into feature-agenti…
mergify[bot] Feb 17, 2026
80c0ac0
Merge branch 'develop-agentic-subcomponent-attrs' into langchain-subc…
mergify[bot] Feb 17, 2026
0480f61
Merge pull request #1655 from newrelic/langchain-subcomponent-attrs
umaannamalai Feb 17, 2026
c9b89e6
Merge branch 'develop-agentic-subcomponent-attrs' into feature-agenti…
umaannamalai Feb 17, 2026
d3fb265
Merge branch 'develop-agentic-subcomponent-attrs' into strands-subcom…
umaannamalai Feb 17, 2026
a01f9d9
Merge branch 'develop-agentic-subcomponent-attrs' into mcp-subcompone…
mergify[bot] Feb 17, 2026
f3ed326
Merge pull request #1654 from newrelic/feature-agentic-component-attrs
umaannamalai Feb 17, 2026
2ec801b
Merge branch 'develop-agentic-subcomponent-attrs' into strands-subcom…
umaannamalai Feb 17, 2026
668d8a8
Merge branch 'develop-agentic-subcomponent-attrs' into mcp-subcompone…
mergify[bot] Feb 17, 2026
619b688
Merge pull request #1653 from newrelic/strands-subcomponent-attrs
umaannamalai Feb 17, 2026
5758689
Merge branch 'develop-agentic-subcomponent-attrs' into mcp-subcompone…
umaannamalai Feb 17, 2026
06ef8ed
Merge pull request #1652 from newrelic/mcp-subcomponent-attrs
umaannamalai Feb 17, 2026
2cb105f
Revert "Add subcomponent attribute to Autogen instrumentation. "
umaannamalai Feb 18, 2026
d58b2fb
Merge pull request #1665 from newrelic/revert-1654-feature-agentic-co…
umaannamalai Feb 18, 2026
c31bddd
Merge branch 'main' into develop-agentic-subcomponent-attrs
umaannamalai Feb 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newrelic/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"response.headers.contentType",
"response.status",
"server.address",
"subcomponent",
"zeebe.client.bpmnProcessId",
"zeebe.client.messageName",
"zeebe.client.correlationKey",
Expand Down
5 changes: 4 additions & 1 deletion newrelic/hooks/adapter_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging

from newrelic.api.function_trace import FunctionTrace
Expand All @@ -37,8 +38,10 @@ async def wrap_call_tool(wrapped, instance, args, kwargs):
bound_args = bind_args(wrapped, args, kwargs)
tool_name = bound_args.get("name") or "tool"
function_trace_name = f"{func_name}/{tool_name}"
agentic_subcomponent_data = {"type": "APM-AI_TOOL", "name": tool_name}

with FunctionTrace(name=function_trace_name, group="Llm/tool/MCP", source=wrapped):
with FunctionTrace(name=function_trace_name, group="Llm/tool/MCP", source=wrapped) as ft:
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
return await wrapped(*args, **kwargs)


Expand Down
19 changes: 19 additions & 0 deletions newrelic/hooks/mlmodel_langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import sys
import time
Expand Down Expand Up @@ -161,9 +162,11 @@ def invoke(self, *args, **kwargs):
agent_id = str(uuid.uuid4())
agent_event_dict = _construct_base_agent_event_dict(agent_name, agent_id, transaction)
function_trace_name = f"invoke/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
try:
return_val = self.__wrapped__.invoke(*args, **kwargs)
except Exception:
Expand All @@ -189,9 +192,11 @@ async def ainvoke(self, *args, **kwargs):
agent_id = str(uuid.uuid4())
agent_event_dict = _construct_base_agent_event_dict(agent_name, agent_id, transaction)
function_trace_name = f"ainvoke/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
try:
return_val = await self.__wrapped__.ainvoke(*args, **kwargs)
except Exception:
Expand All @@ -217,9 +222,11 @@ def stream(self, *args, **kwargs):
agent_id = str(uuid.uuid4())
agent_event_dict = _construct_base_agent_event_dict(agent_name, agent_id, transaction)
function_trace_name = f"stream/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
try:
return_val = self.__wrapped__.stream(*args, **kwargs)
return_val = GeneratorProxy(
Expand All @@ -242,9 +249,11 @@ def astream(self, *args, **kwargs):
agent_id = str(uuid.uuid4())
agent_event_dict = _construct_base_agent_event_dict(agent_name, agent_id, transaction)
function_trace_name = f"astream/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
try:
return_val = self.__wrapped__.astream(*args, **kwargs)
return_val = AsyncGeneratorProxy(
Expand All @@ -267,9 +276,11 @@ def transform(self, *args, **kwargs):
agent_id = str(uuid.uuid4())
agent_event_dict = _construct_base_agent_event_dict(agent_name, agent_id, transaction)
function_trace_name = f"stream/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
try:
return_val = self.__wrapped__.transform(*args, **kwargs)
return_val = GeneratorProxy(
Expand All @@ -292,9 +303,11 @@ def atransform(self, *args, **kwargs):
agent_id = str(uuid.uuid4())
agent_event_dict = _construct_base_agent_event_dict(agent_name, agent_id, transaction)
function_trace_name = f"astream/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
try:
return_val = self.__wrapped__.atransform(*args, **kwargs)
return_val = AsyncGeneratorProxy(
Expand Down Expand Up @@ -512,8 +525,11 @@ def wrap_tool_sync_run(wrapped, instance, args, kwargs):
except Exception:
filtered_tool_input = tool_input

agentic_subcomponent_data = {"type": "APM-AI_TOOL", "name": tool_name}

ft = FunctionTrace(name=f"{wrapped.__name__}/{tool_name}", group="Llm/tool/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
linking_metadata = get_trace_linking_metadata()
try:
return_val = wrapped(**run_args)
Expand Down Expand Up @@ -573,8 +589,11 @@ async def wrap_tool_async_run(wrapped, instance, args, kwargs):
except Exception:
filtered_tool_input = tool_input

agentic_subcomponent_data = {"type": "APM-AI_TOOL", "name": tool_name}

ft = FunctionTrace(name=f"{wrapped.__name__}/{tool_name}", group="Llm/tool/LangChain")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
linking_metadata = get_trace_linking_metadata()
try:
return_val = await wrapped(**run_args)
Expand Down
8 changes: 6 additions & 2 deletions newrelic/hooks/mlmodel_strands.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import sys
import uuid
Expand Down Expand Up @@ -94,9 +95,12 @@ def wrap_stream_async(wrapped, instance, args, kwargs):
func_name = callable_name(wrapped)
agent_name = getattr(instance, "name", "agent")
function_trace_name = f"{func_name}/{agent_name}"
agentic_subcomponent_data = {"type": "APM-AI_AGENT", "name": agent_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/agent/Strands")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))

linking_metadata = get_trace_linking_metadata()
agent_id = str(uuid.uuid4())

Expand All @@ -105,7 +109,6 @@ def wrap_stream_async(wrapped, instance, args, kwargs):
except Exception:
raise

# For streaming responses, wrap with proxy and attach metadata
try:
# For streaming responses, wrap with proxy and attach metadata
proxied_return_val = AsyncGeneratorProxy(
Expand All @@ -126,7 +129,6 @@ def _record_agent_event_on_stop_iteration(self, transaction):
# Use saved linking metadata to maintain correct span association
linking_metadata = self._nr_metadata or get_trace_linking_metadata()
self._nr_ft.__exit__(None, None, None)

try:
strands_attrs = getattr(self, "_nr_strands_attrs", {})

Expand Down Expand Up @@ -352,9 +354,11 @@ def wrap_tool_executor__stream(wrapped, instance, args, kwargs):

func_name = callable_name(wrapped)
function_trace_name = f"{func_name}/{tool_name}"
agentic_subcomponent_data = {"type": "APM-AI_TOOL", "name": tool_name}

ft = FunctionTrace(name=function_trace_name, group="Llm/tool/Strands")
ft.__enter__()
ft._add_agent_attribute("subcomponent", json.dumps(agentic_subcomponent_data))
linking_metadata = get_trace_linking_metadata()
tool_id = str(uuid.uuid4())

Expand Down
3 changes: 3 additions & 0 deletions tests/adapter_mcp/test_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from mcp.server.fastmcp.tools import ToolManager
from testing_support.ml_testing_utils import disabled_ai_monitoring_settings
from testing_support.validators.validate_function_not_called import validate_function_not_called
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics

from newrelic.api.background_task import background_task
Expand Down Expand Up @@ -57,6 +58,7 @@ def echo_prompt(message: str):
rollup_metrics=[("Llm/tool/MCP/mcp.client.session:ClientSession.call_tool/add_exclamation", 1)],
background_task=True,
)
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task()
def test_tool_tracing_via_client_session(loop, fastmcp_server):
async def _test():
Expand All @@ -75,6 +77,7 @@ async def _test():
rollup_metrics=[("Llm/tool/MCP/mcp.server.fastmcp.tools.tool_manager:ToolManager.call_tool/add_exclamation", 1)],
background_task=True,
)
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task()
def test_tool_tracing_via_tool_manager(loop):
async def _test():
Expand Down
14 changes: 13 additions & 1 deletion tests/mlmodel_langchain/test_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import pytest
from langchain.messages import HumanMessage
from langchain.tools import tool
from testing_support.fixtures import reset_core_stats_engine, validate_attributes
from testing_support.fixtures import dt_enabled, reset_core_stats_engine, validate_attributes
from testing_support.ml_testing_utils import (
disabled_ai_monitoring_record_content_settings,
disabled_ai_monitoring_settings,
Expand All @@ -24,6 +24,7 @@
from testing_support.validators.validate_custom_event import validate_custom_event_count
from testing_support.validators.validate_custom_events import validate_custom_events
from testing_support.validators.validate_error_trace_attributes import validate_error_trace_attributes
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_error_event_count import validate_transaction_error_event_count
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics

Expand Down Expand Up @@ -76,6 +77,7 @@ def add_exclamation(message: str) -> str:
return f"{message}!"


@dt_enabled
@reset_core_stats_engine()
def test_agent(exercise_agent, create_agent_runnable, set_trace_info, method_name):
@validate_custom_events(events_with_context_attrs(agent_recorded_event))
Expand All @@ -87,6 +89,8 @@ def test_agent(exercise_agent, create_agent_runnable, set_trace_info, method_nam
background_task=True,
)
@validate_attributes("agent", ["llm"])
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task(name="test_agent")
def _test():
set_trace_info()
Expand All @@ -100,6 +104,7 @@ def _test():
_test()


@dt_enabled
@reset_core_stats_engine()
@disabled_ai_monitoring_record_content_settings
def test_agent_no_content(exercise_agent, create_agent_runnable, set_trace_info, method_name):
Expand All @@ -112,6 +117,8 @@ def test_agent_no_content(exercise_agent, create_agent_runnable, set_trace_info,
background_task=True,
)
@validate_attributes("agent", ["llm"])
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task(name="test_agent_no_content")
def _test():
set_trace_info()
Expand All @@ -123,13 +130,15 @@ def _test():
_test()


@dt_enabled
@reset_core_stats_engine()
@validate_custom_event_count(count=0)
def test_agent_outside_txn(exercise_agent, create_agent_runnable):
my_agent = create_agent_runnable(tools=[add_exclamation], system_prompt="You are a text manipulation algorithm.")
exercise_agent(my_agent, PROMPT)


@dt_enabled
@disabled_ai_monitoring_settings
@reset_core_stats_engine()
@validate_custom_event_count(count=0)
Expand All @@ -140,6 +149,7 @@ def test_agent_disabled_ai_monitoring_events(exercise_agent, create_agent_runnab
exercise_agent(my_agent, PROMPT)


@dt_enabled
@reset_core_stats_engine()
def test_agent_execution_error(exercise_agent, create_agent_runnable, set_trace_info, method_name, agent_runnable_type):
# Add a wrapper to intentionally force an error in the Agent code
Expand All @@ -159,6 +169,8 @@ def inject_exception(wrapped, instance, args, kwargs):
background_task=True,
)
@validate_attributes("agent", ["llm"])
# Only an agent span is expected here and not a tool because the error is injected before the tool is called
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@background_task(name="test_agent_execution_error")
def _test():
set_trace_info()
Expand Down
15 changes: 14 additions & 1 deletion tests/mlmodel_langchain/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import pytest
from langchain.messages import HumanMessage
from testing_support.fixtures import reset_core_stats_engine, validate_attributes
from testing_support.fixtures import dt_enabled, reset_core_stats_engine, validate_attributes
from testing_support.ml_testing_utils import (
disabled_ai_monitoring_record_content_settings,
events_with_context_attrs,
Expand All @@ -23,6 +23,7 @@
from testing_support.validators.validate_custom_event import validate_custom_event_count
from testing_support.validators.validate_custom_events import validate_custom_events
from testing_support.validators.validate_error_trace_attributes import validate_error_trace_attributes
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_error_event_count import validate_transaction_error_event_count
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics

Expand Down Expand Up @@ -95,6 +96,7 @@
]


@dt_enabled
@reset_core_stats_engine()
def test_tool(exercise_agent, set_trace_info, create_agent_runnable, add_exclamation, tool_method_name):
@validate_custom_events(events_with_context_attrs(tool_recorded_event))
Expand All @@ -106,6 +108,8 @@ def test_tool(exercise_agent, set_trace_info, create_agent_runnable, add_exclama
background_task=True,
)
@validate_attributes("agent", ["llm"])
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task(name="test_tool")
def _test():
set_trace_info()
Expand All @@ -119,6 +123,7 @@ def _test():
_test()


@dt_enabled
@reset_core_stats_engine()
@disabled_ai_monitoring_record_content_settings
def test_tool_no_content(exercise_agent, set_trace_info, create_agent_runnable, add_exclamation, tool_method_name):
Expand All @@ -131,6 +136,8 @@ def test_tool_no_content(exercise_agent, set_trace_info, create_agent_runnable,
background_task=True,
)
@validate_attributes("agent", ["llm"])
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task(name="test_tool_no_content")
def _test():
set_trace_info()
Expand All @@ -142,6 +149,7 @@ def _test():
_test()


@dt_enabled
@reset_core_stats_engine()
def test_tool_execution_error(exercise_agent, set_trace_info, create_agent_runnable, add_exclamation, tool_method_name):
@validate_transaction_error_event_count(1)
Expand All @@ -157,6 +165,8 @@ def test_tool_execution_error(exercise_agent, set_trace_info, create_agent_runna
background_task=True,
)
@validate_attributes("agent", ["llm"])
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task(name="test_tool_execution_error")
def _test():
set_trace_info()
Expand All @@ -169,6 +179,7 @@ def _test():
_test()


@dt_enabled
@reset_core_stats_engine()
def test_tool_pre_execution_exception(
exercise_agent, set_trace_info, create_agent_runnable, add_exclamation, tool_method_name
Expand All @@ -190,6 +201,8 @@ def inject_exception(wrapped, instance, args, kwargs):
background_task=True,
)
@validate_attributes("agent", ["llm"])
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_AGENT", "name": "my_agent"}'})
@validate_span_events(count=1, exact_agents={"subcomponent": '{"type": "APM-AI_TOOL", "name": "add_exclamation"}'})
@background_task(name="test_tool_pre_execution_exception")
def _test():
set_trace_info()
Expand Down
Loading
Loading