Skip to content

Commit ac8a144

Browse files
fix(llmobs): safely output format bedrock cohere rerank spans [backport 3.17] (#15137)
Backport 191f10e from #15124 to 3.17. ## Description Closes #14575. <!-- Provide an overview of the change and motivation for the change --> Adds safely accessing output response messages in the bedrock integration. This happens when cohere rerank models are invoked, since cohere rerank responses lack a response["text"] field and will return an empty list. ## Testing Added a test invoking cohere rerank models. <!-- Describe your testing strategy or note what tests are included --> ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers --> Signed-off-by: Yun Kim <[email protected]> Co-authored-by: Yun Kim <[email protected]>
1 parent cf7b327 commit ac8a144

File tree

4 files changed

+92
-12
lines changed

4 files changed

+92
-12
lines changed

ddtrace/llmobs/_integrations/bedrock.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,14 @@ def _extract_output_message(response) -> List[Message]:
371371
"""Extract output messages from the stored response.
372372
Anthropic allows for chat messages, which requires some special casing.
373373
"""
374-
if isinstance(response["text"], str):
375-
return [Message(content=response["text"])]
376-
if isinstance(response["text"], list):
377-
if isinstance(response["text"][0], str):
378-
return [Message(content=str(content)) for content in response["text"]]
379-
if isinstance(response["text"][0], dict):
380-
return [Message(content=response["text"][0].get("text", ""))]
374+
resp_text = response.get("text", "")
375+
if isinstance(resp_text, str):
376+
return [Message(content=resp_text)]
377+
if resp_text and isinstance(resp_text, list):
378+
if isinstance(resp_text[0], str):
379+
return [Message(content=str(content)) for content in resp_text]
380+
if isinstance(resp_text[0], dict):
381+
return [Message(content=resp_text[0].get("text", ""))]
381382
return []
382383

383384
def _get_base_url(self, **kwargs: Dict[str, Any]) -> Optional[str]:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
LLM Observability: Resolves an issue in the bedrock integration where invoking cohere rerank models would result in missing spans due to output formatting index errors.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
interactions:
2+
- request:
3+
body: '{"query": "What is the capital of the United States?", "documents": ["Carson
4+
City is the capital city of the American state of Nevada.", "The Commonwealth
5+
of the Northern Mariana Islands is a group of islands in the Pacific Ocean.
6+
Its capital is Saipan.", "Washington, D.C. (also known as simply Washington
7+
or D.C., and officially as the District of Columbia) is the capital of the United
8+
States. It is a federal district.", "Capitalization or capitalisation in English
9+
grammar is the use of a capital letter at the start of a word. English usage
10+
varies from capitalization in other languages.", "Capital punishment has existed
11+
in the United States since beforethe United States was a country. As of 2017,
12+
capital punishment is legal in 30 of the 50 states."], "api_version": 2, "top_n":
13+
3}'
14+
headers:
15+
Content-Length:
16+
- '790'
17+
User-Agent:
18+
- !!binary |
19+
Qm90bzMvMS4zNC40OSBtZC9Cb3RvY29yZSMxLjM0LjQ5IHVhLzIuMCBvcy9tYWNvcyMyNC42LjAg
20+
bWQvYXJjaCNhcm02NCBsYW5nL3B5dGhvbiMzLjExLjEzIG1kL3B5aW1wbCNDUHl0aG9uIGNmZy9y
21+
ZXRyeS1tb2RlI2xlZ2FjeSBCb3RvY29yZS8xLjM0LjQ5
22+
X-Amz-Date:
23+
- !!binary |
24+
MjAyNTEwMzFUMjEwODQ2Wg==
25+
amz-sdk-invocation-id:
26+
- !!binary |
27+
NDNlZWJhN2EtNGY1Yy00ZDI1LWFmNzUtYjY0NDNjNmUzYzM0
28+
amz-sdk-request:
29+
- !!binary |
30+
YXR0ZW1wdD0x
31+
method: POST
32+
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/cohere.rerank-v3-5%3A0/invoke
33+
response:
34+
body:
35+
string: '{"results":[{"index":2,"relevance_score":0.8742601},{"index":0,"relevance_score":0.1728413},{"index":4,"relevance_score":0.10793502}]}'
36+
headers:
37+
Connection:
38+
- keep-alive
39+
Content-Length:
40+
- '134'
41+
Content-Type:
42+
- application/json
43+
Date:
44+
- Fri, 31 Oct 2025 21:08:47 GMT
45+
X-Amzn-Bedrock-Invocation-Latency:
46+
- '109'
47+
x-amzn-RequestId:
48+
- de4d85e7-fe35-4809-bda9-7675d0eb091f
49+
status:
50+
code: 200
51+
message: OK
52+
version: 1

tests/contrib/botocore/test_bedrock_llmobs.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@
2626
)
2727
class TestLLMObsBedrock:
2828
@staticmethod
29-
def expected_llmobs_span_event(span, n_output, message=False, metadata=None, token_metrics=None):
29+
def expected_llmobs_span_event(
30+
span, n_output, input_message=False, output_message=False, metadata=None, token_metrics=None
31+
):
3032
expected_input = [{"content": mock.ANY}]
31-
if message:
33+
if input_message:
3234
expected_input = [{"content": mock.ANY, "role": "user"}]
35+
expected_output = []
36+
if output_message:
37+
expected_output = [{"content": mock.ANY} for _ in range(n_output)]
3338

3439
# Use empty dicts as defaults for _expected_llmobs_llm_span_event to avoid None issues
3540
expected_parameters = metadata if metadata is not None else {}
@@ -40,7 +45,7 @@ def expected_llmobs_span_event(span, n_output, message=False, metadata=None, tok
4045
model_name=span.get_tag("bedrock.request.model"),
4146
model_provider=span.get_tag("bedrock.request.model_provider"),
4247
input_messages=expected_input,
43-
output_messages=[{"content": mock.ANY} for _ in range(n_output)],
48+
output_messages=expected_output,
4449
metadata=expected_parameters,
4550
token_metrics=expected_token_metrics,
4651
tags={"service": "aws.bedrock-runtime", "ml_app": "<ml-app-name>"},
@@ -86,7 +91,7 @@ def _test_llmobs_invoke(cls, provider, bedrock_client, mock_tracer, llmobs_event
8691

8792
assert len(llmobs_events) == 1
8893
assert llmobs_events[0] == cls.expected_llmobs_span_event(
89-
span, n_output, message="message" in provider, metadata=expected_metadata
94+
span, n_output, input_message="message" in provider, output_message=True, metadata=expected_metadata
9095
)
9196
LLMObs.disable()
9297

@@ -121,7 +126,7 @@ def _test_llmobs_invoke_stream(
121126

122127
assert len(llmobs_events) == 1
123128
assert llmobs_events[0] == cls.expected_llmobs_span_event(
124-
span, n_output, message="message" in provider, metadata=expected_metadata
129+
span, n_output, input_message="message" in provider, output_message=True, metadata=expected_metadata
125130
)
126131

127132
def test_llmobs_ai21_invoke(self, ddtrace_global_config, bedrock_client, mock_tracer, llmobs_events):
@@ -156,6 +161,24 @@ def test_llmobs_cohere_multi_output_invoke(self, ddtrace_global_config, bedrock_
156161
def test_llmobs_meta_invoke(self, ddtrace_global_config, bedrock_client, mock_tracer, llmobs_events):
157162
self._test_llmobs_invoke("meta", bedrock_client, mock_tracer, llmobs_events)
158163

164+
def test_llmobs_cohere_rerank_invoke(self, ddtrace_global_config, bedrock_client, mock_tracer, llmobs_events):
165+
cassette_name = "cohere_rerank_invoke.yaml"
166+
model = "cohere.rerank-v3-5:0"
167+
prompt_data = "What is the capital of the United States?"
168+
documents = [
169+
"Carson City is the capital city of the American state of Nevada.",
170+
"The Commonwealth of the Northern Mariana Islands's capital is Saipan.",
171+
]
172+
body = json.dumps({"query": prompt_data, "documents": documents, "api_version": 2, "top_n": 3})
173+
with get_request_vcr().use_cassette(cassette_name):
174+
response = bedrock_client.invoke_model(body=body, modelId=model)
175+
json.loads(response.get("body").read())
176+
span = mock_tracer.pop_traces()[0][0]
177+
178+
assert len(llmobs_events) == 1
179+
assert llmobs_events[0] == self.expected_llmobs_span_event(span, 1)
180+
LLMObs.disable()
181+
159182
def test_llmobs_amazon_invoke_stream(self, ddtrace_global_config, bedrock_client, mock_tracer, llmobs_events):
160183
self._test_llmobs_invoke_stream("amazon", bedrock_client, mock_tracer, llmobs_events)
161184

0 commit comments

Comments
 (0)