-
Notifications
You must be signed in to change notification settings - Fork 694
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Anthropic instrumentation (#71)
- Loading branch information
Showing
9 changed files
with
506 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
...entelemetry-instrumentation-anthropic/opentelemetry/instrumentation/anthropic/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,150 @@ | ||
"""OpenTelemetry Anthropic instrumentation""" | ||
import logging | ||
from typing import Collection | ||
from wrapt import wrap_function_wrapper | ||
|
||
from opentelemetry import context as context_api | ||
from opentelemetry.trace import get_tracer, SpanKind | ||
from opentelemetry.trace.status import Status, StatusCode | ||
|
||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor | ||
from opentelemetry.instrumentation.utils import ( | ||
_SUPPRESS_INSTRUMENTATION_KEY, | ||
unwrap, | ||
) | ||
|
||
from opentelemetry.semconv.ai import SpanAttributes, LLMRequestTypeValues | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
_instruments = ("anthropic >= 0.3.11",) | ||
__version__ = "0.1.0" | ||
|
||
WRAPPED_METHODS = [ | ||
{ | ||
"object": "Completions", | ||
"method": "create", | ||
"span_name": "anthropic.completion", | ||
}, | ||
] | ||
|
||
|
||
def _set_span_attribute(span, name, value): | ||
if value is not None: | ||
if value != "": | ||
span.set_attribute(name, value) | ||
return | ||
|
||
|
||
def _set_input_attributes(span, kwargs): | ||
_set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, kwargs.get("model")) | ||
_set_span_attribute( | ||
span, SpanAttributes.LLM_REQUEST_MAX_TOKENS, kwargs.get("max_tokens_to_sample") | ||
) | ||
_set_span_attribute(span, SpanAttributes.LLM_TEMPERATURE, kwargs.get("temperature")) | ||
_set_span_attribute(span, SpanAttributes.LLM_TOP_P, kwargs.get("top_p")) | ||
_set_span_attribute( | ||
span, SpanAttributes.LLM_FREQUENCY_PENALTY, kwargs.get("frequency_penalty") | ||
) | ||
_set_span_attribute( | ||
span, SpanAttributes.LLM_PRESENCE_PENALTY, kwargs.get("presence_penalty") | ||
) | ||
|
||
_set_span_attribute( | ||
span, f"{SpanAttributes.LLM_PROMPTS}.0.user", kwargs.get("prompt") | ||
) | ||
|
||
return | ||
|
||
|
||
def _set_span_completions(span, llm_request_type, completion): | ||
index = 0 | ||
prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{index}" | ||
_set_span_attribute(span, f"{prefix}.finish_reason", completion.get("stop_reason")) | ||
_set_span_attribute(span, f"{prefix}.content", completion.get("completion")) | ||
|
||
|
||
def _set_response_attributes(span, llm_request_type, response): | ||
_set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, response.get("model")) | ||
_set_span_completions(span, llm_request_type, response) | ||
|
||
|
||
def _with_tracer_wrapper(func): | ||
"""Helper for providing tracer for wrapper functions.""" | ||
|
||
def _with_tracer(tracer, to_wrap): | ||
def wrapper(wrapped, instance, args, kwargs): | ||
return func(tracer, to_wrap, wrapped, instance, args, kwargs) | ||
|
||
return wrapper | ||
|
||
return _with_tracer | ||
|
||
|
||
@_with_tracer_wrapper | ||
def _wrap(tracer, to_wrap, wrapped, instance, args, kwargs): | ||
"""Instruments and calls every function defined in TO_WRAP.""" | ||
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY): | ||
return wrapped(*args, **kwargs) | ||
|
||
name = to_wrap.get("span_name") | ||
with tracer.start_as_current_span( | ||
name, | ||
kind=SpanKind.CLIENT, | ||
attributes={ | ||
SpanAttributes.LLM_VENDOR: "Anthropic", | ||
SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.COMPLETION, | ||
}, | ||
) as span: | ||
try: | ||
if span.is_recording(): | ||
_set_input_attributes(span, kwargs) | ||
|
||
except Exception as ex: # pylint: disable=broad-except | ||
logger.warning( | ||
"Failed to set input attributes for anthropic span, error: %s", str(ex) | ||
) | ||
|
||
response = wrapped(*args, **kwargs) | ||
|
||
if response: | ||
try: | ||
if span.is_recording(): | ||
_set_response_attributes(span, response) | ||
|
||
except Exception as ex: # pylint: disable=broad-except | ||
logger.warning( | ||
"Failed to set response attributes for openai span, error: %s", | ||
str(ex), | ||
) | ||
if span.is_recording(): | ||
span.set_status(Status(StatusCode.OK)) | ||
|
||
return response | ||
|
||
|
||
class AnthropicInstrumentor(BaseInstrumentor): | ||
"""An instrumentor for Anthropic's client library.""" | ||
|
||
def instrumentation_dependencies(self) -> Collection[str]: | ||
return _instruments | ||
|
||
def _instrument(self, **kwargs): | ||
tracer_provider = kwargs.get("tracer_provider") | ||
tracer = get_tracer(__name__, __version__, tracer_provider) | ||
for wrapped_method in WRAPPED_METHODS: | ||
wrap_object = wrapped_method.get("object") | ||
wrap_method = wrapped_method.get("method") | ||
wrap_function_wrapper( | ||
"anthropic.resources.completions", | ||
f"{wrap_object}.{wrap_method}", | ||
_wrap(tracer, wrapped_method), | ||
) | ||
|
||
def _uninstrument(self, **kwargs): | ||
for wrapped_method in WRAPPED_METHODS: | ||
wrap_object = wrapped_method.get("object") | ||
unwrap( | ||
f"anthropic.resources.completions.{wrap_object}", | ||
wrapped_method.get("method"), | ||
) |
28 changes: 19 additions & 9 deletions
28
packages/opentelemetry-instrumentation-anthropic/poetry.lock
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.