Skip to content
Closed
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `opentelemetry-docker-tests`: Replace deprecated `SpanAttributes` from `opentelemetry.semconv.trace` with `opentelemetry.semconv._incubating.attributes`
([#4339](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4339))
- `opentelemetry-instrumentation-confluent-kafka`: Skip `recv` span creation when `poll()` returns no message or `consume()` returns an empty list, avoiding empty spans on idle polls
([#4349](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4349))
- Fix intermittent `Core Contrib Test` CI failures caused by GitHub git CDN SHA propagation lag by installing core packages from the already-checked-out local copy instead of a second git clone
([#4305](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4305))
- Don't import module in unwrap if not already imported
Expand Down
6 changes: 5 additions & 1 deletion docs/performance/benchmarks.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
Performance Tests - Benchmarks
==============================

Click `here <https://open-telemetry.github.io/opentelemetry-python-contrib/benchmarks/index.html>`_ to view the latest performance benchmarks for packages in this repo.
For instructions on running and writing benchmarks locally, see the
`Benchmarks section of CONTRIBUTING.md <https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/CONTRIBUTING.md#benchmarks>`_.

Live benchmark results are published at
`opentelemetry.io/docs/languages/python/benchmarks <https://opentelemetry.io/docs/languages/python/benchmarks/>`_.
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,14 @@ classifiers = [
"Programming Language :: Python :: 3.14",
]
dependencies = [
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation ~= 0.58b0",
"opentelemetry-semantic-conventions ~= 0.58b0",
"opentelemetry-api ~= 1.39",
"opentelemetry-instrumentation ~= 0.60b0",
"opentelemetry-semantic-conventions ~= 0.60b0",
"opentelemetry-util-genai >= 0.2b0, <0.4b0",
]

[project.optional-dependencies]
instruments = [
"anthropic >= 0.51.0",
]
instruments = ["anthropic >= 0.51.0"]

[project.entry-points.opentelemetry_instrumentor]
anthropic = "opentelemetry.instrumentation.anthropic:AnthropicInstrumentor"
Expand All @@ -48,15 +46,10 @@ Repository = "https://github.com/open-telemetry/opentelemetry-python-contrib"
path = "src/opentelemetry/instrumentation/anthropic/version.py"

[tool.hatch.build.targets.sdist]
include = [
"/src",
"/tests",
"/examples",
]
include = ["/src", "/tests", "/examples"]

[tool.hatch.build.targets.wheel]
packages = ["src/opentelemetry"]

[tool.pytest.ini_options]
testpaths = ["tests"]

Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pytest==7.4.4
pytest-vcr==1.0.2
pytest-asyncio==0.21.0
wrapt==1.16.0
opentelemetry-api==1.37 # when updating, also update in pyproject.toml
opentelemetry-sdk==1.37 # when updating, also update in pyproject.toml
opentelemetry-semantic-conventions==0.58b0 # when updating, also update in pyproject.toml
opentelemetry-api==1.39 # when updating, also update in pyproject.toml
opentelemetry-sdk==1.39 # when updating, also update in pyproject.toml
opentelemetry-semantic-conventions==0.60b0 # when updating, also update in pyproject.toml

-e instrumentation-genai/opentelemetry-instrumentation-anthropic
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,14 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation ~= 0.58b0",
"opentelemetry-semantic-conventions ~= 0.58b0",
"opentelemetry-api ~= 1.39",
"opentelemetry-instrumentation ~= 0.60b0",
"opentelemetry-semantic-conventions ~= 0.60b0",
"opentelemetry-util-genai >= 0.2b0, <0.4b0",
]

[project.optional-dependencies]
instruments = [
"claude-agent-sdk >= 0.1.14",
]
instruments = ["claude-agent-sdk >= 0.1.14"]

[project.entry-points.opentelemetry_instrumentor]
claude-agent-sdk = "opentelemetry.instrumentation.claude_agent_sdk:ClaudeAgentSDKInstrumentor"
Expand All @@ -46,11 +44,7 @@ Repository = "https://github.com/open-telemetry/opentelemetry-python-contrib"
path = "src/opentelemetry/instrumentation/claude_agent_sdk/version.py"

[tool.hatch.build.targets.sdist]
include = [
"/src",
"/tests",
"/examples",
]
include = ["/src", "/tests", "/examples"]

[tool.hatch.build.targets.wheel]
packages = ["src/opentelemetry"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pytest==7.4.4
pytest-vcr==1.0.2
pytest-asyncio==0.21.0
wrapt==1.16.0
opentelemetry-api==1.37 # when updating, also update in pyproject.toml
opentelemetry-sdk==1.37 # when updating, also update in pyproject.toml
opentelemetry-semantic-conventions==0.58b0 # when updating, also update in pyproject.toml
opentelemetry-api==1.39 # when updating, also update in pyproject.toml
opentelemetry-sdk==1.39 # when updating, also update in pyproject.toml
opentelemetry-semantic-conventions==0.60b0 # when updating, also update in pyproject.toml

-e instrumentation-genai/opentelemetry-instrumentation-claude-agent-sdk
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Fix `ChoiceBuffer` crash on streaming tool-call deltas with `arguments=None`
([#4350](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4350))
- Fix `StreamWrapper` missing `.headers` and other attributes when using `with_raw_response` streaming
([#4113](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/4113))
- Add opt-in support for latest experimental semantic conventions (v1.37.0). Set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,14 @@ classifiers = [
"Programming Language :: Python :: 3.14",
]
dependencies = [
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation ~= 0.58b0",
"opentelemetry-semantic-conventions ~= 0.58b0",
"opentelemetry-api ~= 1.39",
"opentelemetry-instrumentation ~= 0.60b0",
"opentelemetry-semantic-conventions ~= 0.60b0",
"opentelemetry-util-genai",
]

[project.optional-dependencies]
instruments = [
"openai >= 1.26.0",
]
instruments = ["openai >= 1.26.0"]

[project.entry-points.opentelemetry_instrumentor]
openai = "opentelemetry.instrumentation.openai_v2:OpenAIInstrumentor"
Expand All @@ -48,10 +46,7 @@ Repository = "https://github.com/open-telemetry/opentelemetry-python-contrib"
path = "src/opentelemetry/instrumentation/openai_v2/version.py"

[tool.hatch.build.targets.sdist]
include = [
"/src",
"/tests",
]
include = ["/src", "/tests"]

[tool.hatch.build.targets.wheel]
packages = ["src/opentelemetry"]
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,8 @@ def __init__(self, index, tool_call_id, function_name):
self.arguments = []

def append_arguments(self, arguments):
self.arguments.append(arguments)
if arguments is not None:
self.arguments.append(arguments)


class ChoiceBuffer:
Expand All @@ -601,13 +602,16 @@ def append_tool_call(self, tool_call):
for _ in range(len(self.tool_calls_buffers), idx + 1):
self.tool_calls_buffers.append(None)

function = tool_call.function
if not self.tool_calls_buffers[idx]:
self.tool_calls_buffers[idx] = ToolCallBuffer(
idx, tool_call.id, tool_call.function.name
idx,
tool_call.id,
function.name if function else None,
)
self.tool_calls_buffers[idx].append_arguments(
tool_call.function.arguments
)

if function:
self.tool_calls_buffers[idx].append_arguments(function.arguments)


class BaseStreamWrapper:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ pytest-vcr==1.0.2
pytest-asyncio==0.21.0
wrapt==1.16.0
opentelemetry-exporter-otlp-proto-http~=1.30
opentelemetry-api==1.37 # when updating, also update in pyproject.toml
opentelemetry-sdk==1.37 # when updating, also update in pyproject.toml
opentelemetry-semantic-conventions==0.58b0 # when updating, also update in pyproject.toml
opentelemetry-api==1.39 # when updating, also update in pyproject.toml
opentelemetry-sdk==1.39 # when updating, also update in pyproject.toml
opentelemetry-semantic-conventions==0.60b0 # when updating, also update in pyproject.toml

-e instrumentation-genai/opentelemetry-instrumentation-openai-v2
-e util/opentelemetry-util-genai
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# 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.

"""Unit tests for ChoiceBuffer and ToolCallBuffer classes."""

from openai.types.chat.chat_completion_chunk import (
ChoiceDeltaToolCall,
ChoiceDeltaToolCallFunction,
)

from opentelemetry.instrumentation.openai_v2.patch import (
ChoiceBuffer,
ToolCallBuffer,
)


def test_toolcallbuffer_append_arguments_with_string():
buf = ToolCallBuffer(0, "call_1", "get_weather")
buf.append_arguments('{"city":')
buf.append_arguments(' "NYC"}')
assert "".join(buf.arguments) == '{"city": "NYC"}'


def test_toolcallbuffer_append_arguments_with_none_is_skipped():
"""Regression test for issue #4344.

Some OpenAI-compatible providers (vLLM, TGI, etc.) send
arguments=None on tool-call delta chunks instead of arguments="".
This must not crash when joining the arguments list.
"""
buf = ToolCallBuffer(0, "call_1", "get_weather")
buf.append_arguments(None)
buf.append_arguments('{"city": "NYC"}')
buf.append_arguments(None)
assert "".join(buf.arguments) == '{"city": "NYC"}'


def test_toolcallbuffer_append_arguments_all_none():
buf = ToolCallBuffer(0, "call_1", "get_weather")
buf.append_arguments(None)
buf.append_arguments(None)
assert "".join(buf.arguments) == ""


def test_toolcallbuffer_append_arguments_empty_string():
buf = ToolCallBuffer(0, "call_1", "get_weather")
buf.append_arguments("")
buf.append_arguments('{"city": "NYC"}')
assert "".join(buf.arguments) == '{"city": "NYC"}'


def test_choicebuffer_append_tool_call_with_none_arguments():
"""End-to-end regression test for issue #4344.

Simulates the exact scenario from the bug report where a provider
sends arguments=None on the first tool-call delta chunk.
"""
buf = ChoiceBuffer(0)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
id="call_1",
type="function",
function=ChoiceDeltaToolCallFunction(
name="get_weather", arguments=None
),
)
)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
function=ChoiceDeltaToolCallFunction(arguments='{"city": "NYC"}'),
)
)

# This must not raise TypeError
result = "".join(buf.tool_calls_buffers[0].arguments)
assert result == '{"city": "NYC"}'


def test_choicebuffer_append_tool_call_normal_flow():
"""Standard OpenAI flow where arguments="" on first delta."""
buf = ChoiceBuffer(0)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
id="call_1",
type="function",
function=ChoiceDeltaToolCallFunction(
name="get_weather", arguments=""
),
)
)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
function=ChoiceDeltaToolCallFunction(arguments='{"city": "NYC"}'),
)
)

result = "".join(buf.tool_calls_buffers[0].arguments)
assert result == '{"city": "NYC"}'


def test_choicebuffer_append_multiple_tool_calls_with_none_arguments():
"""Multiple tool calls where some have arguments=None."""
buf = ChoiceBuffer(0)

# First tool call
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
id="call_1",
type="function",
function=ChoiceDeltaToolCallFunction(
name="get_weather", arguments=None
),
)
)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
function=ChoiceDeltaToolCallFunction(arguments='{"city": "NYC"}'),
)
)

# Second tool call
buf.append_tool_call(
ChoiceDeltaToolCall(
index=1,
id="call_2",
type="function",
function=ChoiceDeltaToolCallFunction(
name="get_time", arguments=None
),
)
)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=1,
function=ChoiceDeltaToolCallFunction(arguments='{"tz": "EST"}'),
)
)

assert "".join(buf.tool_calls_buffers[0].arguments) == '{"city": "NYC"}'
assert "".join(buf.tool_calls_buffers[1].arguments) == '{"tz": "EST"}'


def test_choicebuffer_append_tool_call_with_none_function():
"""Handle delta chunks where function is None."""
buf = ChoiceBuffer(0)
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
id="call_1",
type="function",
function=ChoiceDeltaToolCallFunction(
name="get_weather", arguments='{"city": "NYC"}'
),
)
)
# Subsequent delta with function=None should not crash
buf.append_tool_call(
ChoiceDeltaToolCall(
index=0,
function=None,
)
)

result = "".join(buf.tool_calls_buffers[0].arguments)
assert result == '{"city": "NYC"}'
Loading