Skip to content

fix(anthropic): handle with_raw_response results without crashing - #381

Open
Shailendra005 wants to merge 5 commits into
open-telemetry:mainfrom
Shailendra005:fix/anthropic-raw-response
Open

fix(anthropic): handle with_raw_response results without crashing#381
Shailendra005 wants to merge 5 commits into
open-telemetry:mainfrom
Shailendra005:fix/anthropic-raw-response

Conversation

@Shailendra005

Copy link
Copy Markdown

Fixes #380

Problem

messages.create(...) invoked via with_raw_response returns a LegacyAPIResponse, but the instrumentation assumed a Message and read message.model during extraction, raising AttributeError into the caller after a successful (billed) API call. Instrumentation must not break the app.

Fix

Mirror the OpenAI sibling package's ParsableResponse pattern:

  • Add a _RawResponse runtime-checkable Protocol (shape match on parse(), so we don't import the SDK's private response classes) and a _message_for_extraction() helper.
  • In both sync messages_create and async async_messages_create, parse the raw response to obtain the Message for telemetry, then return the original result untouched so raw-response callers still get their headers and parse().
  • Streaming raw responses (stream=True) and any unexpected payload are left uninstrumented instead of crashing.
  • The existing Messages.parse / AsyncMessages.parse wrappers reuse the create path, so they are covered too.

Tests

Added test_sync_messages_create_with_raw_response and test_async_messages_create_with_raw_response (with cassettes). They fail before the fix (AttributeError: 'LegacyAPIResponse' object has no attribute 'model') and pass after. Full package suite: 108 passed, 3 skipped. ruff check / ruff format clean.

Changelog

Added a towncrier fixed fragment; I'll rename it to <PR_NUMBER>.fixed to match the convention.

Messages.create / AsyncMessages.create (and the parse() wrappers) assumed
the wrapped call always returns a Message. When called through
with_raw_response the SDK returns a LegacyAPIResponse instead, and reading
message.model during extraction raised AttributeError into the caller's app
after the API call had already succeeded.

Detect a raw-response result by shape (a parse() method, mirroring the OpenAI
sibling package's ParsableResponse) and extract telemetry from the parsed
Message, while returning the original raw response to the caller untouched.
Streaming raw responses and any unexpected payload are left uninstrumented
rather than crashing. Covers the sync and async create/parse paths.

Adds sync and async regression tests.

Signed-off-by: Shailendra005 <shivzala06@outlook.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 16:43
@Shailendra005
Shailendra005 requested a review from a team as a code owner August 12, 2026 16:43
@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 12, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

Signed-off-by: Shailendra005 <shivzala06@outlook.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Prevents the Anthropic OTel instrumentation from crashing when messages.create(...) is invoked via with_raw_response, by extracting telemetry from the parsed Message while returning the raw-response object untouched.

Changes:

  • Add a runtime-checkable _RawResponse Protocol plus _message_for_extraction() helper to safely parse raw responses for telemetry.
  • Update sync/async messages_create wrappers to extract attributes from the parsed Message (when available) and return the original result.
  • Add sync/async regression tests (with VCR cassettes) for with_raw_response.create.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/patch.py Adds raw-response shape matching + parses for extraction; returns original result to avoid breaking callers.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_sync_messages.py Adds regression test ensuring with_raw_response.create doesn’t crash and still records a span.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_async_messages.py Adds async regression test covering the same raw-response behavior.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/cassettes/test_sync_messages_create_with_raw_response.yaml VCR cassette for the sync raw-response test.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/cassettes/test_async_messages_create_with_raw_response.yaml VCR cassette for the async raw-response test.
instrumentation/opentelemetry-instrumentation-genai-anthropic/.changelog/381.fixed Towncrier fragment documenting the fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…assette

Signed-off-by: Shailendra005 <shivzala06@outlook.com>
@Shailendra005

Copy link
Copy Markdown
Author

Thanks for the review. Addressing both comments:

1. AnthropicMessage | None return type (patch.py): No change needed. The module has from __future__ import annotations (line 6), so all annotations are strings and never evaluated at runtime, and the package's requires-python is >=3.10. The surrounding code already uses PEP 604 unions in its annotations (e.g. the existing messages_create return type), so this stays consistent with the module.

2. Duplicate interaction in the async cassette: Good catch — fixed in 4d28f80. The async test issues a single with_raw_response.create(...) call, so I trimmed the cassette to the one expected interaction (the sync cassette already had just one). Full package suite still passes (108 passed, 3 skipped).

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 12, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-08-13 21:02 UTC

Two things need attention:

  • Required checks are failing — investigate the failures.
  • 1 review item — respond to each (e.g. link a commit, explain why not, ask a follow-up):
    • Inline threads: 1
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Signed-off-by: Shailendra005 <shivzala06@outlook.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Signed-off-by: Shailendra005 <shivzala06@outlook.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@lmolkova lmolkova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix!

MessageWrapper(message, capture_content).extract_into(
invocation
)
invocation.stop()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker for this fix, but worth a follow-up.

With with_raw_response.create(stream=True) (and with_streaming_response) the SDK
returns a LegacyAPIResponse, not a Stream, so it falls past the
isinstance(result, AnthropicStream) check and we call stop() right away. That gives a
success span with no response attributes, ended before the caller has even called parse().

The openai package solves this in _raw_response.py (#278): the raw response is returned
wrapped in a transparent ObjectProxy so metadata still resolves natively, parse() is
deferred and returns the instrumented stream wrapper so the span ends when the stream is
drained, and a close/aclose fallback finalizes the span if the caller never parses.
Would be good to do the same here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Anthropic instrumentation raises AttributeError on messages.create(...) called via with_raw_response

3 participants