Skip to content

fix(botocore): tag Bedrock embedding InvokeModel calls as embeddings - #4997

Open
BetterAndBetterII wants to merge 4 commits into
open-telemetry:mainfrom
BetterAndBetterII:fix/bedrock-embed-op-name
Open

fix(botocore): tag Bedrock embedding InvokeModel calls as embeddings#4997
BetterAndBetterII wants to merge 4 commits into
open-telemetry:mainfrom
BetterAndBetterII:fix/bedrock-embed-op-name

Conversation

@BetterAndBetterII

@BetterAndBetterII BetterAndBetterII commented Aug 25, 2026

Copy link
Copy Markdown

Description

extract_attributes and _extract_metrics_attributes both labeled every Bedrock InvokeModel call as chat (or Titan text_completion when the modelId contained amazon.titan). Embedding model IDs such as cohere.embed-v4:0 and amazon.titan-embed-* were therefore recorded with gen_ai.operation.name=chat, and the span name plus duration/token metrics inherited that value.

Detect embedding modelIds (substring embed) first in both extract paths and set gen_ai.operation.name to embeddings. Titan text-completion and chat models are unchanged.

Fixes #4996

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Unit tests in test_botocore_bedrock.py covering both extract sites, with and without a request body, for Cohere and Titan embedding model IDs, plus unchanged Titan text-completion and chat models

Does This PR Require a Core Repo Change?

  • No.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@BetterAndBetterII
BetterAndBetterII requested a review from a team as a code owner August 25, 2026 23:14
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 25, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-08-29 10:21 UTC

Respond to 4 review items (e.g. link a commit, explain why not, ask a follow-up):

  • Inline threads: 1, 2
  • Top-level threads: 3, 4
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.

@JoeyPatricio

Copy link
Copy Markdown

Not an approver, but I checked this out and ran it on Python 3.13.

test_botocore_bedrock.py is 91/91 green on the branch. Reverting only bedrock.py to main while keeping the new tests fails all 16 embedding cases, so the tests cover the behavior they claim. GenAiOperationNameValues.EMBEDDINGS.value is "embeddings" in the semconv package this code imports, matching the spec's embeddings span definition.

The pre-fix failures show the bug had two shapes. Titan embed models returned chat with no body and text_completion with a body, so the same model produced different operation names depending on call shape. Worth adding to the description.

  1. In the third call site the Titan branch calls _extract_titan_attributes, while the embedding branch sets the operation name and returns. Is this deliberate? The semconv defines gen_ai.embeddings.dimension.count, and Titan embed takes a dimensions parameter. The tests assert only model and operation name, so nothing pins the behavior either way.

  2. The with_body=True parametrization passes {"inputText": "hello"} for every model, including cohere.embed-v4:0. Cohere Embed on Bedrock takes texts/input_type (AWS docs); inputText is Titan's schema. It passes because the embedding path never reads the body, but it will mislead whoever adds body extraction.

  3. Substring matching on embed beats a prefix check here, since it catches cross-region inference profile IDs like global.cohere.embed-v4:0. Any false-positive case you considered?


@pytest.mark.parametrize("model_id", EMBEDDING_MODEL_IDS)
@pytest.mark.parametrize("with_body", [False, True])
def test_extract_attributes_embedding_operation_name(model_id, with_body):

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.

I would prefer to have recorded tests instead of unit testing the helpers. I don't think we have any embedding test already, so introducing something like a parametrized test_invoke_model_with_embeddings_model. Also I think one model entry per model family is enough.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Replaced the helper unit tests with parametrized recorded test_invoke_model_with_embeddings_model (one model per family).



def _is_embedding_model(model_id: str) -> bool:
return "embed" in model_id.lower()

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.

Are model names case insensitive? Anyway it looks like this will work from what I can see in https://docs.aws.amazon.com/bedrock/latest/userguide/models-api-compatibility.html

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.

As @JoeyPatricio spotted maybe we should split the model_id by . and look only in the last part?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Detection now uses only the last dotted segment of modelId.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

AWS docs list model IDs in lowercase; the last-segment check still lowercases so mixed-case IDs match.

@github-project-automation github-project-automation Bot moved this to Reviewed PRs that need fixes in Python PR digest Aug 26, 2026
@BetterAndBetterII
BetterAndBetterII force-pushed the fix/bedrock-embed-op-name branch from 62c025c to e6f20be Compare August 26, 2026 10:48
result["body"] = StreamingBody(new_stream, len(body_content))

response_body = json.loads(body_content.decode("utf-8"))
if _is_embedding_model(model_id):

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.

IIUC this makes gen_ai.usage.input_tokens disappear for Titan embed models on main the response reaches _handle_amazon_titan_response, which sets it from inputTextTokenCount.

else:
pytest.xfail(f"model family not handled: {model_family}")

(span,) = span_exporter.get_finished_spans()

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.

The new test also pins this: assert_stream_completion_attributes leaves input_tokens as None

@henry3260 henry3260 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.

Non-blocking thought: this fix had to touch 5 places because the model_id dispatch is repeated across 4 functions (_extract_metrics_attributes, extract_attributes, _get_request_messages, _invoke_model_on_success). Would it be worth centralizing the family detection?

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

Labels

None yet

Projects

Status: Reviewed PRs that need fixes

Development

Successfully merging this pull request may close these issues.

Bedrock embedding calls are tagged with chat instead of embeddings

4 participants