From c4ebb20f70ada52f53f9b60c6f7f5114fd7ed38d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 21 Jan 2026 11:14:06 +0100 Subject: [PATCH 01/11] feat(google-genai): Set system instruction attribute --- sentry_sdk/consts.py | 6 ++++ sentry_sdk/integrations/google_genai/utils.py | 26 ++++---------- .../google_genai/test_google_genai.py | 34 +++++++++++++------ 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 93fca6ba3e..4b61a317fb 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -542,6 +542,12 @@ class SPANDATA: Example: 2048 """ + GEN_AI_SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions" + """ + The system instructions passed to the model. + Example: [{"type": "text", "text": "You are a helpful assistant."},{"type": "text", "text": "Be concise and clear."}] + """ + GEN_AI_REQUEST_MESSAGES = "gen_ai.request.messages" """ The messages passed to the model. The "content" can be a string or an array of objects. diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 3b18712d3e..aed0aeb18d 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -743,25 +743,13 @@ def set_span_data_for_request( # Add system instruction if present if config and hasattr(config, "system_instruction"): system_instruction = config.system_instruction - if system_instruction: - system_messages = extract_contents_messages(system_instruction) - # System instruction should be a single system message - # Extract text from all messages and combine into one system message - system_texts = [] - for msg in system_messages: - content = msg.get("content") - if isinstance(content, list): - # Extract text from content parts - for part in content: - if isinstance(part, dict) and part.get("type") == "text": - system_texts.append(part.get("text", "")) - elif isinstance(content, str): - system_texts.append(content) - - if system_texts: - messages.append( - {"role": "system", "content": " ".join(system_texts)} - ) + + set_data_normalized( + span, + SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, + system_instruction, + unpack=False, + ) # Extract messages from contents contents_messages = extract_contents_messages(contents) diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index ad89b878ea..eee61fbc12 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -229,14 +229,28 @@ def test_generate_content_with_system_instruction( (event,) = events invoke_span = event["spans"][0] - # Check that system instruction is included in messages # (PII is enabled and include_prompts is True in this test) - messages_str = invoke_span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES] - # Parse the JSON string to verify content - messages = json.loads(messages_str) - assert len(messages) == 2 - assert messages[0] == {"role": "system", "content": "You are a helpful assistant"} - assert messages[1] == {"role": "user", "content": "What is 2+2?"} + system_instructions = json.loads( + invoke_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] + ) + assert system_instructions == { + "parts": [ + { + "media_resolution": "None", + "code_execution_result": "None", + "executable_code": "None", + "file_data": "None", + "function_call": "None", + "function_response": "None", + "inline_data": "None", + "text": "You are a helpful assistant", + "thought": "None", + "thought_signature": "None", + "video_metadata": "None", + } + ], + "role": "system", + } def test_generate_content_with_tools(sentry_init, capture_events, mock_genai_client): @@ -933,10 +947,8 @@ def test_google_genai_message_truncation( with start_transaction(name="google_genai"): mock_genai_client.models.generate_content( model="gemini-1.5-flash", - contents=small_content, - config=create_test_config( - system_instruction=large_content, - ), + contents=[large_content, small_content], + config=create_test_config(), ) (event,) = events From 87687029e69173be67e0ad0870cab36dbd41b516 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 21 Jan 2026 14:21:02 +0100 Subject: [PATCH 02/11] . --- .../google_genai/test_google_genai.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index eee61fbc12..984a339d6e 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -233,24 +233,7 @@ def test_generate_content_with_system_instruction( system_instructions = json.loads( invoke_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] ) - assert system_instructions == { - "parts": [ - { - "media_resolution": "None", - "code_execution_result": "None", - "executable_code": "None", - "file_data": "None", - "function_call": "None", - "function_response": "None", - "inline_data": "None", - "text": "You are a helpful assistant", - "thought": "None", - "thought_signature": "None", - "video_metadata": "None", - } - ], - "role": "system", - } + assert system_instructions["parts"]["text"] == "You are a helpful assistant" def test_generate_content_with_tools(sentry_init, capture_events, mock_genai_client): From eb4a9ae1cdfc1f0c4870edce9cc7fc7b64adc54a Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 21 Jan 2026 14:26:36 +0100 Subject: [PATCH 03/11] . --- tests/integrations/google_genai/test_google_genai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index 984a339d6e..6433b3ab21 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -233,7 +233,7 @@ def test_generate_content_with_system_instruction( system_instructions = json.loads( invoke_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] ) - assert system_instructions["parts"]["text"] == "You are a helpful assistant" + assert system_instructions["parts"][0]["text"] == "You are a helpful assistant" def test_generate_content_with_tools(sentry_init, capture_events, mock_genai_client): From 48d6d178760ca6a4f774ed914d54b9c9c6ee17c7 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 14:13:41 +0100 Subject: [PATCH 04/11] . --- sentry_sdk/_types.py | 4 ++ sentry_sdk/integrations/google_genai/utils.py | 68 +++++++++++++++++-- .../google_genai/test_google_genai.py | 53 ++++++++++++++- 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/_types.py b/sentry_sdk/_types.py index 7043bbc2ee..ecb8abcd10 100644 --- a/sentry_sdk/_types.py +++ b/sentry_sdk/_types.py @@ -359,3 +359,7 @@ class SDKInfo(TypedDict): ) HttpStatusCodeRange = Union[int, Container[int]] + + class TextPart(TypedDict): + type: Literal["text"] + content: str diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index aed0aeb18d..a05bb47763 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -32,13 +32,16 @@ event_from_exception, safe_serialize, ) -from google.genai.types import GenerateContentConfig +from google.genai.types import GenerateContentConfig, Part, Content +from itertools import chain if TYPE_CHECKING: from sentry_sdk.tracing import Span + from sentry_sdk._types import TextPart from google.genai.types import ( GenerateContentResponse, ContentListUnion, + ContentUnionDict, Tool, Model, EmbedContentResponse, @@ -720,6 +723,56 @@ def extract_finish_reasons( return finish_reasons if finish_reasons else None +def _transform_system_instruction_one_level( + system_instructions: "ContentUnionDict", +) -> "list[TextPart]": + text_parts: "list[TextPart]" = [] + + if isinstance(system_instructions, str): + return [{"type": "text", "content": system_instructions}] + + if isinstance(system_instructions, Part) and system_instructions.text: + return [{"type": "text", "content": system_instructions.text}] + + if isinstance(system_instructions, Content): + for part in system_instructions.parts: + if part.text: + text_parts.append({"type": "text", "content": part.text}) + return text_parts + + if isinstance(system_instructions, dict): + if system_instructions.get("text"): + return [{"type": "text", "content": system_instructions["text"]}] + + parts = system_instructions.get("parts", []) + for part in parts: + if system_instructions.get("text"): + text_parts.append({"type": "text", "content": part["text"]}) + elif isinstance(part, Part) and part.text: + text_parts.append({"type": "text", "content": part.text}) + return text_parts + + return text_parts + + +def _transform_system_instructions( + system_instructions: "ContentUnionDict", +) -> "list[TextPart]": + text_parts: "list[TextPart]" = [] + + if isinstance(system_instructions, list): + text_parts = list( + chain.from_iterable( + _transform_system_instruction_one_level(instructions) + for instructions in system_instructions + ) + ) + + return text_parts + + return _transform_system_instruction_one_level(system_instructions) + + def set_span_data_for_request( span: "Span", integration: "Any", @@ -744,12 +797,13 @@ def set_span_data_for_request( if config and hasattr(config, "system_instruction"): system_instruction = config.system_instruction - set_data_normalized( - span, - SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, - system_instruction, - unpack=False, - ) + if system_instruction: + set_data_normalized( + span, + SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, + _transform_system_instructions(system_instruction), + unpack=False, + ) # Extract messages from contents contents_messages = extract_contents_messages(contents) diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index 6433b3ab21..b18b5b3c5a 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -4,6 +4,7 @@ from google import genai from google.genai import types as genai_types +from google.genai.types import Content, Part from sentry_sdk import start_transaction from sentry_sdk._types import BLOB_DATA_SUBSTITUTE @@ -186,6 +187,7 @@ def test_nonstreaming_generate_content( response_texts = json.loads(response_text) assert response_texts == ["Hello! How can I help you today?"] else: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in invoke_span["data"] assert SPANDATA.GEN_AI_RESPONSE_TEXT not in chat_span["data"] @@ -202,8 +204,47 @@ def test_nonstreaming_generate_content( assert invoke_span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 +# Vibed test cases +@pytest.mark.parametrize( + "system_instructions,expected_texts", + [ + ("You are a helpful assistant", "You are a helpful assistant"), + ( + ["You are a translator", "Translate to French"], + ["You are a translator", "Translate to French"], + ), + ( + Content(role="user", parts=[Part(text="You are a helpful assistant")]), + "You are a helpful assistant", + ), + ( + {"parts": [{"text": "You are a helpful assistant"}], "role": "user"}, + "You are a helpful assistant", + ), + (Part(text="You are a helpful assistant"), "You are a helpful assistant"), + ({"text": "You are a helpful assistant"}, "You are a helpful assistant"), + ( + [Part(text="You are a translator"), Part(text="Translate to French")], + ["You are a translator", "Translate to French"], + ), + ( + [{"text": "You are a translator"}, {"text": "Translate to French"}], + ["You are a translator", "Translate to French"], + ), + ( + Content( + role="user", + parts=[ + Part(text="You are a translator"), + Part(text="Translate to French"), + ], + ), + ["You are a translator", "Translate to French"], + ), + ], +) def test_generate_content_with_system_instruction( - sentry_init, capture_events, mock_genai_client + sentry_init, capture_events, mock_genai_client, system_instructions, expected_texts ): sentry_init( integrations=[GoogleGenAIIntegration(include_prompts=True)], @@ -219,7 +260,7 @@ def test_generate_content_with_system_instruction( ): with start_transaction(name="google_genai"): config = create_test_config( - system_instruction="You are a helpful assistant", + system_instruction=system_instructions, temperature=0.5, ) mock_genai_client.models.generate_content( @@ -233,7 +274,13 @@ def test_generate_content_with_system_instruction( system_instructions = json.loads( invoke_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] ) - assert system_instructions["parts"][0]["text"] == "You are a helpful assistant" + + if isinstance(expected_texts, str): + assert system_instructions == [{"type": "text", "content": expected_texts}] + else: + assert system_instructions == [ + {"type": "text", "content": text} for text in expected_texts + ] def test_generate_content_with_tools(sentry_init, capture_events, mock_genai_client): From 9031f00c140d2f34b4f33e3065fda17bd0e86e10 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 14:22:20 +0100 Subject: [PATCH 05/11] . --- sentry_sdk/integrations/google_genai/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index a05bb47763..12dd7dac1b 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -746,10 +746,10 @@ def _transform_system_instruction_one_level( parts = system_instructions.get("parts", []) for part in parts: - if system_instructions.get("text"): - text_parts.append({"type": "text", "content": part["text"]}) + if part.get("text"): + part.append({"type": "text", "content": part["text"]}) elif isinstance(part, Part) and part.text: - text_parts.append({"type": "text", "content": part.text}) + part.append({"type": "text", "content": part.text}) return text_parts return text_parts From 2b9bd4e46d1f70bc70f069b6617893ee3939dd92 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 14:36:59 +0100 Subject: [PATCH 06/11] more tests --- sentry_sdk/integrations/google_genai/utils.py | 6 +- .../google_genai/test_google_genai.py | 80 ++++++++++++++++--- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 12dd7dac1b..adf09cd892 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -735,7 +735,7 @@ def _transform_system_instruction_one_level( return [{"type": "text", "content": system_instructions.text}] if isinstance(system_instructions, Content): - for part in system_instructions.parts: + for part in system_instructions.parts or []: if part.text: text_parts.append({"type": "text", "content": part.text}) return text_parts @@ -747,9 +747,9 @@ def _transform_system_instruction_one_level( parts = system_instructions.get("parts", []) for part in parts: if part.get("text"): - part.append({"type": "text", "content": part["text"]}) + text_parts.append({"type": "text", "content": part["text"]}) elif isinstance(part, Part) and part.text: - part.append({"type": "text", "content": part.text}) + text_parts.append({"type": "text", "content": part.text}) return text_parts return text_parts diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index b18b5b3c5a..6cd2d9d389 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -204,25 +204,45 @@ def test_nonstreaming_generate_content( assert invoke_span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 -# Vibed test cases +# Threatened bot to generate as many cases as possible @pytest.mark.parametrize( "system_instructions,expected_texts", [ - ("You are a helpful assistant", "You are a helpful assistant"), + ("You are a helpful assistant", ["You are a helpful assistant"]), ( ["You are a translator", "Translate to French"], ["You are a translator", "Translate to French"], ), ( Content(role="user", parts=[Part(text="You are a helpful assistant")]), - "You are a helpful assistant", + ["You are a helpful assistant"], + ), + ( + Content( + role="user", + parts=[ + Part(text="You are a translator"), + Part(text="Translate to French"), + ], + ), + ["You are a translator", "Translate to French"], ), ( {"parts": [{"text": "You are a helpful assistant"}], "role": "user"}, - "You are a helpful assistant", + ["You are a helpful assistant"], ), - (Part(text="You are a helpful assistant"), "You are a helpful assistant"), - ({"text": "You are a helpful assistant"}, "You are a helpful assistant"), + ( + { + "parts": [ + {"text": "You are a translator"}, + {"text": "Translate to French"}, + ], + "role": "user", + }, + ["You are a translator", "Translate to French"], + ), + (Part(text="You are a helpful assistant"), ["You are a helpful assistant"]), + ({"text": "You are a helpful assistant"}, ["You are a helpful assistant"]), ( [Part(text="You are a translator"), Part(text="Translate to French")], ["You are a translator", "Translate to French"], @@ -232,14 +252,44 @@ def test_nonstreaming_generate_content( ["You are a translator", "Translate to French"], ), ( - Content( - role="user", - parts=[ - Part(text="You are a translator"), - Part(text="Translate to French"), + [Part(text="First instruction"), {"text": "Second instruction"}], + ["First instruction", "Second instruction"], + ), + ( + { + "parts": [ + Part(text="First instruction"), + {"text": "Second instruction"}, ], - ), - ["You are a translator", "Translate to French"], + "role": "user", + }, + ["First instruction", "Second instruction"], + ), + (None, None), + ("", []), + ({}, []), + ({"parts": []}, []), + (Content(role="user", parts=[]), []), + ( + { + "parts": [ + {"text": "Text part"}, + {"file_data": {"file_uri": "gs://bucket/file.pdf"}}, + ], + "role": "user", + }, + ["Text part"], + ), + ( + { + "parts": [ + {"text": "First"}, + Part(text="Second"), + {"text": "Third"}, + ], + "role": "user", + }, + ["First", "Second", "Third"], ), ], ) @@ -270,6 +320,10 @@ def test_generate_content_with_system_instruction( (event,) = events invoke_span = event["spans"][0] + if expected_texts is None: + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_span["data"] + return + # (PII is enabled and include_prompts is True in this test) system_instructions = json.loads( invoke_span["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS] From 7678afcb604e7668ff68a57b93ffe0c1f157e972 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 14:47:31 +0100 Subject: [PATCH 07/11] . --- sentry_sdk/integrations/google_genai/utils.py | 6 +++--- tests/integrations/google_genai/test_google_genai.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index adf09cd892..896f8b036f 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -746,10 +746,10 @@ def _transform_system_instruction_one_level( parts = system_instructions.get("parts", []) for part in parts: - if part.get("text"): - text_parts.append({"type": "text", "content": part["text"]}) - elif isinstance(part, Part) and part.text: + if isinstance(part, Part) and part.text: text_parts.append({"type": "text", "content": part.text}) + elif isinstance(system_instructions, dict) and part.get("text"): + text_parts.append({"type": "text", "content": part["text"]}) return text_parts return text_parts diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index 6cd2d9d389..5527350c14 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -291,6 +291,16 @@ def test_nonstreaming_generate_content( }, ["First", "Second", "Third"], ), + ( + { + "parts": [ + Part(text="First"), + Part(text="Second"), + Part(text="Third"), + ], + }, + ["First", "Second", "Third"], + ), ], ) def test_generate_content_with_system_instruction( From 64e344365532fc8840872f0dde5555b2a118db42 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 14:49:57 +0100 Subject: [PATCH 08/11] check for none instead of truthy --- sentry_sdk/integrations/google_genai/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 896f8b036f..7ba6950d39 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -797,7 +797,7 @@ def set_span_data_for_request( if config and hasattr(config, "system_instruction"): system_instruction = config.system_instruction - if system_instruction: + if system_instruction is not None: set_data_normalized( span, SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, From 8208252981b43cdbd7aa023f61e22fb0cb2bb1e0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 18:58:55 +0100 Subject: [PATCH 09/11] fully address comment --- sentry_sdk/integrations/google_genai/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 7ba6950d39..71a78e97c2 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -748,7 +748,7 @@ def _transform_system_instruction_one_level( for part in parts: if isinstance(part, Part) and part.text: text_parts.append({"type": "text", "content": part.text}) - elif isinstance(system_instructions, dict) and part.get("text"): + elif isinstance(part, dict) and part.get("text"): text_parts.append({"type": "text", "content": part["text"]}) return text_parts From 9abe62030d07c3cf0d449cecdb5d1eebedcd6b07 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 19:35:37 +0100 Subject: [PATCH 10/11] manually write test cases to be exhaustive --- sentry_sdk/integrations/google_genai/utils.py | 12 ++- .../google_genai/test_google_genai.py | 97 +++---------------- 2 files changed, 22 insertions(+), 87 deletions(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 71a78e97c2..9748810e2f 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -725,6 +725,7 @@ def extract_finish_reasons( def _transform_system_instruction_one_level( system_instructions: "ContentUnionDict", + can_be_content: bool, ) -> "list[TextPart]": text_parts: "list[TextPart]" = [] @@ -734,7 +735,7 @@ def _transform_system_instruction_one_level( if isinstance(system_instructions, Part) and system_instructions.text: return [{"type": "text", "content": system_instructions.text}] - if isinstance(system_instructions, Content): + if can_be_content and isinstance(system_instructions, Content): for part in system_instructions.parts or []: if part.text: text_parts.append({"type": "text", "content": part.text}) @@ -744,6 +745,7 @@ def _transform_system_instruction_one_level( if system_instructions.get("text"): return [{"type": "text", "content": system_instructions["text"]}] + elif can_be_content and isinstance(system_instructions, dict): parts = system_instructions.get("parts", []) for part in parts: if isinstance(part, Part) and part.text: @@ -763,14 +765,18 @@ def _transform_system_instructions( if isinstance(system_instructions, list): text_parts = list( chain.from_iterable( - _transform_system_instruction_one_level(instructions) + _transform_system_instruction_one_level( + instructions, can_be_content=False + ) for instructions in system_instructions ) ) return text_parts - return _transform_system_instruction_one_level(system_instructions) + return _transform_system_instruction_one_level( + system_instructions, can_be_content=True + ) def set_span_data_for_request( diff --git a/tests/integrations/google_genai/test_google_genai.py b/tests/integrations/google_genai/test_google_genai.py index 5527350c14..c5d4e2f75b 100644 --- a/tests/integrations/google_genai/test_google_genai.py +++ b/tests/integrations/google_genai/test_google_genai.py @@ -204,103 +204,32 @@ def test_nonstreaming_generate_content( assert invoke_span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 -# Threatened bot to generate as many cases as possible @pytest.mark.parametrize( "system_instructions,expected_texts", [ - ("You are a helpful assistant", ["You are a helpful assistant"]), - ( - ["You are a translator", "Translate to French"], - ["You are a translator", "Translate to French"], - ), - ( - Content(role="user", parts=[Part(text="You are a helpful assistant")]), - ["You are a helpful assistant"], - ), - ( - Content( - role="user", - parts=[ - Part(text="You are a translator"), - Part(text="Translate to French"), - ], - ), - ["You are a translator", "Translate to French"], - ), - ( - {"parts": [{"text": "You are a helpful assistant"}], "role": "user"}, - ["You are a helpful assistant"], - ), - ( - { - "parts": [ - {"text": "You are a translator"}, - {"text": "Translate to French"}, - ], - "role": "user", - }, - ["You are a translator", "Translate to French"], - ), - (Part(text="You are a helpful assistant"), ["You are a helpful assistant"]), - ({"text": "You are a helpful assistant"}, ["You are a helpful assistant"]), - ( - [Part(text="You are a translator"), Part(text="Translate to French")], - ["You are a translator", "Translate to French"], - ), - ( - [{"text": "You are a translator"}, {"text": "Translate to French"}], - ["You are a translator", "Translate to French"], - ), - ( - [Part(text="First instruction"), {"text": "Second instruction"}], - ["First instruction", "Second instruction"], - ), - ( - { - "parts": [ - Part(text="First instruction"), - {"text": "Second instruction"}, - ], - "role": "user", - }, - ["First instruction", "Second instruction"], - ), (None, None), ("", []), ({}, []), + (Content(role="system", parts=[]), []), ({"parts": []}, []), - (Content(role="user", parts=[]), []), + ("You are a helpful assistant.", ["You are a helpful assistant."]), + (Part(text="You are a helpful assistant."), ["You are a helpful assistant."]), ( - { - "parts": [ - {"text": "Text part"}, - {"file_data": {"file_uri": "gs://bucket/file.pdf"}}, - ], - "role": "user", - }, - ["Text part"], + Content(role="system", parts=[Part(text="You are a helpful assistant.")]), + ["You are a helpful assistant."], ), + ({"text": "You are a helpful assistant."}, ["You are a helpful assistant."]), ( - { - "parts": [ - {"text": "First"}, - Part(text="Second"), - {"text": "Third"}, - ], - "role": "user", - }, - ["First", "Second", "Third"], + {"parts": [Part(text="You are a helpful assistant.")]}, + ["You are a helpful assistant."], ), ( - { - "parts": [ - Part(text="First"), - Part(text="Second"), - Part(text="Third"), - ], - }, - ["First", "Second", "Third"], + {"parts": [{"text": "You are a helpful assistant."}]}, + ["You are a helpful assistant."], ), + (["You are a helpful assistant."], ["You are a helpful assistant."]), + ([Part(text="You are a helpful assistant.")], ["You are a helpful assistant."]), + ([{"text": "You are a helpful assistant."}], ["You are a helpful assistant."]), ], ) def test_generate_content_with_system_instruction( From a3306721be8e76a595b54afa2055521f74c8a20e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 22 Jan 2026 20:30:21 +0100 Subject: [PATCH 11/11] fix if condition --- sentry_sdk/integrations/google_genai/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 9748810e2f..2864f57d49 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -741,9 +741,8 @@ def _transform_system_instruction_one_level( text_parts.append({"type": "text", "content": part.text}) return text_parts - if isinstance(system_instructions, dict): - if system_instructions.get("text"): - return [{"type": "text", "content": system_instructions["text"]}] + if isinstance(system_instructions, dict) and system_instructions.get("text"): + return [{"type": "text", "content": system_instructions["text"]}] elif can_be_content and isinstance(system_instructions, dict): parts = system_instructions.get("parts", [])