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 @@ -14,8 +14,11 @@

"""Tests for sync Messages.create instrumentation."""

from importlib.metadata import version as _package_version

import pytest
from anthropic import Anthropic, APIConnectionError, NotFoundError
from packaging.version import Version

from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
from opentelemetry.semconv._incubating.attributes import (
Expand All @@ -28,6 +31,8 @@
server_attributes as ServerAttributes,
)

_anthropic_version = _package_version("anthropic")


def assert_span_attributes(
span,
Expand Down Expand Up @@ -114,6 +119,10 @@ def test_sync_messages_create_basic(
)


@pytest.mark.skipif(
Version(_anthropic_version) >= Version("1.0.0"),
reason="anthropic 1.0 removed temperature, top_p and top_k from messages.create()",
)
@pytest.mark.vcr()
def test_sync_messages_create_with_all_params(
span_exporter, anthropic_client, instrument_no_content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# the oldest supported version of external dependencies.

-e util/opentelemetry-util-genai
claude-agent-sdk==0.1.14
claude-agent-sdk==0.1.19 # 0.1.14 has been removed from PyPI; 0.1.18 is yanked
pytest==7.4.4
pytest-vcr==1.0.2
pytest-asyncio==0.21.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

- Relax version constraint of `google-genai` to allow v2 of that library to be used with the instrumentation library.
- Preserve tool parameter schemas supplied through `parameters_json_schema` in Google GenAI function declarations.

## Version 0.7b0 (2026-02-20)
- Fix bug in how tokens are counted when using the streaming `generateContent` method. ([#4152](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4152)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,14 @@ def _tool_to_tool_definition(tool: Tool) -> list[ToolDefinition]:
definitions = []
if tool.function_declarations:
for fd in tool.function_declarations:
parameters = getattr(fd, "parameters", None)
if parameters is None:
parameters = getattr(fd, "parameters_json_schema", None)
definitions.append(
FunctionToolDefinition(
name=getattr(fd, "name", type(fd).__name__),
description=getattr(fd, "description", None),
parameters=_clean_parameters(
getattr(fd, "parameters", None)
),
parameters=_clean_parameters(parameters),
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from unittest.mock import patch

from google.genai import types

from opentelemetry.instrumentation._semconv import (
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_StabilityMode,
)

from .base import TestCase

_JSON_SCHEMA = {
"type": "object",
"properties": {"city": {"type": "string", "enum": ["Paris", "London"]}},
"required": ["city"],
"additionalProperties": False,
}
_PARAMETERS = {
"type": "OBJECT",
"properties": {"city": {"type": "STRING"}},
"required": ["city"],
}


def _expected(parameters):
return [
{
"type": "function",
"name": "get_weather",
"description": "Get the weather for a city.",
"parameters": parameters,
}
]


class ToolDefinitionParametersTestCase(TestCase):
def _get_tool_definitions(self, **parameter_fields):
# Patched per call: the base setUp replaces the stability mapping dict.
with (
patch.dict(
"os.environ",
{
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "SPAN_ONLY",
"OTEL_SEMCONV_STABILITY_OPT_IN": "gen_ai_latest_experimental",
},
),
patch.dict(
_OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING,
{
_OpenTelemetryStabilitySignalType.GEN_AI: _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
},
),
):
self.configure_valid_response(text="Sunny.")
declaration = types.FunctionDeclaration(
name="get_weather",
description="Get the weather for a city.",
**parameter_fields,
)
self.client.models.generate_content(
model="gemini-2.5-flash",
contents="What is the weather in Paris?",
config=types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[declaration])]
),
)
span = self.otel.get_span_named(
"generate_content gemini-2.5-flash"
)
return json.loads(span.attributes["gen_ai.tool.definitions"])

def test_json_schema(self):
self.assertEqual(
self._get_tool_definitions(parameters_json_schema=_JSON_SCHEMA),
_expected(_JSON_SCHEMA),
)

def test_empty_json_schema(self):
self.assertEqual(
self._get_tool_definitions(parameters_json_schema={}),
_expected({}),
)

def test_parameters_wins(self):
self.assertEqual(
self._get_tool_definitions(
parameters=_PARAMETERS, parameters_json_schema=_JSON_SCHEMA
),
_expected(_PARAMETERS),
)
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def on_chat_model_start(
for sub_messages in messages:
for message in sub_messages:
# Cast to Any to avoid type checking issues with LangChain's complex content type
raw_content: Any = message.content # type: ignore[misc]
raw_content: Any = message.content
role = message.type
parts: list[Text] = []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def handle_response(
| None,
) -> None:
attributes = (
get_server_attributes(instance.api_endpoint) # type: ignore[reportUnknownMemberType]
get_server_attributes(instance.api_endpoint)
| request_attributes
| get_genai_response_attributes(response)
)
Expand Down Expand Up @@ -257,7 +257,7 @@ def _with_default_instrumentation(
kwargs: Any,
):
params = _extract_params(*args, **kwargs)
api_endpoint: str = instance.api_endpoint # type: ignore[reportUnknownMemberType]
api_endpoint: str = instance.api_endpoint
span_attributes = {
**get_genai_request_attributes(False, params),
**get_server_attributes(api_endpoint),
Expand Down
Loading