lmnr 0.6.18__py3-none-any.whl → 0.6.20__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.
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +55 -20
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +23 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +442 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1024 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +297 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +308 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +185 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +358 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +319 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +132 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +626 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +1 -3
- lmnr/sdk/browser/browser_use_otel.py +1 -1
- lmnr/sdk/browser/patchright_otel.py +0 -14
- lmnr/sdk/browser/playwright_otel.py +16 -130
- lmnr/sdk/browser/pw_utils.py +45 -31
- lmnr/sdk/client/asynchronous/async_client.py +13 -0
- lmnr/sdk/client/asynchronous/resources/__init__.py +2 -0
- lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/asynchronous/resources/tags.py +4 -10
- lmnr/sdk/client/synchronous/resources/__init__.py +2 -1
- lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/synchronous/resources/tags.py +4 -10
- lmnr/sdk/client/synchronous/sync_client.py +14 -0
- lmnr/sdk/utils.py +23 -0
- lmnr/version.py +1 -1
- {lmnr-0.6.18.dist-info → lmnr-0.6.20.dist-info}/METADATA +2 -5
- {lmnr-0.6.18.dist-info → lmnr-0.6.20.dist-info}/RECORD +37 -18
- {lmnr-0.6.18.dist-info → lmnr-0.6.20.dist-info}/WHEEL +1 -1
- {lmnr-0.6.18.dist-info → lmnr-0.6.20.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
from dataclasses import asdict
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Union
|
4
|
+
|
5
|
+
from opentelemetry._events import Event
|
6
|
+
from ..shared.event_models import (
|
7
|
+
ChoiceEvent,
|
8
|
+
MessageEvent,
|
9
|
+
)
|
10
|
+
from ..utils import (
|
11
|
+
should_emit_events,
|
12
|
+
should_send_prompts,
|
13
|
+
)
|
14
|
+
from opentelemetry.semconv._incubating.attributes import (
|
15
|
+
gen_ai_attributes as GenAIAttributes,
|
16
|
+
)
|
17
|
+
|
18
|
+
from .config import Config
|
19
|
+
|
20
|
+
|
21
|
+
class Roles(Enum):
|
22
|
+
USER = "user"
|
23
|
+
ASSISTANT = "assistant"
|
24
|
+
SYSTEM = "system"
|
25
|
+
TOOL = "tool"
|
26
|
+
|
27
|
+
|
28
|
+
VALID_MESSAGE_ROLES = {role.value for role in Roles}
|
29
|
+
"""The valid roles for naming the message event."""
|
30
|
+
|
31
|
+
EVENT_ATTRIBUTES = {
|
32
|
+
GenAIAttributes.GEN_AI_SYSTEM: GenAIAttributes.GenAiSystemValues.OPENAI.value
|
33
|
+
}
|
34
|
+
"""The attributes to be used for the event."""
|
35
|
+
|
36
|
+
|
37
|
+
def emit_event(event: Union[MessageEvent, ChoiceEvent]) -> None:
|
38
|
+
"""
|
39
|
+
Emit an event to the OpenTelemetry SDK.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
event: The event to emit.
|
43
|
+
"""
|
44
|
+
if not should_emit_events():
|
45
|
+
return
|
46
|
+
|
47
|
+
if isinstance(event, MessageEvent):
|
48
|
+
_emit_message_event(event)
|
49
|
+
elif isinstance(event, ChoiceEvent):
|
50
|
+
_emit_choice_event(event)
|
51
|
+
else:
|
52
|
+
raise TypeError("Unsupported event type")
|
53
|
+
|
54
|
+
|
55
|
+
def _emit_message_event(event: MessageEvent) -> None:
|
56
|
+
body = asdict(event)
|
57
|
+
|
58
|
+
if event.role in VALID_MESSAGE_ROLES:
|
59
|
+
name = "gen_ai.{}.message".format(event.role)
|
60
|
+
# According to the semantic conventions, the role is conditionally required if available
|
61
|
+
# and not equal to the "role" in the message name. So, remove the role from the body if
|
62
|
+
# it is the same as the in the event name.
|
63
|
+
body.pop("role", None)
|
64
|
+
else:
|
65
|
+
name = "gen_ai.user.message"
|
66
|
+
|
67
|
+
# According to the semantic conventions, only the assistant role has tool call
|
68
|
+
if event.role != Roles.ASSISTANT.value and event.tool_calls is not None:
|
69
|
+
del body["tool_calls"]
|
70
|
+
elif event.tool_calls is None:
|
71
|
+
del body["tool_calls"]
|
72
|
+
|
73
|
+
if not should_send_prompts():
|
74
|
+
del body["content"]
|
75
|
+
if body.get("tool_calls") is not None:
|
76
|
+
for tool_call in body["tool_calls"]:
|
77
|
+
tool_call["function"].pop("arguments", None)
|
78
|
+
|
79
|
+
Config.event_logger.emit(Event(name=name, body=body, attributes=EVENT_ATTRIBUTES))
|
80
|
+
|
81
|
+
|
82
|
+
def _emit_choice_event(event: ChoiceEvent) -> None:
|
83
|
+
body = asdict(event)
|
84
|
+
if event.message["role"] == Roles.ASSISTANT.value:
|
85
|
+
# According to the semantic conventions, the role is conditionally required if available
|
86
|
+
# and not equal to "assistant", so remove the role from the body if it is "assistant".
|
87
|
+
body["message"].pop("role", None)
|
88
|
+
|
89
|
+
if event.tool_calls is None:
|
90
|
+
del body["tool_calls"]
|
91
|
+
|
92
|
+
if not should_send_prompts():
|
93
|
+
body["message"].pop("content", None)
|
94
|
+
if body.get("tool_calls") is not None:
|
95
|
+
for tool_call in body["tool_calls"]:
|
96
|
+
tool_call["function"].pop("arguments", None)
|
97
|
+
|
98
|
+
Config.event_logger.emit(
|
99
|
+
Event(name="gen_ai.choice", body=body, attributes=EVENT_ATTRIBUTES)
|
100
|
+
)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Any, List, Literal, Optional, TypedDict
|
3
|
+
|
4
|
+
|
5
|
+
class _FunctionToolCall(TypedDict):
|
6
|
+
function_name: str
|
7
|
+
arguments: Optional[dict[str, Any]]
|
8
|
+
|
9
|
+
|
10
|
+
class ToolCall(TypedDict):
|
11
|
+
"""Represents a tool call in the AI model."""
|
12
|
+
|
13
|
+
id: str
|
14
|
+
function: _FunctionToolCall
|
15
|
+
type: Literal["function"]
|
16
|
+
|
17
|
+
|
18
|
+
class CompletionMessage(TypedDict):
|
19
|
+
"""Represents a message in the AI model."""
|
20
|
+
|
21
|
+
content: Any
|
22
|
+
role: str = "assistant"
|
23
|
+
|
24
|
+
|
25
|
+
@dataclass
|
26
|
+
class MessageEvent:
|
27
|
+
"""Represents an input event for the AI model."""
|
28
|
+
|
29
|
+
content: Any
|
30
|
+
role: str = "user"
|
31
|
+
tool_calls: Optional[List[ToolCall]] = None
|
32
|
+
|
33
|
+
|
34
|
+
@dataclass
|
35
|
+
class ChoiceEvent:
|
36
|
+
"""Represents a completion event for the AI model."""
|
37
|
+
|
38
|
+
index: int
|
39
|
+
message: CompletionMessage
|
40
|
+
finish_reason: str = "unknown"
|
41
|
+
tool_calls: Optional[List[ToolCall]] = None
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from opentelemetry import context as context_api
|
4
|
+
from ..utils import is_openai_v1
|
5
|
+
from ..shared import (
|
6
|
+
_get_openai_base_url,
|
7
|
+
metric_shared_attributes,
|
8
|
+
model_as_dict,
|
9
|
+
)
|
10
|
+
from ..utils import (
|
11
|
+
_with_image_gen_metric_wrapper,
|
12
|
+
)
|
13
|
+
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
14
|
+
from opentelemetry.metrics import Counter, Histogram
|
15
|
+
from opentelemetry.semconv_ai import SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY
|
16
|
+
|
17
|
+
|
18
|
+
@_with_image_gen_metric_wrapper
|
19
|
+
def image_gen_metrics_wrapper(
|
20
|
+
duration_histogram: Histogram,
|
21
|
+
exception_counter: Counter,
|
22
|
+
wrapped,
|
23
|
+
instance,
|
24
|
+
args,
|
25
|
+
kwargs,
|
26
|
+
):
|
27
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY) or context_api.get_value(
|
28
|
+
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY
|
29
|
+
):
|
30
|
+
return wrapped(*args, **kwargs)
|
31
|
+
|
32
|
+
try:
|
33
|
+
# record time for duration
|
34
|
+
start_time = time.time()
|
35
|
+
response = wrapped(*args, **kwargs)
|
36
|
+
end_time = time.time()
|
37
|
+
except Exception as e: # pylint: disable=broad-except
|
38
|
+
end_time = time.time()
|
39
|
+
duration = end_time - start_time if "start_time" in locals() else 0
|
40
|
+
|
41
|
+
attributes = {
|
42
|
+
"error.type": e.__class__.__name__,
|
43
|
+
}
|
44
|
+
|
45
|
+
if duration > 0 and duration_histogram:
|
46
|
+
duration_histogram.record(duration, attributes=attributes)
|
47
|
+
if exception_counter:
|
48
|
+
exception_counter.add(1, attributes=attributes)
|
49
|
+
|
50
|
+
raise
|
51
|
+
|
52
|
+
if is_openai_v1():
|
53
|
+
response_dict = model_as_dict(response)
|
54
|
+
else:
|
55
|
+
response_dict = response
|
56
|
+
|
57
|
+
# not provide response.model in ImagesResponse response, use model in request kwargs
|
58
|
+
shared_attributes = metric_shared_attributes(
|
59
|
+
response_model=kwargs.get("model") or None,
|
60
|
+
operation="image_gen",
|
61
|
+
server_address=_get_openai_base_url(instance),
|
62
|
+
)
|
63
|
+
|
64
|
+
duration = end_time - start_time
|
65
|
+
if duration_histogram:
|
66
|
+
duration_histogram.record(duration, attributes=shared_attributes)
|
67
|
+
|
68
|
+
return response
|
@@ -0,0 +1,185 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
import threading
|
5
|
+
import traceback
|
6
|
+
from contextlib import asynccontextmanager
|
7
|
+
from importlib.metadata import version
|
8
|
+
|
9
|
+
from opentelemetry import context as context_api
|
10
|
+
from opentelemetry._events import EventLogger
|
11
|
+
from .shared.config import Config
|
12
|
+
|
13
|
+
import openai
|
14
|
+
|
15
|
+
_OPENAI_VERSION = version("openai")
|
16
|
+
|
17
|
+
TRACELOOP_TRACE_CONTENT = "TRACELOOP_TRACE_CONTENT"
|
18
|
+
|
19
|
+
|
20
|
+
def is_openai_v1():
|
21
|
+
return _OPENAI_VERSION >= "1.0.0"
|
22
|
+
|
23
|
+
|
24
|
+
def is_azure_openai(instance):
|
25
|
+
return is_openai_v1() and isinstance(
|
26
|
+
instance._client, (openai.AsyncAzureOpenAI, openai.AzureOpenAI)
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
def is_metrics_enabled() -> bool:
|
31
|
+
return (os.getenv("TRACELOOP_METRICS_ENABLED") or "true").lower() == "true"
|
32
|
+
|
33
|
+
|
34
|
+
def should_record_stream_token_usage():
|
35
|
+
return Config.enrich_token_usage
|
36
|
+
|
37
|
+
|
38
|
+
def _with_image_gen_metric_wrapper(func):
|
39
|
+
def _with_metric(duration_histogram, exception_counter):
|
40
|
+
def wrapper(wrapped, instance, args, kwargs):
|
41
|
+
return func(
|
42
|
+
duration_histogram,
|
43
|
+
exception_counter,
|
44
|
+
wrapped,
|
45
|
+
instance,
|
46
|
+
args,
|
47
|
+
kwargs,
|
48
|
+
)
|
49
|
+
|
50
|
+
return wrapper
|
51
|
+
|
52
|
+
return _with_metric
|
53
|
+
|
54
|
+
|
55
|
+
def _with_embeddings_telemetry_wrapper(func):
|
56
|
+
def _with_embeddings_telemetry(
|
57
|
+
tracer,
|
58
|
+
token_counter,
|
59
|
+
vector_size_counter,
|
60
|
+
duration_histogram,
|
61
|
+
exception_counter,
|
62
|
+
):
|
63
|
+
def wrapper(wrapped, instance, args, kwargs):
|
64
|
+
return func(
|
65
|
+
tracer,
|
66
|
+
token_counter,
|
67
|
+
vector_size_counter,
|
68
|
+
duration_histogram,
|
69
|
+
exception_counter,
|
70
|
+
wrapped,
|
71
|
+
instance,
|
72
|
+
args,
|
73
|
+
kwargs,
|
74
|
+
)
|
75
|
+
|
76
|
+
return wrapper
|
77
|
+
|
78
|
+
return _with_embeddings_telemetry
|
79
|
+
|
80
|
+
|
81
|
+
def _with_chat_telemetry_wrapper(func):
|
82
|
+
def _with_chat_telemetry(
|
83
|
+
tracer,
|
84
|
+
token_counter,
|
85
|
+
choice_counter,
|
86
|
+
duration_histogram,
|
87
|
+
exception_counter,
|
88
|
+
streaming_time_to_first_token,
|
89
|
+
streaming_time_to_generate,
|
90
|
+
):
|
91
|
+
def wrapper(wrapped, instance, args, kwargs):
|
92
|
+
return func(
|
93
|
+
tracer,
|
94
|
+
token_counter,
|
95
|
+
choice_counter,
|
96
|
+
duration_histogram,
|
97
|
+
exception_counter,
|
98
|
+
streaming_time_to_first_token,
|
99
|
+
streaming_time_to_generate,
|
100
|
+
wrapped,
|
101
|
+
instance,
|
102
|
+
args,
|
103
|
+
kwargs,
|
104
|
+
)
|
105
|
+
|
106
|
+
return wrapper
|
107
|
+
|
108
|
+
return _with_chat_telemetry
|
109
|
+
|
110
|
+
|
111
|
+
def _with_tracer_wrapper(func):
|
112
|
+
def _with_tracer(tracer):
|
113
|
+
def wrapper(wrapped, instance, args, kwargs):
|
114
|
+
return func(tracer, wrapped, instance, args, kwargs)
|
115
|
+
|
116
|
+
return wrapper
|
117
|
+
|
118
|
+
return _with_tracer
|
119
|
+
|
120
|
+
|
121
|
+
@asynccontextmanager
|
122
|
+
async def start_as_current_span_async(tracer, *args, **kwargs):
|
123
|
+
with tracer.start_as_current_span(*args, **kwargs) as span:
|
124
|
+
yield span
|
125
|
+
|
126
|
+
|
127
|
+
def dont_throw(func):
|
128
|
+
"""
|
129
|
+
A decorator that wraps the passed in function and logs exceptions instead of throwing them.
|
130
|
+
Works for both synchronous and asynchronous functions.
|
131
|
+
"""
|
132
|
+
logger = logging.getLogger(func.__module__)
|
133
|
+
|
134
|
+
async def async_wrapper(*args, **kwargs):
|
135
|
+
try:
|
136
|
+
return await func(*args, **kwargs)
|
137
|
+
except Exception as e:
|
138
|
+
_handle_exception(e, func, logger)
|
139
|
+
|
140
|
+
def sync_wrapper(*args, **kwargs):
|
141
|
+
try:
|
142
|
+
return func(*args, **kwargs)
|
143
|
+
except Exception as e:
|
144
|
+
_handle_exception(e, func, logger)
|
145
|
+
|
146
|
+
def _handle_exception(e, func, logger):
|
147
|
+
logger.debug(
|
148
|
+
"OpenLLMetry failed to trace in %s, error: %s",
|
149
|
+
func.__name__,
|
150
|
+
traceback.format_exc(),
|
151
|
+
)
|
152
|
+
if Config.exception_logger:
|
153
|
+
Config.exception_logger(e)
|
154
|
+
|
155
|
+
return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
|
156
|
+
|
157
|
+
|
158
|
+
def run_async(method):
|
159
|
+
try:
|
160
|
+
loop = asyncio.get_running_loop()
|
161
|
+
except RuntimeError:
|
162
|
+
loop = None
|
163
|
+
|
164
|
+
if loop and loop.is_running():
|
165
|
+
thread = threading.Thread(target=lambda: asyncio.run(method))
|
166
|
+
thread.start()
|
167
|
+
thread.join()
|
168
|
+
else:
|
169
|
+
asyncio.run(method)
|
170
|
+
|
171
|
+
|
172
|
+
def should_send_prompts():
|
173
|
+
return (
|
174
|
+
os.getenv(TRACELOOP_TRACE_CONTENT) or "true"
|
175
|
+
).lower() == "true" or context_api.get_value("override_enable_content_tracing")
|
176
|
+
|
177
|
+
|
178
|
+
def should_emit_events() -> bool:
|
179
|
+
"""
|
180
|
+
Checks if the instrumentation isn't using the legacy attributes
|
181
|
+
and if the event logger is not None.
|
182
|
+
"""
|
183
|
+
return not Config.use_legacy_attributes and isinstance(
|
184
|
+
Config.event_logger, EventLogger
|
185
|
+
)
|
@@ -0,0 +1,176 @@
|
|
1
|
+
from typing import Collection
|
2
|
+
|
3
|
+
from opentelemetry._events import get_event_logger
|
4
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
5
|
+
from ..shared.chat_wrappers import (
|
6
|
+
achat_wrapper,
|
7
|
+
chat_wrapper,
|
8
|
+
)
|
9
|
+
from ..shared.completion_wrappers import (
|
10
|
+
acompletion_wrapper,
|
11
|
+
completion_wrapper,
|
12
|
+
)
|
13
|
+
from ..shared.config import Config
|
14
|
+
from ..shared.embeddings_wrappers import (
|
15
|
+
aembeddings_wrapper,
|
16
|
+
embeddings_wrapper,
|
17
|
+
)
|
18
|
+
from ..utils import is_metrics_enabled
|
19
|
+
from ..version import __version__
|
20
|
+
from opentelemetry.instrumentation.utils import unwrap
|
21
|
+
from opentelemetry.metrics import get_meter
|
22
|
+
from opentelemetry.semconv._incubating.metrics import gen_ai_metrics as GenAIMetrics
|
23
|
+
from opentelemetry.semconv_ai import Meters
|
24
|
+
from opentelemetry.trace import get_tracer
|
25
|
+
from wrapt import wrap_function_wrapper
|
26
|
+
|
27
|
+
_instruments = ("openai >= 0.27.0", "openai < 1.0.0")
|
28
|
+
|
29
|
+
|
30
|
+
class OpenAIV0Instrumentor(BaseInstrumentor):
|
31
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
32
|
+
return _instruments
|
33
|
+
|
34
|
+
def _instrument(self, **kwargs):
|
35
|
+
tracer_provider = kwargs.get("tracer_provider")
|
36
|
+
tracer = get_tracer(__name__, __version__, tracer_provider)
|
37
|
+
|
38
|
+
meter_provider = kwargs.get("meter_provider")
|
39
|
+
meter = get_meter(__name__, __version__, meter_provider)
|
40
|
+
|
41
|
+
if not Config.use_legacy_attributes:
|
42
|
+
event_logger_provider = kwargs.get("event_logger_provider")
|
43
|
+
Config.event_logger = get_event_logger(
|
44
|
+
__name__, __version__, event_logger_provider=event_logger_provider
|
45
|
+
)
|
46
|
+
|
47
|
+
if is_metrics_enabled():
|
48
|
+
tokens_histogram = meter.create_histogram(
|
49
|
+
name=Meters.LLM_TOKEN_USAGE,
|
50
|
+
unit="token",
|
51
|
+
description="Measures number of input and output tokens used",
|
52
|
+
)
|
53
|
+
|
54
|
+
chat_choice_counter = meter.create_counter(
|
55
|
+
name=Meters.LLM_GENERATION_CHOICES,
|
56
|
+
unit="choice",
|
57
|
+
description="Number of choices returned by chat completions call",
|
58
|
+
)
|
59
|
+
|
60
|
+
duration_histogram = meter.create_histogram(
|
61
|
+
name=Meters.LLM_OPERATION_DURATION,
|
62
|
+
unit="s",
|
63
|
+
description="GenAI operation duration",
|
64
|
+
)
|
65
|
+
|
66
|
+
chat_exception_counter = meter.create_counter(
|
67
|
+
name=Meters.LLM_COMPLETIONS_EXCEPTIONS,
|
68
|
+
unit="time",
|
69
|
+
description="Number of exceptions occurred during chat completions",
|
70
|
+
)
|
71
|
+
|
72
|
+
streaming_time_to_first_token = meter.create_histogram(
|
73
|
+
name=GenAIMetrics.GEN_AI_SERVER_TIME_TO_FIRST_TOKEN,
|
74
|
+
unit="s",
|
75
|
+
description="Time to first token in streaming chat completions",
|
76
|
+
)
|
77
|
+
streaming_time_to_generate = meter.create_histogram(
|
78
|
+
name=Meters.LLM_STREAMING_TIME_TO_GENERATE,
|
79
|
+
unit="s",
|
80
|
+
description="Time between first token and completion in streaming chat completions",
|
81
|
+
)
|
82
|
+
else:
|
83
|
+
(
|
84
|
+
tokens_histogram,
|
85
|
+
chat_choice_counter,
|
86
|
+
duration_histogram,
|
87
|
+
chat_exception_counter,
|
88
|
+
streaming_time_to_first_token,
|
89
|
+
streaming_time_to_generate,
|
90
|
+
) = (None, None, None, None, None, None)
|
91
|
+
|
92
|
+
if is_metrics_enabled():
|
93
|
+
embeddings_vector_size_counter = meter.create_counter(
|
94
|
+
name=Meters.LLM_EMBEDDINGS_VECTOR_SIZE,
|
95
|
+
unit="element",
|
96
|
+
description="he size of returned vector",
|
97
|
+
)
|
98
|
+
embeddings_exception_counter = meter.create_counter(
|
99
|
+
name=Meters.LLM_EMBEDDINGS_EXCEPTIONS,
|
100
|
+
unit="time",
|
101
|
+
description="Number of exceptions occurred during embeddings operation",
|
102
|
+
)
|
103
|
+
else:
|
104
|
+
(
|
105
|
+
tokens_histogram,
|
106
|
+
embeddings_vector_size_counter,
|
107
|
+
embeddings_exception_counter,
|
108
|
+
) = (None, None, None)
|
109
|
+
|
110
|
+
wrap_function_wrapper(
|
111
|
+
"openai",
|
112
|
+
"Completion.create",
|
113
|
+
completion_wrapper(tracer),
|
114
|
+
)
|
115
|
+
|
116
|
+
wrap_function_wrapper(
|
117
|
+
"openai",
|
118
|
+
"Completion.acreate",
|
119
|
+
acompletion_wrapper(tracer),
|
120
|
+
)
|
121
|
+
wrap_function_wrapper(
|
122
|
+
"openai",
|
123
|
+
"ChatCompletion.create",
|
124
|
+
chat_wrapper(
|
125
|
+
tracer,
|
126
|
+
tokens_histogram,
|
127
|
+
chat_choice_counter,
|
128
|
+
duration_histogram,
|
129
|
+
chat_exception_counter,
|
130
|
+
streaming_time_to_first_token,
|
131
|
+
streaming_time_to_generate,
|
132
|
+
),
|
133
|
+
)
|
134
|
+
wrap_function_wrapper(
|
135
|
+
"openai",
|
136
|
+
"ChatCompletion.acreate",
|
137
|
+
achat_wrapper(
|
138
|
+
tracer,
|
139
|
+
tokens_histogram,
|
140
|
+
chat_choice_counter,
|
141
|
+
duration_histogram,
|
142
|
+
chat_exception_counter,
|
143
|
+
streaming_time_to_first_token,
|
144
|
+
streaming_time_to_generate,
|
145
|
+
),
|
146
|
+
)
|
147
|
+
wrap_function_wrapper(
|
148
|
+
"openai",
|
149
|
+
"Embedding.create",
|
150
|
+
embeddings_wrapper(
|
151
|
+
tracer,
|
152
|
+
tokens_histogram,
|
153
|
+
embeddings_vector_size_counter,
|
154
|
+
duration_histogram,
|
155
|
+
embeddings_exception_counter,
|
156
|
+
),
|
157
|
+
)
|
158
|
+
wrap_function_wrapper(
|
159
|
+
"openai",
|
160
|
+
"Embedding.acreate",
|
161
|
+
aembeddings_wrapper(
|
162
|
+
tracer,
|
163
|
+
tokens_histogram,
|
164
|
+
embeddings_vector_size_counter,
|
165
|
+
duration_histogram,
|
166
|
+
embeddings_exception_counter,
|
167
|
+
),
|
168
|
+
)
|
169
|
+
|
170
|
+
def _uninstrument(self, **kwargs):
|
171
|
+
unwrap("openai", "Completion.create")
|
172
|
+
unwrap("openai", "Completion.acreate")
|
173
|
+
unwrap("openai", "ChatCompletion.create")
|
174
|
+
unwrap("openai", "ChatCompletion.acreate")
|
175
|
+
unwrap("openai", "Embedding.create")
|
176
|
+
unwrap("openai", "Embedding.acreate")
|