Skip to content

Commit 3000488

Browse files
committed
feat(google-genai): Gate prompt/response collection on data_collection option
Modify the GoogleGenAI integration to respect the data_collection config for controlling whether prompts, responses, and tool calls are captured in spans. When data collection is enabled, the new gen_ai.inputs and gen_ai.outputs flags control what data is collected. When data collection is not configured, falls back to legacy send_default_pii and include_prompts settings for compatibility. Refs PY-2588
1 parent d03110e commit 3000488

3 files changed

Lines changed: 1226 additions & 44 deletions

File tree

sentry_sdk/integrations/google_genai/streaming.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from typing import TYPE_CHECKING, Any, List, Optional, TypedDict, Union
22

3+
import sentry_sdk
34
from sentry_sdk.ai.utils import set_data_normalized
45
from sentry_sdk.consts import SPANDATA
56
from sentry_sdk.scope import should_send_default_pii
67
from sentry_sdk.traces import StreamedSpan
78
from sentry_sdk.utils import (
9+
has_data_collection_enabled,
810
safe_serialize,
911
)
1012

@@ -106,16 +108,7 @@ def set_span_data_for_streaming_response(
106108
set_on_span = (
107109
span.set_attribute if isinstance(span, StreamedSpan) else span.set_data
108110
)
109-
110-
if (
111-
should_send_default_pii()
112-
and integration.include_prompts
113-
and accumulated_response.get("text")
114-
):
115-
set_on_span(
116-
SPANDATA.GEN_AI_RESPONSE_TEXT,
117-
safe_serialize([accumulated_response["text"]]),
118-
)
111+
client = sentry_sdk.get_client()
119112

120113
if accumulated_response.get("finish_reasons"):
121114
set_data_normalized(
@@ -124,12 +117,6 @@ def set_span_data_for_streaming_response(
124117
accumulated_response["finish_reasons"],
125118
)
126119

127-
if accumulated_response.get("tool_calls"):
128-
set_on_span(
129-
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
130-
safe_serialize(accumulated_response["tool_calls"]),
131-
)
132-
133120
response_id = accumulated_response.get("id")
134121
if response_id is not None:
135122
set_on_span(SPANDATA.GEN_AI_RESPONSE_ID, response_id)
@@ -170,3 +157,31 @@ def set_span_data_for_streaming_response(
170157
SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS,
171158
accumulated_response["usage_metadata"]["total_tokens"],
172159
)
160+
161+
if accumulated_response.get("tool_calls"):
162+
if has_data_collection_enabled(client.options):
163+
if client.options["data_collection"]["gen_ai"]["outputs"]:
164+
set_on_span(
165+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
166+
safe_serialize(accumulated_response["tool_calls"]),
167+
)
168+
else:
169+
# Before data collection was introduced this was unconditionally set
170+
set_on_span(
171+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
172+
safe_serialize(accumulated_response["tool_calls"]),
173+
)
174+
175+
if accumulated_response.get("text"):
176+
if has_data_collection_enabled(client.options):
177+
if client.options["data_collection"]["gen_ai"]["outputs"]:
178+
set_on_span(
179+
SPANDATA.GEN_AI_RESPONSE_TEXT,
180+
safe_serialize([accumulated_response["text"]]),
181+
)
182+
183+
elif should_send_default_pii() and integration.include_prompts:
184+
set_on_span(
185+
SPANDATA.GEN_AI_RESPONSE_TEXT,
186+
safe_serialize([accumulated_response["text"]]),
187+
)

sentry_sdk/integrations/google_genai/utils.py

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from sentry_sdk.utils import (
3737
capture_internal_exceptions,
3838
event_from_exception,
39+
has_data_collection_enabled,
3940
safe_serialize,
4041
)
4142

@@ -887,6 +888,7 @@ def set_span_data_for_request(
887888
kwargs: "dict[str, Any]",
888889
) -> None:
889890
"""Set span data for the request."""
891+
client = sentry_sdk.get_client()
890892
set_on_span = (
891893
span.set_attribute if isinstance(span, StreamedSpan) else span.set_data
892894
)
@@ -898,8 +900,37 @@ def set_span_data_for_request(
898900

899901
config: "Optional[GenerateContentConfig]" = kwargs.get("config")
900902

901-
# Set input messages/prompts if PII is allowed
902-
if should_send_default_pii() and integration.include_prompts:
903+
# Set tools if available
904+
if config is not None and hasattr(config, "tools"):
905+
tools = config.tools
906+
if tools:
907+
formatted_tools = _format_tools_for_span(tools)
908+
if formatted_tools:
909+
if has_data_collection_enabled(client.options):
910+
if client.options["data_collection"]["gen_ai"]["inputs"]:
911+
set_data_normalized(
912+
span,
913+
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
914+
formatted_tools,
915+
unpack=False,
916+
)
917+
else:
918+
# To remove once data collection has been fully rolled out
919+
set_data_normalized(
920+
span,
921+
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
922+
formatted_tools,
923+
unpack=False,
924+
)
925+
926+
record_inputs = False
927+
if has_data_collection_enabled(client.options):
928+
if client.options["data_collection"]["gen_ai"]["inputs"]:
929+
record_inputs = True
930+
elif should_send_default_pii() and integration.include_prompts:
931+
record_inputs = True
932+
933+
if record_inputs:
903934
messages = []
904935

905936
# Add system instruction if present
@@ -951,42 +982,19 @@ def set_span_data_for_request(
951982
if value is not None:
952983
set_on_span(span_key, value)
953984

954-
# Set tools if available
955-
if config is not None and hasattr(config, "tools"):
956-
tools = config.tools
957-
if tools:
958-
formatted_tools = _format_tools_for_span(tools)
959-
if formatted_tools:
960-
set_data_normalized(
961-
span,
962-
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
963-
formatted_tools,
964-
unpack=False,
965-
)
966-
967985

968986
def set_span_data_for_response(
969987
span: "Union[Span, StreamedSpan]",
970988
integration: "Any",
971989
response: "GenerateContentResponse",
972990
) -> None:
973-
"""Set span data for the response."""
974991
if not response:
975992
return
976993

994+
client = sentry_sdk.get_client()
977995
set_on_span = (
978996
span.set_attribute if isinstance(span, StreamedSpan) else span.set_data
979997
)
980-
if should_send_default_pii() and integration.include_prompts:
981-
response_texts = _extract_response_text(response)
982-
if response_texts:
983-
# Format as JSON string array as per documentation
984-
set_on_span(SPANDATA.GEN_AI_RESPONSE_TEXT, safe_serialize(response_texts))
985-
986-
tool_calls = extract_tool_calls(response)
987-
if tool_calls:
988-
# Tool calls should be JSON serialized
989-
set_on_span(SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS, safe_serialize(tool_calls))
990998

991999
finish_reasons = extract_finish_reasons(response)
9921000
if finish_reasons:
@@ -1023,6 +1031,31 @@ def set_span_data_for_response(
10231031
if usage_data["total_tokens"]:
10241032
set_on_span(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage_data["total_tokens"])
10251033

1034+
tool_calls = extract_tool_calls(response)
1035+
if tool_calls:
1036+
if has_data_collection_enabled(client.options):
1037+
if client.options["data_collection"]["gen_ai"]["outputs"]:
1038+
set_on_span(
1039+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS, safe_serialize(tool_calls)
1040+
)
1041+
else:
1042+
# Before data collection was introduced, this was set unconditionally
1043+
set_on_span(SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS, safe_serialize(tool_calls))
1044+
1045+
if has_data_collection_enabled(client.options):
1046+
if client.options["data_collection"]["gen_ai"]["outputs"]:
1047+
response_texts = _extract_response_text(response)
1048+
if response_texts:
1049+
set_on_span(
1050+
SPANDATA.GEN_AI_RESPONSE_TEXT, safe_serialize(response_texts)
1051+
)
1052+
elif should_send_default_pii() and integration.include_prompts:
1053+
# TODO: Delete this block once data collection has been completely rolled out
1054+
response_texts = _extract_response_text(response)
1055+
if response_texts:
1056+
# Format as JSON string array as per documentation
1057+
set_on_span(SPANDATA.GEN_AI_RESPONSE_TEXT, safe_serialize(response_texts))
1058+
10261059

10271060
def prepare_generate_content_args(
10281061
args: "tuple[Any, ...]", kwargs: "dict[str, Any]"
@@ -1062,8 +1095,16 @@ def set_span_data_for_embed_request(
10621095
kwargs: "dict[str, Any]",
10631096
) -> None:
10641097
"""Set span data for embedding request."""
1065-
# Include input contents if PII is allowed
1066-
if should_send_default_pii() and integration.include_prompts:
1098+
client = sentry_sdk.get_client()
1099+
1100+
record_inputs = False
1101+
if has_data_collection_enabled(client.options):
1102+
if client.options["data_collection"]["gen_ai"]["inputs"]:
1103+
record_inputs = True
1104+
elif should_send_default_pii() and integration.include_prompts:
1105+
record_inputs = True
1106+
1107+
if record_inputs:
10671108
if contents:
10681109
# For embeddings, contents is typically a list of strings/texts
10691110
input_texts = []

0 commit comments

Comments
 (0)