-
Notifications
You must be signed in to change notification settings - Fork 931
feat: Add sync streaming support for Anthropic instrumentation #4155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
xrmx
merged 54 commits into
open-telemetry:main
from
vasantteja:anthropic-sync-streaming
Mar 9, 2026
Merged
Changes from 31 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
988fa6e
Add sync streaming support for Anthropic instrumentation
vasantteja ea0bd94
Add changelog entry for sync streaming support
vasantteja 504d0df
Fix type checking errors with type: ignore comments
vasantteja 8df752a
Refactor Anthropic instrumentation to improve usage tracking and erro…
vasantteja 1b09377
Refactor utility functions and test cases for improved readability an…
vasantteja e6c83ac
Refactor argument handling in assert_span_attributes function
vasantteja 1ed3c6b
Enhance tests for streaming message handling in Anthropic instrumenta…
vasantteja a011520
Merge branch 'main' into anthropic-sync-streaming
vasantteja 2851e4a
Update test_sync_messages.py to disable pylint warning for too-many-l…
vasantteja 3e5cbda
Merge branch 'anthropic-sync-streaming' of https://github.com/vasantt…
vasantteja 38d4429
Enhance StreamWrapper and MessageStreamManagerWrapper for idempotent …
vasantteja 685e161
Merge branch 'main' into anthropic-sync-streaming
vasantteja 0f481c1
Enhance Anthropic instrumentation to support content capture
vasantteja 75fcb3b
Enhance tests for sync message creation in Anthropic instrumentation
vasantteja 8b5b20f
Remove sensitive 'anthropic-organization-id' headers from test casset…
vasantteja d48c7e3
Refactor tests for sync message handling in Anthropic instrumentation
vasantteja ee173da
Refactor utils.py for improved type safety and clarity
vasantteja ed46be8
Enhance Anthropic instrumentation tests for EVENT_ONLY content capture
vasantteja d1af778
Refactor assertion in sync messages test for clarity
vasantteja 5093cbe
Merge branch 'main' into anthropic-sync-streaming
vasantteja 401e1b1
Refactor content capture logic and enhance streaming tests for Anthro…
vasantteja 15d1b05
unsetting the model.
vasantteja 44b97a8
Merge branch 'main' into anthropic-sync-streaming
vasantteja 7800a0e
Remove instrumentation for Messages.stream() and refactor related cod…
vasantteja df9911e
Refactor Anthropic instrumentation: reorganize imports, enhance utili…
vasantteja 2590274
Add message extractors for Anthropic instrumentation.
vasantteja b4adeec
Refactor message extractors in Anthropic instrumentation: reorganize …
vasantteja e9c235a
Update test cassettes for Anthropic instrumentation: streamline reque…
vasantteja da275d0
Enhance Anthropic instrumentation: update MessageWrapper and StreamWr…
vasantteja 2c2a780
Update test cassettes for Anthropic instrumentation: modify message I…
vasantteja cdd95b1
Merge branch 'main' into anthropic-sync-streaming
vasantteja 1d1b7f5
Rename StreamWrapper to MessagesStreamWrapper and update references i…
vasantteja 0793c46
Merge branch 'main' into anthropic-sync-streaming
vasantteja 5effa69
Refactor type annotations in message extractors and wrappers for impr…
vasantteja 89704d2
Merge branch 'anthropic-sync-streaming' of https://github.com/vasantt…
vasantteja a835a75
Enhance type annotations in message extractors and patch for improved…
vasantteja 471204c
Enhance type safety and error handling in message processing. Update …
vasantteja d1bf3fe
Refactor assertions in test_sync_messages.py for improved readability…
vasantteja 138bfa0
Merge branch 'main' into anthropic-sync-streaming
vasantteja cc0a5b0
Merge branch 'main' into anthropic-sync-streaming
vasantteja 0dc8a9f
enforce strong typing system.
vasantteja f62dd6d
Update anthropic dependency version to 0.51.0 in pyproject.toml and r…
vasantteja 1bf7695
Refactor usage token extraction to utilize a new UsageTokens dataclas…
vasantteja 3d2bd63
Update anthropic dependency version in uv.lock to 0.51.0 for compatib…
vasantteja cc8e4d1
Add tests for should_capture_content function in test_events_options.py.
vasantteja 1656950
Enhance Anthropic instrumentation by adding logging support and refin…
vasantteja ccf5551
Merge branch 'main' into anthropic-sync-streaming
vasantteja d62632f
Refactor content capturing utility function to clarify its purpose in…
vasantteja 5c2481a
Refactor import statements in patch.py for improved readability and o…
vasantteja 8c51037
Merge branch 'main' into anthropic-sync-streaming
vasantteja f2bc3cc
Merge branch 'main' into anthropic-sync-streaming
vasantteja d317443
Merge branch 'main' into anthropic-sync-streaming
aabmass 07aee9f
Merge branch 'main' into anthropic-sync-streaming
vasantteja f5054b3
Merge branch 'main' into anthropic-sync-streaming
vasantteja File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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
202 changes: 202 additions & 0 deletions
202
...trumentation-anthropic/src/opentelemetry/instrumentation/anthropic/messages_extractors.py
This file contains hidden or 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 |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| # 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. | ||
|
|
||
| """Get/extract helpers for Anthropic Messages instrumentation.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from typing import TYPE_CHECKING, Any, Optional, Sequence, cast | ||
| from urllib.parse import urlparse | ||
|
|
||
| from opentelemetry.semconv._incubating.attributes import ( | ||
| gen_ai_attributes as GenAIAttributes, | ||
| ) | ||
| from opentelemetry.semconv._incubating.attributes import ( | ||
| server_attributes as ServerAttributes, | ||
| ) | ||
| from opentelemetry.util.genai.types import ( | ||
| InputMessage, | ||
| MessagePart, | ||
| OutputMessage, | ||
| ) | ||
| from opentelemetry.util.types import AttributeValue | ||
|
|
||
| from .utils import ( | ||
| _as_str, | ||
| _get_field, | ||
| as_int, | ||
| convert_content_to_parts, | ||
| normalize_finish_reason, | ||
| ) | ||
|
|
||
| if TYPE_CHECKING: | ||
| from anthropic.resources.messages import Messages | ||
|
|
||
|
|
||
| @dataclass | ||
| class MessageRequestParams: | ||
| model: str | None = None | ||
| max_tokens: int | None = None | ||
| temperature: float | None = None | ||
| top_k: int | None = None | ||
| top_p: float | None = None | ||
| stop_sequences: Sequence[str] | None = None | ||
| messages: Any | None = None | ||
| system: Any | None = None | ||
|
|
||
|
|
||
| GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = ( | ||
| "gen_ai.usage.cache_creation.input_tokens" | ||
| ) | ||
| GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens" | ||
|
|
||
|
|
||
| def extract_usage_tokens( | ||
| usage: Any | None, | ||
| ) -> tuple[int | None, int | None, int | None, int | None]: | ||
| if usage is None: | ||
| return None, None, None, None | ||
|
|
||
| input_tokens = as_int(getattr(usage, "input_tokens", None)) | ||
|
vasantteja marked this conversation as resolved.
Outdated
|
||
| cache_creation_input_tokens = as_int( | ||
| getattr(usage, "cache_creation_input_tokens", None) | ||
| ) | ||
| cache_read_input_tokens = as_int( | ||
| getattr(usage, "cache_read_input_tokens", None) | ||
| ) | ||
| output_tokens = as_int(getattr(usage, "output_tokens", None)) | ||
|
|
||
| if ( | ||
| input_tokens is None | ||
| and cache_creation_input_tokens is None | ||
| and cache_read_input_tokens is None | ||
| ): | ||
| total_input_tokens = None | ||
| else: | ||
| total_input_tokens = ( | ||
| (input_tokens or 0) | ||
| + (cache_creation_input_tokens or 0) | ||
| + (cache_read_input_tokens or 0) | ||
| ) | ||
|
|
||
| return ( | ||
| total_input_tokens, | ||
| output_tokens, | ||
| cache_creation_input_tokens, | ||
| cache_read_input_tokens, | ||
| ) | ||
|
|
||
|
|
||
| def get_input_messages(messages: Any) -> list[InputMessage]: | ||
|
vasantteja marked this conversation as resolved.
Outdated
|
||
| if not isinstance(messages, list): | ||
| return [] | ||
|
|
||
| result: list[InputMessage] = [] | ||
| for message in cast(list[Any], messages): | ||
| role = _as_str(_get_field(message, "role")) or "user" | ||
| parts = convert_content_to_parts(_get_field(message, "content")) | ||
| result.append(InputMessage(role=role, parts=parts)) | ||
| return result | ||
|
|
||
|
|
||
| def get_system_instruction(system: Any) -> list[MessagePart]: | ||
| return convert_content_to_parts(system) | ||
|
|
||
|
|
||
| def get_output_messages_from_message(message: Any) -> list[OutputMessage]: | ||
| if message is None: | ||
| return [] | ||
|
|
||
| parts = convert_content_to_parts(_get_field(message, "content")) | ||
| finish_reason = normalize_finish_reason(_get_field(message, "stop_reason")) | ||
| return [ | ||
| OutputMessage( | ||
| role=_as_str(_get_field(message, "role")) or "assistant", | ||
| parts=parts, | ||
| finish_reason=finish_reason or "", | ||
| ) | ||
| ] | ||
|
|
||
|
|
||
| def extract_params( # pylint: disable=too-many-locals | ||
| *, | ||
| max_tokens: int | None = None, | ||
| messages: Any | None = None, | ||
| model: str | None = None, | ||
| metadata: Any | None = None, | ||
| service_tier: Any | None = None, | ||
| stop_sequences: Sequence[str] | None = None, | ||
| stream: Any | None = None, | ||
| system: Any | None = None, | ||
| temperature: float | None = None, | ||
| thinking: Any | None = None, | ||
| tool_choice: Any | None = None, | ||
| tools: Any | None = None, | ||
| top_k: int | None = None, | ||
| top_p: float | None = None, | ||
| extra_headers: Any | None = None, | ||
| extra_query: Any | None = None, | ||
| extra_body: Any | None = None, | ||
| timeout: Any | None = None, | ||
| **_kwargs: Any, | ||
| ) -> MessageRequestParams: | ||
| return MessageRequestParams( | ||
| model=model, | ||
| max_tokens=max_tokens, | ||
| temperature=temperature, | ||
| top_p=top_p, | ||
| top_k=top_k, | ||
| stop_sequences=stop_sequences, | ||
| messages=messages, | ||
| system=system, | ||
| ) | ||
|
|
||
|
|
||
| def _set_server_address_and_port( | ||
| client_instance: "Messages", attributes: dict[str, Any] | ||
| ) -> None: | ||
| base_client = getattr(client_instance, "_client", None) | ||
| base_url = getattr(base_client, "base_url", None) | ||
| if not base_url: | ||
| return | ||
|
|
||
| port: Optional[int] = None | ||
| if hasattr(base_url, "host"): | ||
| attributes[ServerAttributes.SERVER_ADDRESS] = base_url.host | ||
| port = getattr(base_url, "port", None) | ||
| elif isinstance(base_url, str): | ||
| url = urlparse(base_url) | ||
| attributes[ServerAttributes.SERVER_ADDRESS] = url.hostname | ||
| port = url.port | ||
|
|
||
| if port and port != 443 and port > 0: | ||
| attributes[ServerAttributes.SERVER_PORT] = port | ||
|
|
||
|
|
||
| def get_llm_request_attributes( | ||
| params: MessageRequestParams, client_instance: "Messages" | ||
| ) -> dict[str, AttributeValue]: | ||
| attributes = { | ||
| GenAIAttributes.GEN_AI_OPERATION_NAME: GenAIAttributes.GenAiOperationNameValues.CHAT.value, | ||
| GenAIAttributes.GEN_AI_SYSTEM: GenAIAttributes.GenAiSystemValues.ANTHROPIC.value, # pyright: ignore[reportDeprecated] | ||
| GenAIAttributes.GEN_AI_REQUEST_MODEL: params.model, | ||
| GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS: params.max_tokens, | ||
| GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE: params.temperature, | ||
| GenAIAttributes.GEN_AI_REQUEST_TOP_P: params.top_p, | ||
| GenAIAttributes.GEN_AI_REQUEST_TOP_K: params.top_k, | ||
| GenAIAttributes.GEN_AI_REQUEST_STOP_SEQUENCES: params.stop_sequences, | ||
| } | ||
| _set_server_address_and_port(client_instance, attributes) | ||
| return {k: v for k, v in attributes.items() if v is not None} | ||
This file contains hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.