Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,9 @@ def _wrap(
)
elif response:
try:
metric_attributes = shared_metrics_attributes(response)
metric_attributes = shared_metrics_attributes(
response, request_model=kwargs.get("model")
)

if duration_histogram:
duration = time.time() - start_time
Expand Down Expand Up @@ -755,7 +757,9 @@ async def _awrap(
ashared_metrics_attributes,
)

metric_attributes = await ashared_metrics_attributes(response)
metric_attributes = await ashared_metrics_attributes(
response, request_model=kwargs.get("model")
)

if duration_histogram:
duration = time.time() - start_time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ def __next__(self): # FR: rewritten for pending-item safety buffer
# the duration_histogram conditional, causing metrics to be recorded twice.
def _handle_completion(self):
"""Handle completion logic"""
metric_attributes = shared_metrics_attributes(self._complete_response)
metric_attributes = shared_metrics_attributes(
self._complete_response,
request_model=self._kwargs.get("model"),
)
set_span_attribute(
self._span,
GenAIAttributes.GEN_AI_RESPONSE_ID,
Expand Down Expand Up @@ -451,7 +454,10 @@ def _complete_instrumentation(self):
return

# This mirrors the logic from abuild_from_streaming_response
metric_attributes = shared_metrics_attributes(self._complete_response)
metric_attributes = shared_metrics_attributes(
self._complete_response,
request_model=self._kwargs.get("model"),
)
set_span_attribute(self._span, GenAIAttributes.GEN_AI_RESPONSE_ID, self._complete_response.get("id"))

if self._duration_histogram:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _extract_response_data(response):


@dont_throw
async def ashared_metrics_attributes(response):
async def ashared_metrics_attributes(response, request_model=None):
import inspect

# If we get a coroutine, await it
Expand Down Expand Up @@ -168,15 +168,19 @@ async def ashared_metrics_attributes(response):

common_attributes = Config.get_common_metrics_attributes()

return {
metric_attributes = {
**common_attributes,
GenAIAttributes.GEN_AI_SYSTEM: GEN_AI_SYSTEM_ANTHROPIC,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model,
}
if request_model:
metric_attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL] = request_model

return metric_attributes


@dont_throw
def shared_metrics_attributes(response):
def shared_metrics_attributes(response, request_model=None):
import inspect

# If we get a coroutine, we cannot process it in sync context
Expand Down Expand Up @@ -205,11 +209,15 @@ def shared_metrics_attributes(response):

common_attributes = Config.get_common_metrics_attributes()

return {
metric_attributes = {
**common_attributes,
GenAIAttributes.GEN_AI_SYSTEM: GEN_AI_SYSTEM_ANTHROPIC,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model,
}
if request_model:
metric_attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL] = request_model

return metric_attributes


@dont_throw
Expand Down
14 changes: 14 additions & 0 deletions packages/opentelemetry-instrumentation-anthropic/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def verify_metrics(
data_point.attributes[GenAIAttributes.GEN_AI_RESPONSE_MODEL]
== model_name
)
assert (
data_point.attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL]
== model_name
)
if not ignore_zero_input_tokens:
assert data_point.sum > 0

Expand All @@ -38,6 +42,10 @@ def verify_metrics(
data_point.attributes[GenAIAttributes.GEN_AI_RESPONSE_MODEL]
== model_name
)
assert (
data_point.attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL]
== model_name
)

if metric.name == Meters.LLM_OPERATION_DURATION:
found_duration_metric = True
Expand All @@ -53,6 +61,12 @@ def verify_metrics(
or data_point.attributes.get("error.type") == "TypeError"
for data_point in metric.data.data_points
)
assert all(
data_point.attributes.get(GenAIAttributes.GEN_AI_REQUEST_MODEL)
== model_name
or data_point.attributes.get("error.type") == "TypeError"
for data_point in metric.data.data_points
)

if metric.name == Meters.LLM_ANTHROPIC_COMPLETION_EXCEPTIONS:
found_exception_metric = True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ def _metric_shared_attributes(
):
return {
"vendor": response_vendor,
GenAIAttributes.GEN_AI_REQUEST_MODEL: response_model,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: response_model,
GenAIAttributes.GEN_AI_SYSTEM: "bedrock",
"stream": is_streaming,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def test_invoke_model_metrics(test_context, brt):

found_token_metric = False
found_duration_metric = False
expected_request_models = {
"amazon.titan-text-express-v1",
"titan-text-express-v1",
}

for rm in resource_metrics:
for sm in rm.scope_metrics:
Expand All @@ -51,6 +55,10 @@ def test_invoke_model_metrics(test_context, brt):
"output",
"input",
]
assert (
data_point.attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL]
in expected_request_models
)
assert data_point.sum > 0

if metric.name == Meters.LLM_OPERATION_DURATION:
Expand All @@ -61,6 +69,18 @@ def test_invoke_model_metrics(test_context, brt):
assert any(
data_point.sum > 0 for data_point in metric.data.data_points
)
model_points = [
data_point
for data_point in metric.data.data_points
if GenAIAttributes.GEN_AI_REQUEST_MODEL
in data_point.attributes
]
assert model_points
assert all(
data_point.attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL]
in expected_request_models
for data_point in model_points
)

if metric.name in (Meters.LLM_TOKEN_USAGE, Meters.LLM_OPERATION_DURATION):
assert (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ async def _awrap(
duration,
attributes={
GenAIAttributes.GEN_AI_PROVIDER_NAME: "Google",
GenAIAttributes.GEN_AI_REQUEST_MODEL: llm_model,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: llm_model,
},
)
Expand Down Expand Up @@ -318,6 +319,7 @@ def _wrap(
duration,
attributes={
GenAIAttributes.GEN_AI_PROVIDER_NAME: "Google",
GenAIAttributes.GEN_AI_REQUEST_MODEL: llm_model,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: llm_model,
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ def set_model_response_attributes(span, response, llm_model, token_histogram):
attributes={
GenAIAttributes.GEN_AI_PROVIDER_NAME: "Google",
GenAIAttributes.GEN_AI_TOKEN_TYPE: "input",
GenAIAttributes.GEN_AI_REQUEST_MODEL: llm_model,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: llm_model,
}
)
Expand All @@ -483,6 +484,7 @@ def set_model_response_attributes(span, response, llm_model, token_histogram):
attributes={
GenAIAttributes.GEN_AI_PROVIDER_NAME: "Google",
GenAIAttributes.GEN_AI_TOKEN_TYPE: "output",
GenAIAttributes.GEN_AI_REQUEST_MODEL: llm_model,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: llm_model,
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ def test_generate_metrics(metrics_test_context, genai_client):
# Required attributes (values are intentionally not hard-coded)
assert GenAIAttributes.GEN_AI_PROVIDER_NAME in duration_dp.attributes
assert GenAIAttributes.GEN_AI_RESPONSE_MODEL in duration_dp.attributes
assert (
duration_dp.attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL]
== "gemini-2.5-flash"
)

# ---- Token metric (only emitted when response includes usage_metadata) ----
if Meters.LLM_TOKEN_USAGE in metrics:
Expand All @@ -120,3 +124,7 @@ def test_generate_metrics(metrics_test_context, genai_client):
# Required semantic attributes
assert GenAIAttributes.GEN_AI_PROVIDER_NAME in dp.attributes
assert GenAIAttributes.GEN_AI_RESPONSE_MODEL in dp.attributes
assert (
dp.attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL]
== "gemini-2.5-flash"
)
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ def on_llm_end(
attributes={
GenAIAttributes.GEN_AI_SYSTEM: vendor,
GenAIAttributes.GEN_AI_TOKEN_TYPE: "input",
GenAIAttributes.GEN_AI_REQUEST_MODEL: model_name or "unknown",
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model_name or "unknown",
},
)
Expand All @@ -678,6 +679,7 @@ def on_llm_end(
attributes={
GenAIAttributes.GEN_AI_SYSTEM: vendor,
GenAIAttributes.GEN_AI_TOKEN_TYPE: "output",
GenAIAttributes.GEN_AI_REQUEST_MODEL: model_name or "unknown",
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model_name or "unknown",
},
)
Expand All @@ -698,6 +700,7 @@ def on_llm_end(
duration,
attributes={
GenAIAttributes.GEN_AI_SYSTEM: vendor,
GenAIAttributes.GEN_AI_REQUEST_MODEL: model_name or "unknown",
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model_name or "unknown",
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def set_chat_response_usage(
attributes={
GenAIAttributes.GEN_AI_SYSTEM: vendor,
GenAIAttributes.GEN_AI_TOKEN_TYPE: "input",
GenAIAttributes.GEN_AI_REQUEST_MODEL: model_name,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model_name,
},
)
Expand All @@ -383,6 +384,7 @@ def set_chat_response_usage(
attributes={
GenAIAttributes.GEN_AI_SYSTEM: vendor,
GenAIAttributes.GEN_AI_TOKEN_TYPE: "output",
GenAIAttributes.GEN_AI_REQUEST_MODEL: model_name,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: model_name,
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,19 +373,27 @@ def _token_type(token_type: str):


def metric_shared_attributes(
response_model: str, operation: str, server_address: str, is_streaming: bool = False
response_model: str,
operation: str,
server_address: str,
is_streaming: bool = False,
request_model: str = None,
):
attributes = Config.get_common_metrics_attributes()
vendor = _get_vendor_from_url(server_address)

return {
metric_attributes = {
**attributes,
GenAIAttributes.GEN_AI_SYSTEM: vendor,
GenAIAttributes.GEN_AI_RESPONSE_MODEL: response_model,
"gen_ai.operation.name": operation,
"server.address": server_address,
"stream": is_streaming,
}
if request_model:
metric_attributes[GenAIAttributes.GEN_AI_REQUEST_MODEL] = request_model

return metric_attributes


def propagate_trace_context(span, kwargs):
Expand Down
Loading
Loading