monocle-apptrace 0.4.2__py3-none-any.whl → 0.5.0__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/__main__.py +1 -1
- monocle_apptrace/exporters/file_exporter.py +125 -37
- monocle_apptrace/instrumentation/common/__init__.py +16 -1
- monocle_apptrace/instrumentation/common/constants.py +14 -1
- monocle_apptrace/instrumentation/common/instrumentor.py +19 -152
- monocle_apptrace/instrumentation/common/method_wrappers.py +376 -0
- monocle_apptrace/instrumentation/common/span_handler.py +58 -32
- monocle_apptrace/instrumentation/common/utils.py +52 -15
- monocle_apptrace/instrumentation/common/wrapper.py +124 -18
- monocle_apptrace/instrumentation/common/wrapper_method.py +47 -1
- monocle_apptrace/instrumentation/metamodel/a2a/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/a2a/_helper.py +37 -0
- monocle_apptrace/instrumentation/metamodel/a2a/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +112 -0
- monocle_apptrace/instrumentation/metamodel/a2a/methods.py +22 -0
- monocle_apptrace/instrumentation/metamodel/adk/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/adk/_helper.py +182 -0
- monocle_apptrace/instrumentation/metamodel/adk/entities/agent.py +50 -0
- monocle_apptrace/instrumentation/metamodel/adk/entities/tool.py +57 -0
- monocle_apptrace/instrumentation/metamodel/adk/methods.py +24 -0
- monocle_apptrace/instrumentation/metamodel/agents/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/agents/_helper.py +220 -0
- monocle_apptrace/instrumentation/metamodel/agents/agents_processor.py +152 -0
- monocle_apptrace/instrumentation/metamodel/agents/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +191 -0
- monocle_apptrace/instrumentation/metamodel/agents/methods.py +56 -0
- monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +6 -11
- monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +112 -18
- monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +18 -10
- monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +13 -11
- monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +5 -0
- monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +88 -8
- monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +22 -8
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +92 -16
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +13 -8
- monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +1 -1
- monocle_apptrace/instrumentation/metamodel/fastapi/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/_helper.py +82 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +44 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +23 -0
- monocle_apptrace/instrumentation/metamodel/finish_types.py +463 -0
- monocle_apptrace/instrumentation/metamodel/flask/_helper.py +6 -11
- monocle_apptrace/instrumentation/metamodel/gemini/_helper.py +51 -7
- monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +22 -11
- monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +43 -0
- monocle_apptrace/instrumentation/metamodel/gemini/methods.py +18 -1
- monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +79 -8
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +15 -10
- monocle_apptrace/instrumentation/metamodel/haystack/methods.py +7 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/_helper.py +78 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +51 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/methods.py +23 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/wrapper.py +23 -0
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +145 -19
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +19 -10
- monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +67 -10
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +127 -20
- monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +46 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +35 -9
- monocle_apptrace/instrumentation/metamodel/litellm/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/litellm/_helper.py +89 -0
- monocle_apptrace/instrumentation/metamodel/litellm/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/litellm/entities/inference.py +108 -0
- monocle_apptrace/instrumentation/metamodel/litellm/methods.py +19 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +227 -16
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +127 -10
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +13 -8
- monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +62 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +68 -1
- monocle_apptrace/instrumentation/metamodel/mcp/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +118 -0
- monocle_apptrace/instrumentation/metamodel/mcp/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +48 -0
- monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +8 -0
- monocle_apptrace/instrumentation/metamodel/mcp/methods.py +21 -0
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +188 -16
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +148 -92
- monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +1 -1
- monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +53 -23
- monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +1 -1
- monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +15 -9
- monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +0 -4
- {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/METADATA +27 -11
- {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/RECORD +88 -47
- {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
from typing import Any, Dict, Optional
|
|
3
4
|
from urllib.parse import urlparse
|
|
4
5
|
from monocle_apptrace.instrumentation.common.utils import (
|
|
5
|
-
|
|
6
|
+
get_json_dumps,
|
|
6
7
|
get_exception_message,
|
|
8
|
+
get_status_code,
|
|
7
9
|
)
|
|
10
|
+
from monocle_apptrace.instrumentation.metamodel.finish_types import map_azure_ai_inference_finish_reason_to_finish_type
|
|
8
11
|
|
|
9
12
|
logger = logging.getLogger(__name__)
|
|
10
13
|
|
|
@@ -22,7 +25,7 @@ def extract_messages(args_or_kwargs: Any) -> str:
|
|
|
22
25
|
if msg.get("content") and msg.get("role"):
|
|
23
26
|
messages.append({msg["role"]: msg["content"]})
|
|
24
27
|
|
|
25
|
-
return [
|
|
28
|
+
return [get_json_dumps(message) for message in messages]
|
|
26
29
|
except Exception as e:
|
|
27
30
|
logger.warning("Warning: Error occurred in extract_messages: %s", str(e))
|
|
28
31
|
return []
|
|
@@ -83,20 +86,29 @@ def extract_assistant_message(arguments: Dict[str, Any]) -> str:
|
|
|
83
86
|
return get_exception_message(arguments)
|
|
84
87
|
|
|
85
88
|
result = arguments.get("result")
|
|
89
|
+
role = "assistant"
|
|
90
|
+
messages = []
|
|
86
91
|
if not result:
|
|
87
92
|
return ""
|
|
88
93
|
if hasattr(result, "output_text"):
|
|
89
94
|
# If the result has output_text attribute
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
role = getattr(result, "role", role)
|
|
96
|
+
if "assistant" in role.lower():
|
|
97
|
+
# If the role is assistant, we can assume it's a chat completion
|
|
98
|
+
role = "assistant"
|
|
99
|
+
messages.append({role: result.output_text})
|
|
100
|
+
if (hasattr(result, "choices")
|
|
101
|
+
and result.choices
|
|
93
102
|
and result.choices[0].message
|
|
94
103
|
and result.choices[0].message.content
|
|
95
104
|
):
|
|
105
|
+
role = getattr(result.choices[0].message, "role", role)
|
|
106
|
+
if "assistant" in role.lower():
|
|
107
|
+
# If the role is assistant, we can assume it's a chat completion
|
|
108
|
+
role = "assistant"
|
|
96
109
|
# If the result is a chat completion with content
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return str(result)
|
|
110
|
+
messages.append({role: result.choices[0].message.content})
|
|
111
|
+
return get_json_dumps(messages[0]) if messages else ""
|
|
100
112
|
except Exception as e:
|
|
101
113
|
logger.warning(
|
|
102
114
|
"Warning: Error occurred in extract_assistant_message: %s", str(e)
|
|
@@ -214,3 +226,71 @@ def get_provider_name(instance: Any) -> str:
|
|
|
214
226
|
except Exception as e:
|
|
215
227
|
logger.warning("Warning: Error occurred in get_provider_name: %s", str(e))
|
|
216
228
|
return "azure_ai_inference"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def extract_finish_reason(arguments: Dict[str, Any]) -> Optional[str]:
|
|
232
|
+
"""Extract finish_reason from Azure AI Inference response."""
|
|
233
|
+
try:
|
|
234
|
+
# Handle exception cases first
|
|
235
|
+
if arguments.get("exception") is not None:
|
|
236
|
+
ex = arguments["exception"]
|
|
237
|
+
if hasattr(ex, "message") and isinstance(ex.message, str):
|
|
238
|
+
message = ex.message
|
|
239
|
+
if "content_filter" in message.lower():
|
|
240
|
+
return "content_filter"
|
|
241
|
+
return "error"
|
|
242
|
+
|
|
243
|
+
result = arguments.get("result")
|
|
244
|
+
if result is None:
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
# Check various possible locations for finish_reason in Azure AI Inference responses
|
|
248
|
+
|
|
249
|
+
# Direct finish_reason attribute
|
|
250
|
+
if hasattr(result, "finish_reason") and result.finish_reason:
|
|
251
|
+
return result.finish_reason
|
|
252
|
+
|
|
253
|
+
# Check for choices structure (OpenAI-compatible format)
|
|
254
|
+
if hasattr(result, "choices") and result.choices:
|
|
255
|
+
choice = result.choices[0]
|
|
256
|
+
if hasattr(choice, "finish_reason") and choice.finish_reason:
|
|
257
|
+
return choice.finish_reason
|
|
258
|
+
|
|
259
|
+
# Check for additional metadata or response attributes
|
|
260
|
+
if hasattr(result, "additional_kwargs") and result.additional_kwargs:
|
|
261
|
+
kwargs = result.additional_kwargs
|
|
262
|
+
if isinstance(kwargs, dict):
|
|
263
|
+
for key in ["finish_reason", "stop_reason"]:
|
|
264
|
+
if key in kwargs:
|
|
265
|
+
return kwargs[key]
|
|
266
|
+
|
|
267
|
+
# Check for response metadata
|
|
268
|
+
if hasattr(result, "response_metadata") and result.response_metadata:
|
|
269
|
+
metadata = result.response_metadata
|
|
270
|
+
if isinstance(metadata, dict):
|
|
271
|
+
for key in ["finish_reason", "stop_reason"]:
|
|
272
|
+
if key in metadata:
|
|
273
|
+
return metadata[key]
|
|
274
|
+
|
|
275
|
+
# Check for streaming response with accumulated finish reason
|
|
276
|
+
if hasattr(result, "type") and result.type == "stream":
|
|
277
|
+
# For streaming responses, default to stop if completed successfully
|
|
278
|
+
return "stop"
|
|
279
|
+
|
|
280
|
+
# If no specific finish reason found, infer from status
|
|
281
|
+
status_code = get_status_code(arguments)
|
|
282
|
+
if status_code == 'success':
|
|
283
|
+
return "stop" # Default success finish reason
|
|
284
|
+
elif status_code == 'error':
|
|
285
|
+
return "error"
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.warning("Warning: Error occurred in extract_finish_reason: %s", str(e))
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def map_finish_reason_to_finish_type(finish_reason: Optional[str]) -> Optional[str]:
|
|
295
|
+
"""Map Azure AI Inference finish_reason to finish_type."""
|
|
296
|
+
return map_azure_ai_inference_finish_reason_to_finish_type(finish_reason)
|
|
@@ -3,6 +3,7 @@ import time
|
|
|
3
3
|
from types import SimpleNamespace
|
|
4
4
|
from monocle_apptrace.instrumentation.metamodel.azureaiinference import _helper
|
|
5
5
|
from monocle_apptrace.instrumentation.common.utils import (
|
|
6
|
+
get_error_message,
|
|
6
7
|
resolve_from_alias,
|
|
7
8
|
patch_instance_method,
|
|
8
9
|
get_status,
|
|
@@ -20,13 +21,14 @@ def process_stream(to_wrap, response, span_processor):
|
|
|
20
21
|
stream_closed_time = None
|
|
21
22
|
accumulated_response = ""
|
|
22
23
|
token_usage = None
|
|
24
|
+
role = "assistant"
|
|
23
25
|
|
|
24
26
|
# For sync iteration - patch __next__ instead of __iter__
|
|
25
27
|
if to_wrap and hasattr(response, "__next__"):
|
|
26
28
|
original_next = response.__next__
|
|
27
29
|
|
|
28
30
|
def new_next(self):
|
|
29
|
-
nonlocal waiting_for_first_token, first_token_time, stream_closed_time, accumulated_response, token_usage
|
|
31
|
+
nonlocal waiting_for_first_token, first_token_time, stream_closed_time, accumulated_response, token_usage, role
|
|
30
32
|
|
|
31
33
|
try:
|
|
32
34
|
item = original_next()
|
|
@@ -34,6 +36,8 @@ def process_stream(to_wrap, response, span_processor):
|
|
|
34
36
|
# Handle Azure AI Inference streaming chunks
|
|
35
37
|
if hasattr(item, 'choices') and item.choices:
|
|
36
38
|
choice = item.choices[0]
|
|
39
|
+
if hasattr(choice, 'delta') and hasattr(choice.delta, 'role') and choice.delta.role:
|
|
40
|
+
role = choice.delta.role
|
|
37
41
|
if hasattr(choice, 'delta') and hasattr(choice.delta, 'content') and choice.delta.content:
|
|
38
42
|
if waiting_for_first_token:
|
|
39
43
|
waiting_for_first_token = False
|
|
@@ -53,6 +57,7 @@ def process_stream(to_wrap, response, span_processor):
|
|
|
53
57
|
if span_processor:
|
|
54
58
|
ret_val = SimpleNamespace(
|
|
55
59
|
type="stream",
|
|
60
|
+
role=role,
|
|
56
61
|
timestamps={
|
|
57
62
|
"data.input": int(stream_start_time),
|
|
58
63
|
"data.output": int(first_token_time),
|
|
@@ -77,7 +82,7 @@ def process_stream(to_wrap, response, span_processor):
|
|
|
77
82
|
original_anext = response.__anext__
|
|
78
83
|
|
|
79
84
|
async def new_anext(self):
|
|
80
|
-
nonlocal waiting_for_first_token, first_token_time, stream_closed_time, accumulated_response, token_usage
|
|
85
|
+
nonlocal waiting_for_first_token, first_token_time, stream_closed_time, accumulated_response, token_usage, role
|
|
81
86
|
|
|
82
87
|
try:
|
|
83
88
|
item = await original_anext()
|
|
@@ -85,6 +90,8 @@ def process_stream(to_wrap, response, span_processor):
|
|
|
85
90
|
# Handle Azure AI Inference streaming chunks
|
|
86
91
|
if hasattr(item, 'choices') and item.choices:
|
|
87
92
|
choice = item.choices[0]
|
|
93
|
+
if hasattr(choice, 'delta') and hasattr(choice.delta, 'role') and choice.delta.role:
|
|
94
|
+
role = choice.delta.role
|
|
88
95
|
if hasattr(choice, 'delta') and hasattr(choice.delta, 'content') and choice.delta.content:
|
|
89
96
|
if waiting_for_first_token:
|
|
90
97
|
waiting_for_first_token = False
|
|
@@ -104,6 +111,7 @@ def process_stream(to_wrap, response, span_processor):
|
|
|
104
111
|
if span_processor:
|
|
105
112
|
ret_val = SimpleNamespace(
|
|
106
113
|
type="stream",
|
|
114
|
+
role=role,
|
|
107
115
|
timestamps={
|
|
108
116
|
"data.input": int(stream_start_time),
|
|
109
117
|
"data.output": int(first_token_time),
|
|
@@ -183,12 +191,8 @@ INFERENCE = {
|
|
|
183
191
|
"accessor": lambda arguments: _helper.extract_assistant_message(arguments)
|
|
184
192
|
},
|
|
185
193
|
{
|
|
186
|
-
"attribute": "
|
|
187
|
-
"accessor": lambda arguments:
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"attribute": "status_code",
|
|
191
|
-
"accessor": lambda arguments: get_exception_status_code(arguments)
|
|
194
|
+
"attribute": "error_code",
|
|
195
|
+
"accessor": lambda arguments: get_error_message(arguments)
|
|
192
196
|
}
|
|
193
197
|
]
|
|
194
198
|
},
|
|
@@ -201,6 +205,16 @@ INFERENCE = {
|
|
|
201
205
|
arguments['result'],
|
|
202
206
|
arguments.get('instance')
|
|
203
207
|
)
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"attribute": "finish_reason",
|
|
211
|
+
"accessor": lambda arguments: _helper.extract_finish_reason(arguments)
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"attribute": "finish_type",
|
|
215
|
+
"accessor": lambda arguments: _helper.map_finish_reason_to_finish_type(
|
|
216
|
+
_helper.extract_finish_reason(arguments)
|
|
217
|
+
)
|
|
204
218
|
}
|
|
205
219
|
]
|
|
206
220
|
}
|
|
@@ -8,7 +8,8 @@ import json
|
|
|
8
8
|
from io import BytesIO
|
|
9
9
|
from functools import wraps
|
|
10
10
|
from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
11
|
-
from monocle_apptrace.instrumentation.common.utils import ( get_exception_message,)
|
|
11
|
+
from monocle_apptrace.instrumentation.common.utils import ( get_exception_message, get_json_dumps, get_status_code,)
|
|
12
|
+
from monocle_apptrace.instrumentation.metamodel.finish_types import map_bedrock_finish_reason_to_finish_type
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
14
15
|
|
|
@@ -25,7 +26,7 @@ def extract_messages(args):
|
|
|
25
26
|
role = args['messages'][0]['role']
|
|
26
27
|
user_message = extract_query_from_content(args['messages'][0]['content'][0]['text'])
|
|
27
28
|
messages.append({role: user_message})
|
|
28
|
-
return [
|
|
29
|
+
return [get_json_dumps(message) for message in messages]
|
|
29
30
|
except Exception as e:
|
|
30
31
|
logger.warning("Warning: Error occurred in extract_messages: %s", str(e))
|
|
31
32
|
return []
|
|
@@ -39,18 +40,11 @@ def get_exception_status_code(arguments):
|
|
|
39
40
|
else:
|
|
40
41
|
return 'success'
|
|
41
42
|
|
|
42
|
-
def get_status_code(arguments):
|
|
43
|
-
if arguments["exception"] is not None:
|
|
44
|
-
return get_exception_status_code(arguments)
|
|
45
|
-
elif hasattr(arguments["result"], "status"):
|
|
46
|
-
return arguments["result"].status
|
|
47
|
-
else:
|
|
48
|
-
return 'success'
|
|
49
|
-
|
|
50
43
|
def extract_assistant_message(arguments):
|
|
51
44
|
try:
|
|
52
45
|
status = get_status_code(arguments)
|
|
53
|
-
|
|
46
|
+
messages = []
|
|
47
|
+
role = "assistant"
|
|
54
48
|
if status == 'success':
|
|
55
49
|
if "Body" in arguments['result'] and hasattr(arguments['result']['Body'], "_raw_stream"):
|
|
56
50
|
raw_stream = getattr(arguments['result']['Body'], "_raw_stream")
|
|
@@ -59,20 +53,20 @@ def extract_assistant_message(arguments):
|
|
|
59
53
|
response_str = response_bytes.decode('utf-8')
|
|
60
54
|
response_dict = json.loads(response_str)
|
|
61
55
|
arguments['result']['Body'] = BytesIO(response_bytes)
|
|
62
|
-
|
|
56
|
+
messages.append({role: response_dict["answer"]})
|
|
63
57
|
if "output" in arguments['result']:
|
|
64
58
|
output = arguments['result'].get("output", {})
|
|
65
59
|
message = output.get("message", {})
|
|
66
60
|
content = message.get("content", [])
|
|
67
61
|
if isinstance(content, list) and len(content) > 0 and "text" in content[0]:
|
|
68
62
|
reply = content[0]["text"]
|
|
69
|
-
|
|
63
|
+
messages.append({role: reply})
|
|
70
64
|
else:
|
|
71
65
|
if arguments["exception"] is not None:
|
|
72
|
-
|
|
66
|
+
return get_exception_message(arguments)
|
|
73
67
|
elif hasattr(arguments["result"], "error"):
|
|
74
|
-
|
|
75
|
-
return
|
|
68
|
+
return arguments["result"].error
|
|
69
|
+
return get_json_dumps(messages[0]) if messages else ""
|
|
76
70
|
except Exception as e:
|
|
77
71
|
logger.warning("Warning: Error occurred in extract_assistant_message: %s", str(e))
|
|
78
72
|
return []
|
|
@@ -118,3 +112,85 @@ def update_span_from_llm_response(response, instance):
|
|
|
118
112
|
meta_dict.update({"prompt_tokens": resolve_from_alias(token_usage,["prompt_tokens","input_tokens","inputTokens"])})
|
|
119
113
|
meta_dict.update({"total_tokens": resolve_from_alias(token_usage,["total_tokens","totalTokens"])})
|
|
120
114
|
return meta_dict
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def extract_finish_reason(arguments):
|
|
118
|
+
"""Extract finish_reason/stopReason from Bedrock response."""
|
|
119
|
+
try:
|
|
120
|
+
# Handle exception cases first
|
|
121
|
+
if arguments.get("exception") is not None:
|
|
122
|
+
return "error"
|
|
123
|
+
|
|
124
|
+
result = arguments.get("result")
|
|
125
|
+
if result is None:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
# Check various possible locations for finish_reason in Bedrock responses
|
|
129
|
+
|
|
130
|
+
# Direct stopReason attribute (Bedrock Converse API)
|
|
131
|
+
if "stopReason" in result:
|
|
132
|
+
return result["stopReason"]
|
|
133
|
+
|
|
134
|
+
# Check for completionReason (some Bedrock models)
|
|
135
|
+
if "completionReason" in result:
|
|
136
|
+
return result["completionReason"]
|
|
137
|
+
|
|
138
|
+
# Check for output structure (Bedrock Converse API)
|
|
139
|
+
if "output" in result and "message" in result["output"]:
|
|
140
|
+
message = result["output"]["message"]
|
|
141
|
+
if "stopReason" in message:
|
|
142
|
+
return message["stopReason"]
|
|
143
|
+
|
|
144
|
+
# Check for nested result structure
|
|
145
|
+
if "result" in result:
|
|
146
|
+
nested_result = result["result"]
|
|
147
|
+
if "stopReason" in nested_result:
|
|
148
|
+
return nested_result["stopReason"]
|
|
149
|
+
if "completionReason" in nested_result:
|
|
150
|
+
return nested_result["completionReason"]
|
|
151
|
+
|
|
152
|
+
# Check for streaming response accumulated finish reason
|
|
153
|
+
if "type" in result and result["type"] == "stream":
|
|
154
|
+
if "stopReason" in result:
|
|
155
|
+
return result["stopReason"]
|
|
156
|
+
|
|
157
|
+
# Check for response metadata
|
|
158
|
+
if "ResponseMetadata" in result:
|
|
159
|
+
metadata = result["ResponseMetadata"]
|
|
160
|
+
if "stopReason" in metadata:
|
|
161
|
+
return metadata["stopReason"]
|
|
162
|
+
|
|
163
|
+
# Check for Body content (for some Bedrock responses)
|
|
164
|
+
if "Body" in result:
|
|
165
|
+
body = result["Body"]
|
|
166
|
+
if hasattr(body, "_raw_stream"):
|
|
167
|
+
raw_stream = getattr(body, "_raw_stream")
|
|
168
|
+
if hasattr(raw_stream, "data"):
|
|
169
|
+
response_bytes = getattr(raw_stream, "data")
|
|
170
|
+
response_str = response_bytes.decode('utf-8')
|
|
171
|
+
try:
|
|
172
|
+
response_dict = json.loads(response_str)
|
|
173
|
+
if "stopReason" in response_dict:
|
|
174
|
+
return response_dict["stopReason"]
|
|
175
|
+
if "completionReason" in response_dict:
|
|
176
|
+
return response_dict["completionReason"]
|
|
177
|
+
except json.JSONDecodeError:
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
# If no specific finish reason found, infer from status
|
|
181
|
+
status_code = get_status_code(arguments)
|
|
182
|
+
if status_code == 'success':
|
|
183
|
+
return "end_turn" # Default successful completion
|
|
184
|
+
elif status_code == 'error':
|
|
185
|
+
return "error"
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.warning("Warning: Error occurred in extract_finish_reason: %s", str(e))
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def map_finish_reason_to_finish_type(finish_reason):
|
|
195
|
+
"""Map Bedrock finish_reason/stopReason to finish_type."""
|
|
196
|
+
return map_bedrock_finish_reason_to_finish_type(finish_reason)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from monocle_apptrace.instrumentation.metamodel.botocore import (
|
|
2
2
|
_helper,
|
|
3
3
|
)
|
|
4
|
-
from monocle_apptrace.instrumentation.common.utils import (get_llm_type, get_status,)
|
|
4
|
+
from monocle_apptrace.instrumentation.common.utils import (get_error_message, get_llm_type, get_status,)
|
|
5
5
|
INFERENCE = {
|
|
6
6
|
"type": "inference",
|
|
7
7
|
"attributes": [
|
|
@@ -43,14 +43,9 @@ INFERENCE = {
|
|
|
43
43
|
{
|
|
44
44
|
"name": "data.output",
|
|
45
45
|
"attributes": [
|
|
46
|
-
{
|
|
47
|
-
"_comment": "this is result from LLM",
|
|
48
|
-
"attribute": "status",
|
|
49
|
-
"accessor": lambda arguments: get_status(arguments)
|
|
50
|
-
},
|
|
51
46
|
{
|
|
52
|
-
"attribute": "
|
|
53
|
-
"accessor": lambda arguments:
|
|
47
|
+
"attribute": "error_code",
|
|
48
|
+
"accessor": lambda arguments: get_error_message(arguments)
|
|
54
49
|
},
|
|
55
50
|
{
|
|
56
51
|
"_comment": "this is response from LLM",
|
|
@@ -66,6 +61,16 @@ INFERENCE = {
|
|
|
66
61
|
"_comment": "this is metadata usage from LLM",
|
|
67
62
|
"accessor": lambda arguments: _helper.update_span_from_llm_response(arguments['result'],
|
|
68
63
|
arguments['instance'])
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"attribute": "finish_reason",
|
|
67
|
+
"accessor": lambda arguments: _helper.extract_finish_reason(arguments)
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"attribute": "finish_type",
|
|
71
|
+
"accessor": lambda arguments: _helper.map_finish_reason_to_finish_type(
|
|
72
|
+
_helper.extract_finish_reason(arguments)
|
|
73
|
+
)
|
|
69
74
|
}
|
|
70
75
|
]
|
|
71
76
|
}
|
|
@@ -20,7 +20,7 @@ class BotoCoreSpanHandler(SpanHandler):
|
|
|
20
20
|
instrumented_method = instrumentor(to_wrap, wrapped, span_name, return_value, original_method)
|
|
21
21
|
setattr(return_value, method_name, instrumented_method)
|
|
22
22
|
|
|
23
|
-
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
23
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value,token=None):
|
|
24
24
|
self._botocore_processor(to_wrap=to_wrap, wrapped=wrapped, instance=instance, return_value=return_value, args=args,
|
|
25
25
|
kwargs=kwargs)
|
|
26
26
|
return super().post_tracing(to_wrap, wrapped, instance, args, kwargs,return_value)
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from threading import local
|
|
3
|
+
from monocle_apptrace.instrumentation.common.utils import extract_http_headers, clear_http_scopes
|
|
4
|
+
from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
|
|
5
|
+
from monocle_apptrace.instrumentation.common.constants import HTTP_SUCCESS_CODES
|
|
6
|
+
from monocle_apptrace.instrumentation.common.utils import MonocleSpanException
|
|
7
|
+
from opentelemetry.context import get_current
|
|
8
|
+
from opentelemetry.trace import Span
|
|
9
|
+
from opentelemetry.trace.propagation import _SPAN_KEY
|
|
10
|
+
import json
|
|
11
|
+
import urllib.parse
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
MAX_DATA_LENGTH = 1000
|
|
15
|
+
token_data = local()
|
|
16
|
+
token_data.current_token = None
|
|
17
|
+
|
|
18
|
+
def get_route(scope) -> str:
|
|
19
|
+
return scope.get('path', '')
|
|
20
|
+
|
|
21
|
+
def get_method(scope) -> str:
|
|
22
|
+
return scope.get('method', '')
|
|
23
|
+
|
|
24
|
+
def get_params(args) -> dict:
|
|
25
|
+
try:
|
|
26
|
+
query_bytes = args.get("query_string", "")
|
|
27
|
+
query_str = query_bytes.decode('utf-8')
|
|
28
|
+
params = urllib.parse.parse_qs(query_str)
|
|
29
|
+
question = params.get('question', [''])[0]
|
|
30
|
+
return question
|
|
31
|
+
except Exception as e:
|
|
32
|
+
logger.warning(f"Error extracting params: {e}")
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
def extract_response(response) -> str:
|
|
36
|
+
try:
|
|
37
|
+
if hasattr(response, 'body'):
|
|
38
|
+
data = response.body
|
|
39
|
+
answer = json.loads(data.decode("utf-8"))
|
|
40
|
+
return answer
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.warning(f"Error extracting response: {e}")
|
|
43
|
+
return ""
|
|
44
|
+
|
|
45
|
+
def extract_status(instance) -> str:
|
|
46
|
+
status = f"{instance.status_code}" if hasattr(instance, 'status_code') else ""
|
|
47
|
+
if status not in HTTP_SUCCESS_CODES:
|
|
48
|
+
error_message = extract_response(instance)
|
|
49
|
+
raise MonocleSpanException(f"error: {status} - {error_message}")
|
|
50
|
+
return status
|
|
51
|
+
|
|
52
|
+
def fastapi_pre_tracing(scope):
|
|
53
|
+
headers = {k.decode('utf-8').lower(): v.decode('utf-8')
|
|
54
|
+
for k, v in scope.get('headers', [])}
|
|
55
|
+
token_data.current_token = extract_http_headers(headers)
|
|
56
|
+
|
|
57
|
+
def fastapi_post_tracing():
|
|
58
|
+
clear_http_scopes(token_data.current_token)
|
|
59
|
+
token_data.current_token = None
|
|
60
|
+
|
|
61
|
+
class FastAPISpanHandler(SpanHandler):
|
|
62
|
+
def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
|
|
63
|
+
scope = args[0] if args else {}
|
|
64
|
+
fastapi_pre_tracing(scope)
|
|
65
|
+
return super().pre_tracing(to_wrap, wrapped, instance, args, kwargs)
|
|
66
|
+
|
|
67
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
68
|
+
fastapi_post_tracing()
|
|
69
|
+
return super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
70
|
+
|
|
71
|
+
class FastAPIResponseSpanHandler(SpanHandler):
|
|
72
|
+
def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
|
|
73
|
+
try:
|
|
74
|
+
ctx = get_current()
|
|
75
|
+
if ctx is not None:
|
|
76
|
+
parent_span: Span = ctx.get(_SPAN_KEY)
|
|
77
|
+
if parent_span is not None:
|
|
78
|
+
self.hydrate_events(to_wrap, wrapped, instance, args, kwargs,
|
|
79
|
+
return_value, parent_span=parent_span)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.info(f"Failed to propagate fastapi response: {e}")
|
|
82
|
+
super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.metamodel.fastapi import _helper
|
|
2
|
+
|
|
3
|
+
FASTAPI_HTTP_PROCESSOR = {
|
|
4
|
+
"type": "http.process",
|
|
5
|
+
"attributes": [
|
|
6
|
+
[
|
|
7
|
+
{
|
|
8
|
+
"attribute": "method",
|
|
9
|
+
"accessor": lambda arguments: _helper.get_method(arguments['args'][0])
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"attribute": "route",
|
|
13
|
+
"accessor": lambda arguments: _helper.get_route(arguments['args'][0])
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
FASTAPI_RESPONSE_PROCESSOR = {
|
|
20
|
+
"events": [
|
|
21
|
+
{
|
|
22
|
+
"name": "data.input",
|
|
23
|
+
"attributes": [
|
|
24
|
+
{
|
|
25
|
+
"attribute": "params",
|
|
26
|
+
"accessor": lambda arguments: _helper.get_params(arguments['args'][0])
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "data.output",
|
|
32
|
+
"attributes": [
|
|
33
|
+
{
|
|
34
|
+
"attribute": "status",
|
|
35
|
+
"accessor": lambda arguments: _helper.extract_status(arguments['instance'])
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"attribute": "response",
|
|
39
|
+
"accessor": lambda arguments: _helper.extract_response(arguments['instance'])
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.common.wrapper import atask_wrapper
|
|
2
|
+
from monocle_apptrace.instrumentation.metamodel.fastapi.entities.http import FASTAPI_HTTP_PROCESSOR, FASTAPI_RESPONSE_PROCESSOR
|
|
3
|
+
|
|
4
|
+
FASTAPI_METHODS = [
|
|
5
|
+
# {
|
|
6
|
+
# "package": "fastapi",
|
|
7
|
+
# "object": "FastAPI",
|
|
8
|
+
# "method": "__call__",
|
|
9
|
+
# "wrapper_method": atask_wrapper,
|
|
10
|
+
# "span_name": "fastapi.request",
|
|
11
|
+
# "span_handler": "fastapi_handler",
|
|
12
|
+
# "output_processor": FASTAPI_HTTP_PROCESSOR,
|
|
13
|
+
# },
|
|
14
|
+
# {
|
|
15
|
+
# "package": "starlette.responses",
|
|
16
|
+
# "object": "Response",
|
|
17
|
+
# "method": "__call__",
|
|
18
|
+
# "span_name": "fastapi.response",
|
|
19
|
+
# "wrapper_method": atask_wrapper,
|
|
20
|
+
# "span_handler": "fastapi_response_handler",
|
|
21
|
+
# "output_processor": FASTAPI_RESPONSE_PROCESSOR
|
|
22
|
+
# }
|
|
23
|
+
]
|