Skip to content
Merged
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
238 changes: 218 additions & 20 deletions sentry_sdk/integrations/boto3/_services/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,37 +93,235 @@
span.set_attribute if isinstance(span, StreamedSpan) else span.set_data
)

role: "Optional[str]" = None
parts: "dict[int, Union[TextPart, ReasoningPart, ToolCallPart, ToolCallResponsePart, UriPart, BlobPart]]" = {}

try:
for event in original_iter(self):
if "metadata" not in event:
yield event
continue
if "metadata" in event:
usage = event["metadata"]["usage"]

usage = event["metadata"]["usage"]
input_tokens = usage["inputTokens"]
set_on_span(
SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, usage["outputTokens"]
)
set_on_span(
SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage["totalTokens"]
)

input_tokens = usage["inputTokens"]
set_on_span(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, usage["outputTokens"])
set_on_span(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage["totalTokens"])
cache_read_tokens = usage.get("cacheReadInputTokens")
if cache_read_tokens is not None:
input_tokens += cache_read_tokens
set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED, cache_read_tokens
)

cache_write_tokens = usage.get("cacheWriteInputTokens")
if cache_write_tokens is not None:
input_tokens += cache_write_tokens
set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE,
cache_write_tokens,
)

cache_read_tokens = usage.get("cacheReadInputTokens")
if cache_read_tokens is not None:
input_tokens += cache_read_tokens
set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED, cache_read_tokens
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS,
input_tokens,
)

cache_write_tokens = usage.get("cacheWriteInputTokens")
if cache_write_tokens is not None:
input_tokens += cache_write_tokens
yield event
continue

client = sentry_sdk.get_client()
if (
not has_data_collection_enabled(client.options)
or not client.options["data_collection"]["gen_ai"]["outputs"]
) and (
has_data_collection_enabled(client.options)
or not should_send_default_pii()
):
yield event
continue

if "messageStart" in event:
role = event["messageStart"]["role"]

yield event
continue

if "messageStop" in event and role is not None:
set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE,
cache_write_tokens,
SPANDATA.GEN_AI_OUTPUT_MESSAGES,
json.dumps(
[
{
"role": role,
"parts": [parts[index] for index in sorted(parts)],
}
]
),
)

set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS,
input_tokens,
)
yield event
continue

if (
"contentBlockStart" in event
and "toolUse" in event["contentBlockStart"]["start"]
):
parts[event["contentBlockStart"]["contentBlockIndex"]] = {
"type": "tool_call",
"name": event["contentBlockStart"]["start"]["toolUse"]["name"],
}

yield event
continue

if (
"contentBlockStart" in event
and "toolResult" in event["contentBlockStart"]["start"]
):
parts[event["contentBlockStart"]["contentBlockIndex"]] = {
"type": "tool_call_response",
}

yield event
continue

if "contentBlockDelta" not in event:
yield event
continue

index = event["contentBlockDelta"]["contentBlockIndex"]
delta = event["contentBlockDelta"]["delta"]

if "text" in delta and index not in parts:
parts[index] = {"type": "text", "content": delta["text"]}

yield event
continue

if "text" in delta:
cast("TextPart", parts[index])["content"] += delta["text"]

yield event
continue

if "toolUse" in delta and index not in parts:
parts[index] = {
"type": "tool_call",
"arguments": delta["toolUse"]["input"],
}

yield event
continue

if "toolUse" in delta and "arguments" in parts[index]:
cast("ToolCallPart", parts[index])["arguments"] += delta["toolUse"][
"input"
]
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved

yield event
continue

if "toolUse" in delta:
cast("ToolCallPart", parts[index])["arguments"] = delta["toolUse"][
"input"
]

yield event
continue

if "toolResult" in delta and index not in parts:
parts[index] = {
"type": "tool_call_response",
"response": [
item
for item in delta["toolResult"]
if "json" in item or "text" in item
],
}

yield event
continue

if "toolResult" in delta and "response" in parts[index]:
cast("ToolCallResponsePart", parts[index])["response"] += [
item
for item in delta["toolResult"]
if "json" in item or "text" in item
]

yield event
continue

if "toolResult" in delta:
cast("ToolCallResponsePart", parts[index])["response"] = [
item
for item in delta["toolResult"]
if "json" in item or "text" in item
]

yield event
continue

if "reasoningContent" in delta and index not in parts:
parts[index] = {"type": "reasoning"}

if "text" in delta["reasoningContent"]:
cast("ReasoningPart", parts[index])["content"] = delta[
"reasoningContent"
]["text"]

yield event
continue

Check failure on line 277 in sentry_sdk/integrations/boto3/_services/bedrock.py

View check run for this annotation

@sentry/warden / warden: sdk-review

Stream event parsing not wrapped in capture_internal_exceptions

Wrap Sentry's EventStream event parsing in `capture_internal_exceptions()` so unexpected event shapes cannot raise into the user's iterator or finish the span as an error.
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Resolved

if (
"reasoningContent" in delta
and "text" in delta["reasoningContent"]
and "content" in parts[index]
):
cast("ReasoningPart", parts[index])["content"] += delta[
"reasoningContent"
]["text"]

yield event
continue

if "reasoningContent" in delta and "text" in delta["reasoningContent"]:
cast("ReasoningPart", parts[index])["content"] = delta[
"reasoningContent"
]["text"]

yield event
continue

if (
"image" in delta
and "source" in delta["image"]
and "s3Location" in delta["image"]["source"]
):
parts[index] = {
"type": "uri",
"modality": "image",
"uri": delta["image"]["source"]["s3Location"]["uri"],
}

yield event
continue

if (
"image" in delta
and "source" in delta["image"]
and "bytes" in delta["image"]["source"]
):
parts[index] = {
"type": "blob",
"modality": "image",
}

yield event
continue
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Resolved

yield event
except GeneratorExit:
Expand Down
Loading
Loading