monocle-apptrace 0.4.1__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.

Files changed (91) hide show
  1. monocle_apptrace/__main__.py +1 -1
  2. monocle_apptrace/exporters/file_exporter.py +125 -37
  3. monocle_apptrace/instrumentation/common/__init__.py +16 -1
  4. monocle_apptrace/instrumentation/common/constants.py +14 -1
  5. monocle_apptrace/instrumentation/common/instrumentor.py +19 -152
  6. monocle_apptrace/instrumentation/common/method_wrappers.py +376 -0
  7. monocle_apptrace/instrumentation/common/span_handler.py +58 -32
  8. monocle_apptrace/instrumentation/common/utils.py +52 -15
  9. monocle_apptrace/instrumentation/common/wrapper.py +124 -18
  10. monocle_apptrace/instrumentation/common/wrapper_method.py +48 -1
  11. monocle_apptrace/instrumentation/metamodel/a2a/__init__.py +0 -0
  12. monocle_apptrace/instrumentation/metamodel/a2a/_helper.py +37 -0
  13. monocle_apptrace/instrumentation/metamodel/a2a/entities/__init__.py +0 -0
  14. monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +112 -0
  15. monocle_apptrace/instrumentation/metamodel/a2a/methods.py +22 -0
  16. monocle_apptrace/instrumentation/metamodel/adk/__init__.py +0 -0
  17. monocle_apptrace/instrumentation/metamodel/adk/_helper.py +182 -0
  18. monocle_apptrace/instrumentation/metamodel/adk/entities/agent.py +50 -0
  19. monocle_apptrace/instrumentation/metamodel/adk/entities/tool.py +57 -0
  20. monocle_apptrace/instrumentation/metamodel/adk/methods.py +24 -0
  21. monocle_apptrace/instrumentation/metamodel/agents/__init__.py +0 -0
  22. monocle_apptrace/instrumentation/metamodel/agents/_helper.py +220 -0
  23. monocle_apptrace/instrumentation/metamodel/agents/agents_processor.py +152 -0
  24. monocle_apptrace/instrumentation/metamodel/agents/entities/__init__.py +0 -0
  25. monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +191 -0
  26. monocle_apptrace/instrumentation/metamodel/agents/methods.py +56 -0
  27. monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +6 -11
  28. monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +112 -18
  29. monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +18 -10
  30. monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +13 -11
  31. monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +5 -0
  32. monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +88 -8
  33. monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +22 -8
  34. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +92 -16
  35. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +13 -8
  36. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +1 -1
  37. monocle_apptrace/instrumentation/metamodel/fastapi/__init__.py +0 -0
  38. monocle_apptrace/instrumentation/metamodel/fastapi/_helper.py +82 -0
  39. monocle_apptrace/instrumentation/metamodel/fastapi/entities/__init__.py +0 -0
  40. monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +44 -0
  41. monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +23 -0
  42. monocle_apptrace/instrumentation/metamodel/finish_types.py +463 -0
  43. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +6 -11
  44. monocle_apptrace/instrumentation/metamodel/gemini/__init__.py +0 -0
  45. monocle_apptrace/instrumentation/metamodel/gemini/_helper.py +120 -0
  46. monocle_apptrace/instrumentation/metamodel/gemini/entities/__init__.py +0 -0
  47. monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +86 -0
  48. monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +43 -0
  49. monocle_apptrace/instrumentation/metamodel/gemini/methods.py +31 -0
  50. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +79 -8
  51. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +15 -10
  52. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +7 -0
  53. monocle_apptrace/instrumentation/metamodel/lambdafunc/_helper.py +78 -0
  54. monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +51 -0
  55. monocle_apptrace/instrumentation/metamodel/lambdafunc/methods.py +23 -0
  56. monocle_apptrace/instrumentation/metamodel/lambdafunc/wrapper.py +23 -0
  57. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +145 -19
  58. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +19 -10
  59. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +67 -10
  60. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +127 -20
  61. monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +46 -0
  62. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +35 -9
  63. monocle_apptrace/instrumentation/metamodel/litellm/__init__.py +0 -0
  64. monocle_apptrace/instrumentation/metamodel/litellm/_helper.py +89 -0
  65. monocle_apptrace/instrumentation/metamodel/litellm/entities/__init__.py +0 -0
  66. monocle_apptrace/instrumentation/metamodel/litellm/entities/inference.py +108 -0
  67. monocle_apptrace/instrumentation/metamodel/litellm/methods.py +19 -0
  68. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +227 -16
  69. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +127 -10
  70. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +13 -8
  71. monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +62 -0
  72. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +68 -1
  73. monocle_apptrace/instrumentation/metamodel/mcp/__init__.py +0 -0
  74. monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +118 -0
  75. monocle_apptrace/instrumentation/metamodel/mcp/entities/__init__.py +0 -0
  76. monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +48 -0
  77. monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +8 -0
  78. monocle_apptrace/instrumentation/metamodel/mcp/methods.py +21 -0
  79. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +188 -16
  80. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +148 -92
  81. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +1 -1
  82. monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +53 -23
  83. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +1 -1
  84. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +15 -9
  85. monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +0 -4
  86. {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/METADATA +27 -11
  87. monocle_apptrace-0.5.0.dist-info/RECORD +142 -0
  88. monocle_apptrace-0.4.1.dist-info/RECORD +0 -96
  89. {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/WHEEL +0 -0
  90. {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/licenses/LICENSE +0 -0
  91. {monocle_apptrace-0.4.1.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
- resolve_from_alias,
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 [str(message) for message in messages]
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
- return result.output_text
91
- if (
92
- result.choices
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
- return result.choices[0].message.content
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": "status",
187
- "accessor": lambda arguments: get_status(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 [str(d) for d in messages]
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
- response: str = ""
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
- response = response_dict["answer"]
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
- response = reply
63
+ messages.append({role: reply})
70
64
  else:
71
65
  if arguments["exception"] is not None:
72
- response = get_exception_message(arguments)
66
+ return get_exception_message(arguments)
73
67
  elif hasattr(arguments["result"], "error"):
74
- response = arguments["result"].error
75
- return response
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": "status_code",
53
- "accessor": lambda arguments: _helper.get_status_code(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)
@@ -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)
@@ -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
+ ]