HOME


Mini Shell 1.0
DIR: /opt/hc_python/lib64/python3.12/site-packages/sentry_sdk/integrations/google_genai/
Upload File :
Current File : //opt/hc_python/lib64/python3.12/site-packages/sentry_sdk/integrations/google_genai/streaming.py
from typing import (
    TYPE_CHECKING,
    Any,
    List,
    TypedDict,
    Optional,
)

from sentry_sdk.ai.utils import set_data_normalized
from sentry_sdk.consts import SPANDATA
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
    safe_serialize,
)
from .utils import (
    extract_tool_calls,
    extract_finish_reasons,
    extract_contents_text,
    extract_usage_data,
    UsageData,
)

if TYPE_CHECKING:
    from sentry_sdk.tracing import Span
    from google.genai.types import GenerateContentResponse


class AccumulatedResponse(TypedDict):
    id: Optional[str]
    model: Optional[str]
    text: str
    finish_reasons: List[str]
    tool_calls: List[dict[str, Any]]
    usage_metadata: UsageData


def accumulate_streaming_response(chunks):
    # type: (List[GenerateContentResponse]) -> AccumulatedResponse
    """Accumulate streaming chunks into a single response-like object."""
    accumulated_text = []
    finish_reasons = []
    tool_calls = []
    total_input_tokens = 0
    total_output_tokens = 0
    total_tokens = 0
    total_cached_tokens = 0
    total_reasoning_tokens = 0
    response_id = None
    model = None

    for chunk in chunks:
        # Extract text and tool calls
        if getattr(chunk, "candidates", None):
            for candidate in getattr(chunk, "candidates", []):
                if hasattr(candidate, "content") and getattr(
                    candidate.content, "parts", []
                ):
                    extracted_text = extract_contents_text(candidate.content)
                    if extracted_text:
                        accumulated_text.append(extracted_text)

        extracted_finish_reasons = extract_finish_reasons(chunk)
        if extracted_finish_reasons:
            finish_reasons.extend(extracted_finish_reasons)

        extracted_tool_calls = extract_tool_calls(chunk)
        if extracted_tool_calls:
            tool_calls.extend(extracted_tool_calls)

        # Accumulate token usage
        extracted_usage_data = extract_usage_data(chunk)
        total_input_tokens += extracted_usage_data["input_tokens"]
        total_output_tokens += extracted_usage_data["output_tokens"]
        total_cached_tokens += extracted_usage_data["input_tokens_cached"]
        total_reasoning_tokens += extracted_usage_data["output_tokens_reasoning"]
        total_tokens += extracted_usage_data["total_tokens"]

    accumulated_response = AccumulatedResponse(
        text="".join(accumulated_text),
        finish_reasons=finish_reasons,
        tool_calls=tool_calls,
        usage_metadata=UsageData(
            input_tokens=total_input_tokens,
            output_tokens=total_output_tokens,
            input_tokens_cached=total_cached_tokens,
            output_tokens_reasoning=total_reasoning_tokens,
            total_tokens=total_tokens,
        ),
        id=response_id,
        model=model,
    )

    return accumulated_response


def set_span_data_for_streaming_response(span, integration, accumulated_response):
    # type: (Span, Any, AccumulatedResponse) -> None
    """Set span data for accumulated streaming response."""
    if (
        should_send_default_pii()
        and integration.include_prompts
        and accumulated_response.get("text")
    ):
        span.set_data(
            SPANDATA.GEN_AI_RESPONSE_TEXT,
            safe_serialize([accumulated_response["text"]]),
        )

    if accumulated_response.get("finish_reasons"):
        set_data_normalized(
            span,
            SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS,
            accumulated_response["finish_reasons"],
        )

    if accumulated_response.get("tool_calls"):
        span.set_data(
            SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
            safe_serialize(accumulated_response["tool_calls"]),
        )

    if accumulated_response.get("id"):
        span.set_data(SPANDATA.GEN_AI_RESPONSE_ID, accumulated_response["id"])
    if accumulated_response.get("model"):
        span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, accumulated_response["model"])

    if accumulated_response["usage_metadata"]["input_tokens"]:
        span.set_data(
            SPANDATA.GEN_AI_USAGE_INPUT_TOKENS,
            accumulated_response["usage_metadata"]["input_tokens"],
        )

    if accumulated_response["usage_metadata"]["input_tokens_cached"]:
        span.set_data(
            SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED,
            accumulated_response["usage_metadata"]["input_tokens_cached"],
        )

    if accumulated_response["usage_metadata"]["output_tokens"]:
        span.set_data(
            SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS,
            accumulated_response["usage_metadata"]["output_tokens"],
        )

    if accumulated_response["usage_metadata"]["output_tokens_reasoning"]:
        span.set_data(
            SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING,
            accumulated_response["usage_metadata"]["output_tokens_reasoning"],
        )

    if accumulated_response["usage_metadata"]["total_tokens"]:
        span.set_data(
            SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS,
            accumulated_response["usage_metadata"]["total_tokens"],
        )