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.
- monocle_apptrace/instrumentation/__init__.py +2 -1
- monocle_apptrace/instrumentation/common/constants.py +3 -0
- monocle_apptrace/instrumentation/common/instrumentor.py +1 -1
- monocle_apptrace/instrumentation/common/span_handler.py +1 -1
- monocle_apptrace/instrumentation/common/utils.py +20 -2
- monocle_apptrace/instrumentation/common/wrapper_method.py +5 -1
- monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +29 -4
- monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +12 -2
- monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +78 -0
- monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +51 -0
- monocle_apptrace/instrumentation/metamodel/azfunc/methods.py +23 -0
- monocle_apptrace/instrumentation/metamodel/azfunc/wrapper.py +23 -0
- monocle_apptrace/instrumentation/metamodel/azureaiinference/__init__.py +1 -0
- monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +216 -0
- monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +208 -0
- monocle_apptrace/instrumentation/metamodel/azureaiinference/methods.py +23 -0
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +42 -17
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +11 -3
- monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +20 -12
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +10 -2
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +19 -13
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +10 -2
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +21 -13
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +10 -2
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +17 -9
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +3 -2
- monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +50 -4
- monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +32 -12
- monocle_apptrace/instrumentation/metamodel/teamsai/methods.py +30 -17
- monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +448 -0
- {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/METADATA +1 -1
- {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/RECORD +35 -26
- {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.4.0b3.dist-info → monocle_apptrace-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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"
|