monocle-apptrace 0.4.0b3__py3-none-any.whl → 0.4.1__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 (35) hide show
  1. monocle_apptrace/instrumentation/__init__.py +2 -1
  2. monocle_apptrace/instrumentation/common/constants.py +3 -0
  3. monocle_apptrace/instrumentation/common/instrumentor.py +1 -1
  4. monocle_apptrace/instrumentation/common/span_handler.py +1 -1
  5. monocle_apptrace/instrumentation/common/utils.py +20 -2
  6. monocle_apptrace/instrumentation/common/wrapper_method.py +5 -1
  7. monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +29 -4
  8. monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +12 -2
  9. monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +78 -0
  10. monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +51 -0
  11. monocle_apptrace/instrumentation/metamodel/azfunc/methods.py +23 -0
  12. monocle_apptrace/instrumentation/metamodel/azfunc/wrapper.py +23 -0
  13. monocle_apptrace/instrumentation/metamodel/azureaiinference/__init__.py +1 -0
  14. monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +216 -0
  15. monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +208 -0
  16. monocle_apptrace/instrumentation/metamodel/azureaiinference/methods.py +23 -0
  17. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +42 -17
  18. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +11 -3
  19. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +20 -12
  20. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +10 -2
  21. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +19 -13
  22. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +10 -2
  23. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +21 -13
  24. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +10 -2
  25. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +17 -9
  26. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +3 -2
  27. monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +50 -4
  28. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +32 -12
  29. monocle_apptrace/instrumentation/metamodel/teamsai/methods.py +30 -17
  30. monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +448 -0
  31. {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/METADATA +1 -1
  32. {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/RECORD +35 -26
  33. {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/WHEEL +0 -0
  34. {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/licenses/LICENSE +0 -0
  35. {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/licenses/NOTICE +0 -0
@@ -1 +1,2 @@
1
- from .common import *
1
+ from .common import *
2
+ from .metamodel.azfunc.wrapper import monocle_trace_azure_function_route
@@ -54,6 +54,9 @@ llm_type_map = {
54
54
  "anthropic": "anthropic",
55
55
  "chatanthropic":"anthropic",
56
56
  "anthropicchatgenerator":"anthropic",
57
+ "chatcompletionsclient": "azure_ai_inference",
58
+ "embeddingsclient": "azure_ai_inference",
59
+ "imageembeddingsclient": "azure_ai_inference",
57
60
  }
58
61
 
59
62
  MONOCLE_INSTRUMENTOR = "monocle_apptrace"
@@ -362,7 +362,7 @@ def monocle_trace_http_route(func):
362
362
  if inspect.iscoroutinefunction(func):
363
363
  @wraps(func)
364
364
  async def wrapper(*args, **kwargs):
365
- return http_async_route_handler(func, *args, **kwargs)
365
+ return await http_async_route_handler(func, *args, **kwargs)
366
366
  return wrapper
367
367
  else:
368
368
  @wraps(func)
@@ -159,7 +159,7 @@ class SpanHandler:
159
159
  result = accessor(arguments)
160
160
  if result and isinstance(result, dict):
161
161
  result = dict((key, value) for key, value in result.items() if value is not None)
162
- if result and isinstance(result, (str, list, dict)):
162
+ if result and isinstance(result, (int, str, list, dict)):
163
163
  if attribute_key is not None:
164
164
  event_attributes[attribute_key] = result
165
165
  else:
@@ -376,10 +376,12 @@ def get_status(arguments):
376
376
  return 'success'
377
377
 
378
378
  def get_exception_status_code(arguments):
379
- if arguments['exception'] is not None and hasattr(arguments['exception'], 'code'):
379
+ if arguments['exception'] is not None and hasattr(arguments['exception'], 'code') and arguments['exception'].code is not None:
380
380
  return arguments['exception'].code
381
- else:
381
+ elif arguments['exception'] is not None:
382
382
  return 'error'
383
+ else:
384
+ return 'success'
383
385
 
384
386
  def get_exception_message(arguments):
385
387
  if arguments['exception'] is not None:
@@ -390,6 +392,22 @@ def get_exception_message(arguments):
390
392
  else:
391
393
  return ''
392
394
 
395
+ def get_status_code(arguments):
396
+ if arguments["exception"] is not None:
397
+ return get_exception_status_code(arguments)
398
+ elif hasattr(arguments["result"], "status"):
399
+ return arguments["result"].status
400
+ else:
401
+ return 'success'
402
+
403
+ def get_status(arguments):
404
+ if arguments["exception"] is not None:
405
+ return 'error'
406
+ elif get_status_code(arguments) == 'success':
407
+ return 'success'
408
+ else:
409
+ return 'error'
410
+
393
411
  def patch_instance_method(obj, method_name, func):
394
412
  """
395
413
  Patch a special method (like __iter__) for a single instance.
@@ -2,6 +2,7 @@
2
2
  from typing import Any, Dict
3
3
  from monocle_apptrace.instrumentation.common.wrapper import task_wrapper, scope_wrapper
4
4
  from monocle_apptrace.instrumentation.common.span_handler import SpanHandler, NonFrameworkSpanHandler
5
+ from monocle_apptrace.instrumentation.metamodel.azureaiinference.methods import AZURE_AI_INFERENCE_METHODS
5
6
  from monocle_apptrace.instrumentation.metamodel.botocore.methods import BOTOCORE_METHODS
6
7
  from monocle_apptrace.instrumentation.metamodel.botocore.handlers.botocore_span_handler import BotoCoreSpanHandler
7
8
  from monocle_apptrace.instrumentation.metamodel.langchain.methods import (
@@ -20,6 +21,8 @@ from monocle_apptrace.instrumentation.metamodel.teamsai.methods import (TEAMAI_M
20
21
  from monocle_apptrace.instrumentation.metamodel.anthropic.methods import (ANTHROPIC_METHODS, )
21
22
  from monocle_apptrace.instrumentation.metamodel.aiohttp.methods import (AIOHTTP_METHODS, )
22
23
  from monocle_apptrace.instrumentation.metamodel.aiohttp._helper import aiohttpSpanHandler
24
+ from monocle_apptrace.instrumentation.metamodel.azfunc._helper import (azureSpanHandler)
25
+ from monocle_apptrace.instrumentation.metamodel.azfunc.methods import AZFUNC_HTTP_METHODS
23
26
  class WrapperMethod:
24
27
  def __init__(
25
28
  self,
@@ -68,7 +71,7 @@ class WrapperMethod:
68
71
  def get_span_handler(self) -> SpanHandler:
69
72
  return self.span_handler()
70
73
 
71
- DEFAULT_METHODS_LIST = LANGCHAIN_METHODS + LLAMAINDEX_METHODS + HAYSTACK_METHODS + BOTOCORE_METHODS + FLASK_METHODS + REQUESTS_METHODS + LANGGRAPH_METHODS + OPENAI_METHODS + TEAMAI_METHODS + ANTHROPIC_METHODS + AIOHTTP_METHODS
74
+ DEFAULT_METHODS_LIST = LANGCHAIN_METHODS + LLAMAINDEX_METHODS + HAYSTACK_METHODS + BOTOCORE_METHODS + FLASK_METHODS + REQUESTS_METHODS + LANGGRAPH_METHODS + OPENAI_METHODS + TEAMAI_METHODS + ANTHROPIC_METHODS + AIOHTTP_METHODS + AZURE_AI_INFERENCE_METHODS + AZFUNC_HTTP_METHODS
72
75
 
73
76
  MONOCLE_SPAN_HANDLERS: Dict[str, SpanHandler] = {
74
77
  "default": SpanHandler(),
@@ -79,4 +82,5 @@ MONOCLE_SPAN_HANDLERS: Dict[str, SpanHandler] = {
79
82
  "request_handler": RequestSpanHandler(),
80
83
  "non_framework_handler": NonFrameworkSpanHandler(),
81
84
  "openai_handler": OpenAISpanHandler(),
85
+ "azure_func_handler": azureSpanHandler(),
82
86
  }
@@ -9,6 +9,7 @@ from monocle_apptrace.instrumentation.common.utils import (
9
9
  get_keys_as_tuple,
10
10
  get_nested_value,
11
11
  try_option,
12
+ get_exception_message,
12
13
  )
13
14
 
14
15
 
@@ -39,12 +40,36 @@ def extract_messages(kwargs):
39
40
  logger.warning("Warning: Error occurred in extract_messages: %s", str(e))
40
41
  return []
41
42
 
43
+ def get_exception_status_code(arguments):
44
+ if arguments['exception'] is not None and hasattr(arguments['exception'], 'status_code') and arguments['exception'].status_code is not None:
45
+ return arguments['exception'].status_code
46
+ elif arguments['exception'] is not None:
47
+ return 'error'
48
+ else:
49
+ return 'success'
42
50
 
43
- def extract_assistant_message(response):
51
+ def get_status_code(arguments):
52
+ if arguments["exception"] is not None:
53
+ return get_exception_status_code(arguments)
54
+ elif hasattr(arguments["result"], "status"):
55
+ return arguments["result"].status
56
+ else:
57
+ return 'success'
58
+
59
+ def extract_assistant_message(arguments):
44
60
  try:
45
- if response is not None and hasattr(response,"content") and len(response.content) >0:
46
- if hasattr(response.content[0],"text"):
47
- return response.content[0].text
61
+ status = get_status_code(arguments)
62
+ response: str = ""
63
+ if status == 'success':
64
+ if arguments['result'] is not None and hasattr(arguments['result'],"content") and len(arguments['result'].content) >0:
65
+ if hasattr(arguments['result'].content[0],"text"):
66
+ response = arguments['result'].content[0].text
67
+ else:
68
+ if arguments["exception"] is not None:
69
+ response = get_exception_message(arguments)
70
+ elif hasattr(arguments["result"], "error"):
71
+ response = arguments["result"].error
72
+ return response
48
73
  except (IndexError, AttributeError) as e:
49
74
  logger.warning("Warning: Error occurred in extract_assistant_message: %s", str(e))
50
75
  return None
@@ -1,7 +1,9 @@
1
1
  from monocle_apptrace.instrumentation.metamodel.anthropic import (
2
2
  _helper,
3
3
  )
4
- from monocle_apptrace.instrumentation.common.utils import resolve_from_alias, get_llm_type
4
+ from monocle_apptrace.instrumentation.common.utils import (resolve_from_alias, get_llm_type,
5
+ get_status, get_status_code
6
+ )
5
7
 
6
8
  INFERENCE = {
7
9
  "type": "inference",
@@ -52,10 +54,18 @@ INFERENCE = {
52
54
  {
53
55
  "name": "data.output",
54
56
  "attributes": [
57
+ {
58
+ "attribute": "status",
59
+ "accessor": lambda arguments: get_status(arguments)
60
+ },
61
+ {
62
+ "attribute": "status_code",
63
+ "accessor": lambda arguments: _helper.get_status_code(arguments)
64
+ },
55
65
  {
56
66
  "_comment": "this is result from LLM",
57
67
  "attribute": "response",
58
- "accessor": lambda arguments: _helper.extract_assistant_message(arguments['result'])
68
+ "accessor": lambda arguments: _helper.extract_assistant_message(arguments)
59
69
  }
60
70
  ]
61
71
  },
@@ -0,0 +1,78 @@
1
+ import logging
2
+ from threading import local
3
+ from monocle_apptrace.instrumentation.common.utils import extract_http_headers, clear_http_scopes, try_option, Option, MonocleSpanException
4
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
5
+ from monocle_apptrace.instrumentation.common.constants import HTTP_SUCCESS_CODES
6
+ from urllib.parse import unquote, urlparse, ParseResult
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+ MAX_DATA_LENGTH = 1000
11
+ token_data = local()
12
+ token_data.current_token = None
13
+
14
+ def get_url(kwargs) -> ParseResult:
15
+ url_str = try_option(getattr, kwargs['req'], 'url')
16
+ url = url_str.unwrap_or(None)
17
+ if url is not None:
18
+ return urlparse(url)
19
+ else:
20
+ return None
21
+
22
+ def get_route(kwargs) -> str:
23
+ url:ParseResult = get_url(kwargs)
24
+ if url is not None:
25
+ return url.path
26
+
27
+ def get_method(kwargs) -> str:
28
+ # return args[0]['method'] if 'method' in args[0] else ""
29
+ http_method: Option[str] = try_option(getattr, kwargs['req'], 'method')
30
+ return http_method.unwrap_or("")
31
+
32
+ def get_params(kwargs) -> dict:
33
+ url:ParseResult = get_url(kwargs)
34
+ if url is not None:
35
+ return unquote(url.query)
36
+
37
+ def get_body(kwargs) -> dict:
38
+ if hasattr(kwargs['req'], 'get_body'):
39
+ response = kwargs.get_body()
40
+ if isinstance(response, bytes):
41
+ response = response.decode('utf-8', errors='ignore')
42
+ else:
43
+ response = ""
44
+ return response
45
+
46
+ def extract_response(result) -> str:
47
+ if hasattr(result, 'get_body'):
48
+ response = result.get_body() # text[0:max(result.text.__len__(), MAX_DATA_LENGTH)]
49
+ if isinstance(response, bytes):
50
+ response = response.decode('utf-8', errors='ignore')
51
+ else:
52
+ response = ""
53
+ return response
54
+
55
+ def extract_status(result) -> str:
56
+ status = f"{result.status_code}" if hasattr(result, 'status_code') else ""
57
+ if status not in HTTP_SUCCESS_CODES:
58
+ error_message = extract_response(result)
59
+ raise MonocleSpanException(f"error: {status} - {error_message}")
60
+ return status
61
+
62
+ def azure_func_pre_tracing(kwargs):
63
+ headers = kwargs['req'].headers if hasattr(kwargs['req'], 'headers') else {}
64
+ token_data.current_token = extract_http_headers(headers)
65
+
66
+ def azure_func_post_tracing():
67
+ clear_http_scopes(token_data.current_token)
68
+ token_data.current_token = None
69
+
70
+ class azureSpanHandler(SpanHandler):
71
+
72
+ def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
73
+ azure_func_pre_tracing(kwargs)
74
+ return super().pre_tracing(to_wrap, wrapped, instance, args, kwargs)
75
+
76
+ def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
77
+ azure_func_post_tracing()
78
+ return super().post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
@@ -0,0 +1,51 @@
1
+ from monocle_apptrace.instrumentation.metamodel.azfunc import _helper
2
+ AZFUNC_HTTP_PROCESSOR = {
3
+ "type": "http.process",
4
+ "attributes": [
5
+ [
6
+ {
7
+ "_comment": "request method, request URI",
8
+ "attribute": "method",
9
+ "accessor": lambda arguments: _helper.get_method(arguments['kwargs'])
10
+ },
11
+ {
12
+ "_comment": "request method, request URI",
13
+ "attribute": "route",
14
+ "accessor": lambda arguments: _helper.get_route(arguments['kwargs'])
15
+ },
16
+ {
17
+ "_comment": "request method, request URI",
18
+ "attribute": "body",
19
+ "accessor": lambda arguments: _helper.get_body(arguments['kwargs'])
20
+ },
21
+ ]
22
+ ],
23
+ "events": [
24
+ {
25
+ "name": "data.input",
26
+ "attributes": [
27
+ {
28
+ "_comment": "route params",
29
+ "attribute": "params",
30
+ "accessor": lambda arguments: _helper.get_params(arguments['kwargs'])
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "name": "data.output",
36
+ "attributes": [
37
+ {
38
+ "_comment": "status from HTTP response",
39
+ "attribute": "status",
40
+ "accessor": lambda arguments: _helper.extract_status(arguments['result'])
41
+ },
42
+ {
43
+ "_comment": "this is result from LLM",
44
+ "attribute": "response",
45
+ "accessor": lambda arguments: _helper.extract_response(arguments['result'])
46
+ }
47
+ ]
48
+ }
49
+
50
+ ]
51
+ }
@@ -0,0 +1,23 @@
1
+ from monocle_apptrace.instrumentation.common.wrapper import atask_wrapper, task_wrapper
2
+ from monocle_apptrace.instrumentation.metamodel.azfunc.entities.http import AZFUNC_HTTP_PROCESSOR
3
+
4
+ AZFUNC_HTTP_METHODS = [
5
+ {
6
+ "package": "monocle_apptrace.instrumentation.metamodel.azfunc.wrapper",
7
+ "object": "AzureFunctionRouteWrapper",
8
+ "method": "run_async",
9
+ "span_name": "azure_function_route",
10
+ "wrapper_method": atask_wrapper,
11
+ "span_handler": "azure_func_handler",
12
+ "output_processor": AZFUNC_HTTP_PROCESSOR
13
+ },
14
+ {
15
+ "package": "monocle_apptrace.instrumentation.metamodel.azfunc.wrapper",
16
+ "object": "AzureFunctionRouteWrapper",
17
+ "method": "run_sync",
18
+ "span_name": "azure_function_route",
19
+ "wrapper_method": task_wrapper,
20
+ "span_handler": "azure_func_handler",
21
+ "output_processor": AZFUNC_HTTP_PROCESSOR
22
+ }
23
+ ]
@@ -0,0 +1,23 @@
1
+ from functools import wraps
2
+ import inspect
3
+
4
+ def monocle_trace_azure_function_route(func):
5
+ if inspect.iscoroutinefunction(func):
6
+ @wraps(func)
7
+ async def wrapper(*args, **kwargs):
8
+ return await AzureFunctionRouteWrapper.run_async(func, *args, **kwargs)
9
+ return wrapper
10
+ else:
11
+ @wraps(func)
12
+ def wrapper(*args, **kwargs):
13
+ return AzureFunctionRouteWrapper.run_sync(func ,*args, **kwargs)
14
+ return wrapper
15
+
16
+ class AzureFunctionRouteWrapper:
17
+ @staticmethod
18
+ async def run_async(func, *args, **kwargs):
19
+ return await func(*args, **kwargs)
20
+
21
+ @staticmethod
22
+ def run_sync(func, *args, **kwargs):
23
+ return func(*args, **kwargs)
@@ -0,0 +1 @@
1
+ # Azure AI Inference instrumentation module
@@ -0,0 +1,216 @@
1
+ import logging
2
+ from typing import Any, Dict, Optional
3
+ from urllib.parse import urlparse
4
+ from monocle_apptrace.instrumentation.common.utils import (
5
+ resolve_from_alias,
6
+ get_exception_message,
7
+ )
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def extract_messages(args_or_kwargs: Any) -> str:
13
+ """Extract messages from azure-ai-inference request arguments."""
14
+ try:
15
+ messages = []
16
+ if "instructions" in args_or_kwargs:
17
+ messages.append({"instructions": args_or_kwargs.get("instructions", {})})
18
+ if "input" in args_or_kwargs:
19
+ messages.append({"input": args_or_kwargs.get("input", {})})
20
+ if "messages" in args_or_kwargs and len(args_or_kwargs["messages"]) > 0:
21
+ for msg in args_or_kwargs["messages"]:
22
+ if msg.get("content") and msg.get("role"):
23
+ messages.append({msg["role"]: msg["content"]})
24
+
25
+ return [str(message) for message in messages]
26
+ except Exception as e:
27
+ logger.warning("Warning: Error occurred in extract_messages: %s", str(e))
28
+ return []
29
+
30
+
31
+ def extract_inference_endpoint(instance: Any) -> str:
32
+ """Extract inference endpoint from azure-ai-inference client instance."""
33
+ try:
34
+ return instance._config.endpoint
35
+ except Exception as e:
36
+ logger.warning(
37
+ "Warning: Error occurred in extract_inference_endpoint: %s", str(e)
38
+ )
39
+ return ""
40
+
41
+
42
+ def extract_embeddings_input(args_or_kwargs: Any) -> str:
43
+ """Extract input text from azure-ai-inference embeddings request."""
44
+ try:
45
+ # Handle both args and kwargs scenarios
46
+ if isinstance(args_or_kwargs, dict):
47
+ if "input" in args_or_kwargs:
48
+ input_data = args_or_kwargs["input"]
49
+ elif len(args_or_kwargs) > 0:
50
+ first_arg = list(args_or_kwargs.values())[0]
51
+ if isinstance(first_arg, dict) and "input" in first_arg:
52
+ input_data = first_arg["input"]
53
+ else:
54
+ input_data = first_arg
55
+ else:
56
+ return ""
57
+ elif hasattr(args_or_kwargs, "__iter__") and len(args_or_kwargs) > 0:
58
+ first_arg = args_or_kwargs[0]
59
+ if hasattr(first_arg, "get") and "input" in first_arg:
60
+ input_data = first_arg["input"]
61
+ else:
62
+ input_data = first_arg
63
+ else:
64
+ return ""
65
+
66
+ # Format input for display
67
+ if isinstance(input_data, (list, tuple)):
68
+ return " | ".join(str(item) for item in input_data)
69
+ else:
70
+ return str(input_data)
71
+ except Exception as e:
72
+ logger.warning(
73
+ "Warning: Error occurred in extract_embeddings_input: %s", str(e)
74
+ )
75
+ return ""
76
+
77
+
78
+ def extract_assistant_message(arguments: Dict[str, Any]) -> str:
79
+ """Extract assistant response from azure-ai-inference completion result."""
80
+ try:
81
+ # Check for exception first
82
+ if arguments.get("exception") is not None:
83
+ return get_exception_message(arguments)
84
+
85
+ result = arguments.get("result")
86
+ if not result:
87
+ return ""
88
+ if hasattr(result, "output_text"):
89
+ # If the result has output_text attribute
90
+ return result.output_text
91
+ if (
92
+ result.choices
93
+ and result.choices[0].message
94
+ and result.choices[0].message.content
95
+ ):
96
+ # If the result is a chat completion with content
97
+ return result.choices[0].message.content
98
+
99
+ return str(result)
100
+ except Exception as e:
101
+ logger.warning(
102
+ "Warning: Error occurred in extract_assistant_message: %s", str(e)
103
+ )
104
+ return ""
105
+
106
+
107
+ def extract_embeddings_output(arguments: Dict[str, Any]) -> str:
108
+ """Extract embeddings from azure-ai-inference embeddings result."""
109
+ try:
110
+ result = arguments.get("result")
111
+ if not result:
112
+ return ""
113
+
114
+ if hasattr(result, "data") and result.data:
115
+ # Format as summary of embeddings data
116
+ embeddings_info = []
117
+ for i, item in enumerate(result.data):
118
+ if hasattr(item, "embedding") and hasattr(item, "index"):
119
+ embedding_length = len(item.embedding) if item.embedding else 0
120
+ embeddings_info.append(
121
+ f"index={item.index}, embedding=[{embedding_length} dimensions]"
122
+ )
123
+ return " | ".join(embeddings_info)
124
+
125
+ return str(result)
126
+ except Exception as e:
127
+ logger.warning(
128
+ "Warning: Error occurred in extract_embeddings_output: %s", str(e)
129
+ )
130
+ return ""
131
+
132
+
133
+ def update_span_from_llm_response(result: Any, instance: Any = None) -> Dict[str, Any]:
134
+ """Extract usage metadata from azure-ai-inference response."""
135
+ try:
136
+ attributes = {}
137
+
138
+ # Handle streaming responses with accumulated usage data
139
+ if hasattr(result, "usage") and result.usage:
140
+ usage = result.usage
141
+ if hasattr(usage, "completion_tokens"):
142
+ attributes["completion_tokens"] = usage.completion_tokens
143
+ if hasattr(usage, "prompt_tokens"):
144
+ attributes["prompt_tokens"] = usage.prompt_tokens
145
+ if hasattr(usage, "total_tokens"):
146
+ attributes["total_tokens"] = usage.total_tokens
147
+
148
+ # Handle regular response usage
149
+ elif hasattr(result, "usage"):
150
+ usage = result.usage
151
+ if hasattr(usage, "completion_tokens"):
152
+ attributes["completion_tokens"] = usage.completion_tokens
153
+ if hasattr(usage, "prompt_tokens"):
154
+ attributes["prompt_tokens"] = usage.prompt_tokens
155
+ if hasattr(usage, "total_tokens"):
156
+ attributes["total_tokens"] = usage.total_tokens
157
+
158
+ # Extract model information if available
159
+ if hasattr(result, "model"):
160
+ attributes["model"] = result.model
161
+
162
+ return attributes
163
+ except Exception as e:
164
+ logger.warning(
165
+ "Warning: Error occurred in update_span_from_llm_response: %s", str(e)
166
+ )
167
+ return {}
168
+
169
+
170
+ def get_model_name(arguments: Dict[str, Any]) -> str:
171
+ """Extract model name from azure-ai-inference request arguments."""
172
+ try:
173
+
174
+ # Try to get from instance
175
+ instance = arguments.get("instance")
176
+ if arguments.get('kwargs') and arguments.get('kwargs').get('model'):
177
+ return arguments['kwargs'].get('model')
178
+ if instance and hasattr(instance, "_config") and hasattr(instance._config, "model"):
179
+ return instance._config.endpoint.split("/")[-1]
180
+
181
+ return ""
182
+ except Exception as e:
183
+ logger.warning("Warning: Error occurred in get_model_name: %s", str(e))
184
+ return ""
185
+
186
+
187
+ def get_inference_type(arguments) -> str:
188
+ instance = arguments.get("instance")
189
+ if instance and hasattr(instance, "_config") and hasattr(instance._config, "endpoint"):
190
+ endpoint = instance._config.endpoint
191
+ try:
192
+ parsed = urlparse(endpoint)
193
+ hostname = parsed.hostname or endpoint
194
+ hostname = hostname.lower()
195
+ except Exception:
196
+ hostname = str(endpoint).lower()
197
+ if hostname.endswith("services.ai.azure.com"):
198
+ return "azure_ai_inference"
199
+ if hostname.endswith("openai.azure.com"):
200
+ return "azure_openai"
201
+ return "azure_ai_inference"
202
+
203
+
204
+ def get_provider_name(instance: Any) -> str:
205
+ """Extract provider name from azure-ai-inference client instance."""
206
+ try:
207
+ # extract hostname from instance._config.endpoint
208
+ # https://okahu-openai-dev.openai.azure.com/openai/deployments/kshitiz-gpt => okahu-openai-dev.openai.azure.com
209
+ endpoint = instance._config.endpoint
210
+ if endpoint:
211
+ # Extract the hostname part
212
+ provider_name = endpoint.split("/")[2] if "/" in endpoint else endpoint
213
+ return provider_name
214
+ except Exception as e:
215
+ logger.warning("Warning: Error occurred in get_provider_name: %s", str(e))
216
+ return "azure_ai_inference"