monocle-apptrace 0.6.0__py3-none-any.whl → 0.6.6__py3-none-any.whl
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.
Potentially problematic release.
This version of monocle-apptrace might be problematic. Click here for more details.
- monocle_apptrace/instrumentation/common/constants.py +8 -0
- monocle_apptrace/instrumentation/common/span_handler.py +73 -23
- monocle_apptrace/instrumentation/common/utils.py +63 -6
- monocle_apptrace/instrumentation/common/wrapper.py +111 -42
- monocle_apptrace/instrumentation/common/wrapper_method.py +4 -2
- monocle_apptrace/instrumentation/metamodel/a2a/methods.py +1 -1
- monocle_apptrace/instrumentation/metamodel/adk/_helper.py +2 -1
- monocle_apptrace/instrumentation/metamodel/agents/_helper.py +3 -3
- monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +2 -0
- monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +1 -1
- monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +1 -4
- monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +1 -1
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +5 -0
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +4 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/_helper.py +4 -4
- monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +4 -4
- monocle_apptrace/instrumentation/metamodel/flask/_helper.py +3 -3
- monocle_apptrace/instrumentation/metamodel/hugging_face/_helper.py +1 -1
- monocle_apptrace/instrumentation/metamodel/hugging_face/entities/inference.py +1 -4
- monocle_apptrace/instrumentation/metamodel/lambdafunc/_helper.py +1 -1
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +1 -4
- monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +30 -6
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +1 -1
- monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +88 -19
- monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +22 -6
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +30 -10
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +4 -3
- monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +15 -7
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +1 -8
- monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +1 -1
- monocle_apptrace/instrumentation/metamodel/mistral/_helper.py +1 -1
- monocle_apptrace/instrumentation/metamodel/mistral/entities/inference.py +1 -4
- monocle_apptrace/instrumentation/metamodel/mistral/methods.py +0 -8
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +47 -7
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +20 -4
- monocle_apptrace/instrumentation/metamodel/openai/methods.py +1 -1
- monocle_apptrace/instrumentation/metamodel/strands/_helper.py +44 -0
- monocle_apptrace/instrumentation/metamodel/strands/entities/agent.py +179 -0
- monocle_apptrace/instrumentation/metamodel/strands/entities/tool.py +62 -0
- monocle_apptrace/instrumentation/metamodel/strands/methods.py +20 -0
- {monocle_apptrace-0.6.0.dist-info → monocle_apptrace-0.6.6.dist-info}/METADATA +15 -4
- {monocle_apptrace-0.6.0.dist-info → monocle_apptrace-0.6.6.dist-info}/RECORD +45 -41
- {monocle_apptrace-0.6.0.dist-info → monocle_apptrace-0.6.6.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.6.0.dist-info → monocle_apptrace-0.6.6.dist-info}/entry_points.txt +0 -0
- {monocle_apptrace-0.6.0.dist-info → monocle_apptrace-0.6.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,6 +4,7 @@ This module provides utility functions to extract various attributes from agent
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from ast import arguments
|
|
7
|
+
import json
|
|
7
8
|
from typing import Any, Dict, Optional
|
|
8
9
|
from monocle_apptrace.instrumentation.metamodel.finish_types import map_adk_finish_reason_to_finish_type
|
|
9
10
|
|
|
@@ -181,7 +182,7 @@ def extract_tool_input(arguments: Dict[str, Any]) -> Any:
|
|
|
181
182
|
Returns:
|
|
182
183
|
Any: The extracted input data
|
|
183
184
|
"""
|
|
184
|
-
return
|
|
185
|
+
return json.dumps(arguments['kwargs'].get('args', {}))
|
|
185
186
|
|
|
186
187
|
def extract_tool_response(result: Any) -> Any:
|
|
187
188
|
"""
|
|
@@ -134,12 +134,12 @@ def extract_tool_input(arguments):
|
|
|
134
134
|
1
|
|
135
135
|
] # Second argument is usually the JSON string with params
|
|
136
136
|
if isinstance(tool_input, str):
|
|
137
|
-
return
|
|
137
|
+
return tool_input
|
|
138
138
|
elif isinstance(tool_input, dict):
|
|
139
|
-
return
|
|
139
|
+
return get_json_dumps(tool_input)
|
|
140
140
|
|
|
141
141
|
# Fallback to all args
|
|
142
|
-
return
|
|
142
|
+
return str(arguments["args"])
|
|
143
143
|
except Exception as e:
|
|
144
144
|
logger.warning("Warning: Error occurred in extract_tool_input: %s", str(e))
|
|
145
145
|
return []
|
|
@@ -128,6 +128,7 @@ TOOLS = {
|
|
|
128
128
|
{
|
|
129
129
|
"_comment": "tool type",
|
|
130
130
|
"attribute": "type",
|
|
131
|
+
"phase": "post_execution",
|
|
131
132
|
"accessor": lambda arguments: _helper.get_tool_type(arguments["span"]),
|
|
132
133
|
},
|
|
133
134
|
{
|
|
@@ -206,6 +207,7 @@ AGENT_DELEGATION = {
|
|
|
206
207
|
{
|
|
207
208
|
"_comment": "name of the target agent",
|
|
208
209
|
"attribute": "to_agent",
|
|
210
|
+
"phase": "post_execution",
|
|
209
211
|
"accessor": lambda arguments: _helper.extract_handoff_target(arguments),
|
|
210
212
|
},
|
|
211
213
|
]
|
|
@@ -67,7 +67,7 @@ def get_function_name(args) -> str:
|
|
|
67
67
|
class aiohttpSpanHandler(SpanHandler):
|
|
68
68
|
|
|
69
69
|
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
70
|
-
return aiohttp_pre_tracing(args)
|
|
70
|
+
return aiohttp_pre_tracing(args), None
|
|
71
71
|
|
|
72
72
|
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token):
|
|
73
73
|
aiohttp_post_tracing(token)
|
|
@@ -6,6 +6,7 @@ from monocle_apptrace.instrumentation.common.utils import (get_error_message, re
|
|
|
6
6
|
|
|
7
7
|
INFERENCE = {
|
|
8
8
|
"type": SPAN_TYPES.INFERENCE,
|
|
9
|
+
"subtype": lambda arguments: _helper.agent_inference_type(arguments),
|
|
9
10
|
"attributes": [
|
|
10
11
|
[
|
|
11
12
|
{
|
|
@@ -80,10 +81,6 @@ INFERENCE = {
|
|
|
80
81
|
"_comment": "finish type mapped from finish reason",
|
|
81
82
|
"attribute": "finish_type",
|
|
82
83
|
"accessor": lambda arguments: _helper.map_finish_reason_to_finish_type(_helper.extract_finish_reason(arguments))
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"attribute": "inference_sub_type",
|
|
86
|
-
"accessor": lambda arguments: _helper.agent_inference_type(arguments)
|
|
87
84
|
}
|
|
88
85
|
]
|
|
89
86
|
}
|
|
@@ -84,7 +84,7 @@ def azure_func_post_tracing(token):
|
|
|
84
84
|
class azureSpanHandler(SpanHandler):
|
|
85
85
|
|
|
86
86
|
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
87
|
-
return azure_func_pre_tracing(kwargs)
|
|
87
|
+
return azure_func_pre_tracing(kwargs), None
|
|
88
88
|
|
|
89
89
|
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token):
|
|
90
90
|
azure_func_post_tracing(token)
|
|
@@ -7,6 +7,8 @@ import logging
|
|
|
7
7
|
import json
|
|
8
8
|
from io import BytesIO
|
|
9
9
|
from functools import wraps
|
|
10
|
+
|
|
11
|
+
from rfc3986 import urlparse
|
|
10
12
|
from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
11
13
|
from monocle_apptrace.instrumentation.common.utils import ( get_exception_message, get_json_dumps, get_status_code,)
|
|
12
14
|
from monocle_apptrace.instrumentation.metamodel.finish_types import map_bedrock_finish_reason_to_finish_type
|
|
@@ -194,3 +196,6 @@ def extract_finish_reason(arguments):
|
|
|
194
196
|
def map_finish_reason_to_finish_type(finish_reason):
|
|
195
197
|
"""Map Bedrock finish_reason/stopReason to finish_type."""
|
|
196
198
|
return map_bedrock_finish_reason_to_finish_type(finish_reason)
|
|
199
|
+
|
|
200
|
+
def extract_provider_name(instance):
|
|
201
|
+
return urlparse(instance.meta.endpoint_url).hostname
|
|
@@ -15,6 +15,10 @@ INFERENCE = {
|
|
|
15
15
|
{
|
|
16
16
|
"attribute": "inference_endpoint",
|
|
17
17
|
"accessor": lambda arguments: arguments['instance'].meta.endpoint_url
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"attribute": "provider_name",
|
|
21
|
+
"accessor": lambda arguments: _helper.extract_provider_name(arguments['instance'])
|
|
18
22
|
}
|
|
19
23
|
],
|
|
20
24
|
[
|
|
@@ -77,12 +77,12 @@ class FastAPISpanHandler(SpanHandler):
|
|
|
77
77
|
fastapi_pre_tracing(scope)
|
|
78
78
|
return super().pre_tracing(to_wrap, wrapped, instance, args, kwargs)
|
|
79
79
|
|
|
80
|
-
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
80
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token):
|
|
81
81
|
fastapi_post_tracing()
|
|
82
|
-
return super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
82
|
+
return super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value, token)
|
|
83
83
|
|
|
84
84
|
class FastAPIResponseSpanHandler(SpanHandler):
|
|
85
|
-
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
85
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token):
|
|
86
86
|
try:
|
|
87
87
|
ctx = get_current()
|
|
88
88
|
if ctx is not None:
|
|
@@ -92,4 +92,4 @@ class FastAPIResponseSpanHandler(SpanHandler):
|
|
|
92
92
|
return_value, parent_span=parent_span)
|
|
93
93
|
except Exception as e:
|
|
94
94
|
logger.info(f"Failed to propagate fastapi response: {e}")
|
|
95
|
-
super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
95
|
+
super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value, token)
|
|
@@ -3,10 +3,10 @@ from monocle_apptrace.instrumentation.metamodel.fastapi.entities.http import FAS
|
|
|
3
3
|
|
|
4
4
|
FASTAPI_METHODS = [
|
|
5
5
|
{
|
|
6
|
-
"package": "fastapi",
|
|
7
|
-
"object": "
|
|
8
|
-
"method": "
|
|
9
|
-
"wrapper_method":
|
|
6
|
+
"package": "fastapi.routing",
|
|
7
|
+
"object": "APIRoute",
|
|
8
|
+
"method": "handle",
|
|
9
|
+
"wrapper_method": atask_wrapper,
|
|
10
10
|
"span_name": "fastapi.request",
|
|
11
11
|
"span_handler": "fastapi_handler",
|
|
12
12
|
"output_processor": FASTAPI_HTTP_PROCESSOR,
|
|
@@ -67,13 +67,13 @@ def flask_post_tracing(token):
|
|
|
67
67
|
class FlaskSpanHandler(SpanHandler):
|
|
68
68
|
|
|
69
69
|
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
70
|
-
return flask_pre_tracing(args)
|
|
70
|
+
return flask_pre_tracing(args), None
|
|
71
71
|
|
|
72
72
|
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token):
|
|
73
73
|
flask_post_tracing(token)
|
|
74
74
|
|
|
75
75
|
class FlaskResponseSpanHandler(SpanHandler):
|
|
76
|
-
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
76
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value, token):
|
|
77
77
|
try:
|
|
78
78
|
_parent_span_context = get_current()
|
|
79
79
|
if _parent_span_context is not None:
|
|
@@ -82,4 +82,4 @@ class FlaskResponseSpanHandler(SpanHandler):
|
|
|
82
82
|
self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, return_value, parent_span=parent_span)
|
|
83
83
|
except Exception as e:
|
|
84
84
|
logger.info(f"Failed to propogate flask response: {e}")
|
|
85
|
-
super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
85
|
+
super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value, token)
|
|
@@ -95,7 +95,7 @@ def update_span_from_llm_response(result, include_token_counts=False):
|
|
|
95
95
|
"total_tokens": getattr(result.usage, "total_tokens", 0),
|
|
96
96
|
} if include_token_counts else {}
|
|
97
97
|
# Add other metadata fields like finish_reason, etc.
|
|
98
|
-
return {**tokens
|
|
98
|
+
return {**tokens}
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def get_exception_status_code(exc):
|
|
@@ -5,6 +5,7 @@ from monocle_apptrace.instrumentation.metamodel.hugging_face import _helper
|
|
|
5
5
|
|
|
6
6
|
INFERENCE = {
|
|
7
7
|
"type": SPAN_TYPES.INFERENCE,
|
|
8
|
+
"subtype": lambda arguments: _helper.agent_inference_type(arguments),
|
|
8
9
|
"attributes": [
|
|
9
10
|
[
|
|
10
11
|
{
|
|
@@ -86,10 +87,6 @@ INFERENCE = {
|
|
|
86
87
|
"_comment": "finish type mapped from finish reason",
|
|
87
88
|
"attribute": "finish_type",
|
|
88
89
|
"accessor": lambda arguments: _helper.map_finish_reason_to_finish_type(_helper.extract_finish_reason(arguments))
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"attribute": "inference_sub_type",
|
|
92
|
-
"accessor": lambda arguments: _helper.agent_inference_type(arguments)
|
|
93
90
|
}
|
|
94
91
|
]
|
|
95
92
|
}
|
|
@@ -83,7 +83,7 @@ def lambda_func_post_tracing(token):
|
|
|
83
83
|
|
|
84
84
|
class lambdaSpanHandler(SpanHandler):
|
|
85
85
|
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
86
|
-
return lambda_func_pre_tracing(kwargs)
|
|
86
|
+
return lambda_func_pre_tracing(kwargs), None
|
|
87
87
|
|
|
88
88
|
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value,token):
|
|
89
89
|
lambda_func_post_tracing(token)
|
|
@@ -6,6 +6,7 @@ from monocle_apptrace.instrumentation.common.utils import get_error_message, res
|
|
|
6
6
|
|
|
7
7
|
INFERENCE = {
|
|
8
8
|
"type": SPAN_TYPES.INFERENCE_FRAMEWORK,
|
|
9
|
+
"subtype": lambda arguments: _helper.agent_inference_type(arguments),
|
|
9
10
|
"attributes": [
|
|
10
11
|
[
|
|
11
12
|
{
|
|
@@ -79,10 +80,6 @@ INFERENCE = {
|
|
|
79
80
|
"accessor": lambda arguments: _helper.map_finish_reason_to_finish_type(
|
|
80
81
|
_helper.extract_finish_reason(arguments)
|
|
81
82
|
)
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"attribute": "inference_sub_type",
|
|
85
|
-
"accessor": lambda arguments: _helper.agent_inference_type(arguments)
|
|
86
83
|
}
|
|
87
84
|
]
|
|
88
85
|
}
|
|
@@ -22,9 +22,19 @@ def agent_instructions(arguments):
|
|
|
22
22
|
else:
|
|
23
23
|
return arguments['kwargs']['agent'].instructions
|
|
24
24
|
|
|
25
|
+
def extract_request_agent_input(arguments):
|
|
26
|
+
if arguments['kwargs'] is not None and 'input' in arguments['kwargs']:
|
|
27
|
+
history = arguments['kwargs']['input']['messages']
|
|
28
|
+
messages = []
|
|
29
|
+
for message in history:
|
|
30
|
+
if 'content' in message and 'role' in message and message['role'] == "user": # Check if the message is a UserMessage
|
|
31
|
+
messages.append(message['content'])
|
|
32
|
+
return messages
|
|
33
|
+
return []
|
|
34
|
+
|
|
25
35
|
def extract_agent_input(arguments):
|
|
26
|
-
if arguments['
|
|
27
|
-
history = arguments['
|
|
36
|
+
if arguments['args'] is not None and len(arguments['args']) > 0 and 'messages' in arguments['args'][0]:
|
|
37
|
+
history = arguments['args'][0]['messages']
|
|
28
38
|
messages = []
|
|
29
39
|
for message in history:
|
|
30
40
|
if hasattr(message, 'content') and hasattr(message, 'type') and message.type == "human": # Check if the message is a HumanMessage
|
|
@@ -58,6 +68,10 @@ def update_span_from_llm_response(response):
|
|
|
58
68
|
def extract_tool_response(result):
|
|
59
69
|
if result is not None and hasattr(result, 'content'):
|
|
60
70
|
return result.content
|
|
71
|
+
if isinstance(result, str):
|
|
72
|
+
return result
|
|
73
|
+
if isinstance(result[0], str):
|
|
74
|
+
return result[0]
|
|
61
75
|
return None
|
|
62
76
|
|
|
63
77
|
def get_status(result):
|
|
@@ -66,11 +80,21 @@ def get_status(result):
|
|
|
66
80
|
return None
|
|
67
81
|
|
|
68
82
|
def extract_tool_input(arguments):
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return [tool_input]
|
|
83
|
+
if arguments['args'] and len(arguments['args']) > 0:
|
|
84
|
+
tool_input = arguments['args'][0]
|
|
72
85
|
else:
|
|
73
|
-
|
|
86
|
+
tool_input:dict = arguments['kwargs'].copy()
|
|
87
|
+
tool_input.pop('run_manager', None) # remove run_manager if exists
|
|
88
|
+
tool_input.pop('config', None) # remove config if exists
|
|
89
|
+
return str(tool_input)
|
|
90
|
+
|
|
91
|
+
# if isinstance(tool_input, str):
|
|
92
|
+
# return [tool_input]
|
|
93
|
+
# elif isinstance(tool_input, dict):
|
|
94
|
+
# # return array of key value pairs
|
|
95
|
+
# return [f"'{k}': '{str(v)}'" for k, v in tool_input.items()]
|
|
96
|
+
# else:
|
|
97
|
+
# return [str(tool_input)]
|
|
74
98
|
|
|
75
99
|
def get_name(instance):
|
|
76
100
|
return instance.name if hasattr(instance, 'name') else ""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from opentelemetry.context import set_value, attach, detach, get_value
|
|
2
3
|
from monocle_apptrace.instrumentation.common.constants import AGENT_PREFIX_KEY, SCOPE_NAME
|
|
3
4
|
from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
@@ -5,46 +6,114 @@ from monocle_apptrace.instrumentation.metamodel.langgraph._helper import (
|
|
|
5
6
|
DELEGATION_NAME_PREFIX, get_name, is_root_agent_name, is_delegation_tool, LANGGRAPTH_AGENT_NAME_KEY
|
|
6
7
|
)
|
|
7
8
|
from monocle_apptrace.instrumentation.metamodel.langgraph.entities.inference import (
|
|
8
|
-
AGENT_DELEGATION, AGENT_REQUEST
|
|
9
|
+
AGENT_DELEGATION, AGENT_REQUEST, AGENT
|
|
9
10
|
)
|
|
10
11
|
from monocle_apptrace.instrumentation.common.scope_wrapper import start_scope, stop_scope
|
|
12
|
+
from monocle_apptrace.instrumentation.common.utils import is_scope_set
|
|
13
|
+
try:
|
|
14
|
+
from langgraph.errors import ParentCommand
|
|
15
|
+
except ImportError:
|
|
16
|
+
ParentCommand = None
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
class ParentCommandFilterSpan:
|
|
21
|
+
"""A wrapper for spans that filters out ParentCommand exceptions from being recorded."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, span):
|
|
24
|
+
self.span = span
|
|
25
|
+
self.original_record_exception = span.record_exception
|
|
26
|
+
|
|
27
|
+
def record_exception(self, exception, attributes=None, timestamp=None, escaped=False):
|
|
28
|
+
"""Filter out ParentCommand exceptions before recording them."""
|
|
29
|
+
try:
|
|
30
|
+
# Check if this is a ParentCommand exception
|
|
31
|
+
if ParentCommand is not None and isinstance(exception, ParentCommand):
|
|
32
|
+
logger.debug("Filtering out ParentCommand exception from span recording")
|
|
33
|
+
return # Don't record ParentCommand exceptions
|
|
34
|
+
|
|
35
|
+
# For all other exceptions, use the original record_exception method
|
|
36
|
+
return self.original_record_exception(exception, attributes, timestamp, escaped)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.debug(f"Error in ParentCommand filtering: {e}")
|
|
39
|
+
# If filtering fails, fall back to original behavior
|
|
40
|
+
return self.original_record_exception(exception, attributes, timestamp, escaped)
|
|
11
41
|
|
|
12
42
|
class LanggraphAgentHandler(SpanHandler):
|
|
13
43
|
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
14
44
|
context = set_value(LANGGRAPTH_AGENT_NAME_KEY, get_name(instance))
|
|
15
45
|
context = set_value(AGENT_PREFIX_KEY, DELEGATION_NAME_PREFIX, context)
|
|
16
46
|
scope_name = AGENT_REQUEST.get("type")
|
|
17
|
-
if
|
|
18
|
-
|
|
47
|
+
if not is_scope_set(scope_name):
|
|
48
|
+
agent_request_wrapper = to_wrap.copy()
|
|
49
|
+
agent_request_wrapper["output_processor"] = AGENT_REQUEST
|
|
50
|
+
# return start_scope(scope_name, scope_value=None, context=context)
|
|
51
|
+
return attach(context), agent_request_wrapper
|
|
19
52
|
else:
|
|
20
|
-
return attach(context)
|
|
53
|
+
return attach(context), None
|
|
21
54
|
|
|
22
55
|
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, result, token):
|
|
23
56
|
if token is not None:
|
|
24
57
|
detach(token)
|
|
25
58
|
|
|
59
|
+
def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span):
|
|
60
|
+
"""Apply ParentCommand filtering to the span before task execution."""
|
|
61
|
+
# Apply ParentCommand filtering to this span
|
|
62
|
+
self._apply_parent_command_filtering(span)
|
|
63
|
+
super().post_task_processing(to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span)
|
|
64
|
+
|
|
65
|
+
def _apply_parent_command_filtering(self, span):
|
|
66
|
+
"""Apply ParentCommand exception filtering to a span."""
|
|
67
|
+
try:
|
|
68
|
+
if hasattr(span, 'record_exception'):
|
|
69
|
+
# Create a filtered wrapper and replace the record_exception method
|
|
70
|
+
filter_wrapper = ParentCommandFilterSpan(span)
|
|
71
|
+
span.record_exception = filter_wrapper.record_exception
|
|
72
|
+
logger.debug("Applied ParentCommand filtering to LangGraph agent span")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.debug(f"Failed to apply ParentCommand filtering: {e}")
|
|
75
|
+
|
|
26
76
|
# In multi agent scenarios, the root agent is the one that orchestrates the other agents. LangGraph generates an extra root level invoke()
|
|
27
77
|
# call on top of the supervisor agent invoke().
|
|
28
78
|
# This span handler resets the parent invoke call as generic type to avoid duplicate attributes/events in supervisor span and this root span.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
parent_span.set_attribute("parent.agent.span", True)
|
|
37
|
-
return super().hydrate_span(agent_request_wrapper, wrapped, instance, args, kwargs, result, span, parent_span, ex)
|
|
79
|
+
|
|
80
|
+
def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span, parent_span = None, ex:Exception = None, is_post_exec:bool= False) -> bool:
|
|
81
|
+
# Filter out ParentCommand exceptions as they are LangGraph control flow mechanisms, not actual errors
|
|
82
|
+
if ParentCommand is not None and isinstance(ex, ParentCommand):
|
|
83
|
+
ex = None # Suppress the ParentCommand exception from being recorded
|
|
84
|
+
|
|
85
|
+
return super().hydrate_span(to_wrap, wrapped, instance, args, kwargs, result, span, parent_span, ex, is_post_exec)
|
|
38
86
|
|
|
39
87
|
class LanggraphToolHandler(SpanHandler):
|
|
40
|
-
|
|
41
|
-
# Hence we usea different output processor for tool invoke() to format the span as agentic.delegation.
|
|
42
|
-
def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span, parent_span = None, ex:Exception = None) -> bool:
|
|
88
|
+
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
43
89
|
if is_delegation_tool(instance):
|
|
44
90
|
agent_request_wrapper = to_wrap.copy()
|
|
45
91
|
agent_request_wrapper["output_processor"] = AGENT_DELEGATION
|
|
46
92
|
else:
|
|
47
|
-
agent_request_wrapper =
|
|
93
|
+
agent_request_wrapper = None
|
|
94
|
+
return None, agent_request_wrapper
|
|
95
|
+
|
|
96
|
+
# LangGraph uses an internal tool to initate delegation to other agents. The method is tool invoke() with tool name as `transfer_to_<agent_name>`.
|
|
97
|
+
# Hence we usea different output processor for tool invoke() to format the span as agentic.delegation.
|
|
98
|
+
def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span):
|
|
99
|
+
"""Apply ParentCommand filtering to the span before task execution."""
|
|
100
|
+
# Apply ParentCommand filtering to this span
|
|
101
|
+
self._apply_parent_command_filtering(span)
|
|
102
|
+
super().post_task_processing(to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span)
|
|
103
|
+
|
|
104
|
+
def _apply_parent_command_filtering(self, span):
|
|
105
|
+
"""Apply ParentCommand exception filtering to a span."""
|
|
106
|
+
try:
|
|
107
|
+
if hasattr(span, 'record_exception'):
|
|
108
|
+
# Create a filtered wrapper and replace the record_exception method
|
|
109
|
+
filter_wrapper = ParentCommandFilterSpan(span)
|
|
110
|
+
span.record_exception = filter_wrapper.record_exception
|
|
111
|
+
logger.debug("Applied ParentCommand filtering to LangGraph tool span")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.debug(f"Failed to apply ParentCommand filtering: {e}")
|
|
48
114
|
|
|
49
|
-
|
|
50
|
-
|
|
115
|
+
def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span, parent_span = None, ex:Exception = None, is_post_exec:bool= False) -> bool:
|
|
116
|
+
# Filter out ParentCommand exceptions as they are LangGraph control flow mechanisms, not actual errors
|
|
117
|
+
if ParentCommand is not None and isinstance(ex, ParentCommand):
|
|
118
|
+
ex = None # Suppress the ParentCommand exception from being recorded
|
|
119
|
+
return super().hydrate_span(to_wrap, wrapped, instance, args, kwargs, result, span, parent_span, ex, is_post_exec)
|
|
@@ -24,17 +24,33 @@ LANGGRAPH_METHODS = [
|
|
|
24
24
|
"output_processor": AGENT,
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
"package": "langchain_core.tools.
|
|
28
|
-
"object": "
|
|
29
|
-
"method": "
|
|
27
|
+
"package": "langchain_core.tools.simple",
|
|
28
|
+
"object": "Tool",
|
|
29
|
+
"method": "_run",
|
|
30
30
|
"wrapper_method": task_wrapper,
|
|
31
31
|
"span_handler": "langgraph_tool_handler",
|
|
32
32
|
"output_processor": TOOLS,
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
"package": "langchain_core.tools.
|
|
36
|
-
"object": "
|
|
37
|
-
"method": "
|
|
35
|
+
"package": "langchain_core.tools.structured",
|
|
36
|
+
"object": "StructuredTool",
|
|
37
|
+
"method": "_run",
|
|
38
|
+
"wrapper_method": task_wrapper,
|
|
39
|
+
"span_handler": "langgraph_tool_handler",
|
|
40
|
+
"output_processor": TOOLS,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"package": "langchain_core.tools.simple",
|
|
44
|
+
"object": "Tool",
|
|
45
|
+
"method": "_arun",
|
|
46
|
+
"wrapper_method": atask_wrapper,
|
|
47
|
+
"span_handler": "langgraph_tool_handler",
|
|
48
|
+
"output_processor": TOOLS,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"package": "langchain_core.tools.structured",
|
|
52
|
+
"object": "StructuredTool",
|
|
53
|
+
"method": "_arun",
|
|
38
54
|
"wrapper_method": atask_wrapper,
|
|
39
55
|
"span_handler": "langgraph_tool_handler",
|
|
40
56
|
"output_processor": TOOLS,
|
|
@@ -20,6 +20,11 @@ from monocle_apptrace.instrumentation.common.utils import (
|
|
|
20
20
|
from monocle_apptrace.instrumentation.metamodel.finish_types import map_llamaindex_finish_reason_to_finish_type
|
|
21
21
|
|
|
22
22
|
LLAMAINDEX_AGENT_NAME_KEY = "_active_agent_name"
|
|
23
|
+
|
|
24
|
+
# Thread-local storage for current agent context
|
|
25
|
+
import threading
|
|
26
|
+
_thread_local = threading.local()
|
|
27
|
+
|
|
23
28
|
logger = logging.getLogger(__name__)
|
|
24
29
|
|
|
25
30
|
def get_status(result):
|
|
@@ -64,18 +69,18 @@ def get_tool_description(arguments):
|
|
|
64
69
|
return ""
|
|
65
70
|
|
|
66
71
|
def extract_tool_args(arguments):
|
|
67
|
-
tool_args =
|
|
72
|
+
tool_args = {}
|
|
68
73
|
if len(arguments['args']) > 1:
|
|
69
74
|
for key, value in arguments['args'][2].items():
|
|
70
75
|
# check if value is builtin type or a string
|
|
71
76
|
if value is not None and isinstance(value, (str, int, float, bool)):
|
|
72
|
-
tool_args
|
|
77
|
+
tool_args[key] = value
|
|
73
78
|
else:
|
|
74
79
|
for key, value in arguments['kwargs'].items():
|
|
75
80
|
# check if value is builtin type or a string
|
|
76
81
|
if value is not None and isinstance(value, (str, int, float, bool)):
|
|
77
|
-
tool_args
|
|
78
|
-
return
|
|
82
|
+
tool_args[key] = value
|
|
83
|
+
return get_json_dumps(tool_args)
|
|
79
84
|
|
|
80
85
|
def extract_tool_response(response):
|
|
81
86
|
if hasattr(response, 'raw_output'):
|
|
@@ -96,12 +101,27 @@ def get_agent_description(instance) -> str:
|
|
|
96
101
|
return instance.description
|
|
97
102
|
return ""
|
|
98
103
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
def get_name(instance):
|
|
105
|
+
return instance.name if hasattr(instance, 'name') else ""
|
|
106
|
+
|
|
107
|
+
def set_current_agent(agent_name: str):
|
|
108
|
+
"""Set the current agent name in thread-local storage."""
|
|
109
|
+
_thread_local.current_agent = agent_name
|
|
110
|
+
|
|
111
|
+
def get_current_agent() -> str:
|
|
112
|
+
"""Get the current agent name from thread-local storage."""
|
|
113
|
+
return getattr(_thread_local, 'current_agent', '')
|
|
114
|
+
|
|
115
|
+
def get_source_agent() -> str:
|
|
116
|
+
"""Get the name of the agent that initiated the request."""
|
|
117
|
+
source_agent = get_value(LLAMAINDEX_AGENT_NAME_KEY)
|
|
118
|
+
if source_agent is not None and isinstance(source_agent,str) and source_agent != "":
|
|
119
|
+
return source_agent
|
|
120
|
+
|
|
121
|
+
source_agent = get_current_agent()
|
|
122
|
+
if source_agent:
|
|
123
|
+
return source_agent
|
|
124
|
+
return ""
|
|
105
125
|
|
|
106
126
|
def get_target_agent(results) -> str:
|
|
107
127
|
if hasattr(results, 'raw_input'):
|
|
@@ -111,7 +111,7 @@ TOOLS = {
|
|
|
111
111
|
{
|
|
112
112
|
"_comment": "name of the agent",
|
|
113
113
|
"attribute": "name",
|
|
114
|
-
"accessor": lambda arguments: _helper.get_source_agent(
|
|
114
|
+
"accessor": lambda arguments: _helper.get_source_agent()
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"_comment": "agent type",
|
|
@@ -157,12 +157,13 @@ AGENT_DELEGATION = {
|
|
|
157
157
|
{
|
|
158
158
|
"_comment": "name of the agent",
|
|
159
159
|
"attribute": "from_agent",
|
|
160
|
-
"accessor": lambda arguments: _helper.get_source_agent(
|
|
160
|
+
"accessor": lambda arguments: _helper.get_source_agent()
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"_comment": "name of the agent called",
|
|
164
164
|
"attribute": "to_agent",
|
|
165
|
-
"accessor": lambda arguments: _helper.get_target_agent(arguments['result'])
|
|
165
|
+
"accessor": lambda arguments: _helper.get_target_agent(arguments['result']),
|
|
166
|
+
"phase": "post_execution"
|
|
166
167
|
}
|
|
167
168
|
]
|
|
168
169
|
]
|