Skip to content

Commit 7a5fcbc

Browse files
committed
fix(ai): Gate response tool calls on the gen_ai inputs setting
`gen_ai.response.tool_calls` was gated on `data_collection.gen_ai.outputs` in the OpenAI, Anthropic, OpenAI Agents, and Google GenAI integrations. Tool calls belong to the inputs category, so they are now gated on `data_collection.gen_ai.inputs` while response text stays on `outputs`. Where both attributes previously shared a single gate, the block is split so each is evaluated independently. The legacy `send_default_pii`/`include_prompts` path is unchanged. Refs PY-2588 Refs #6748
1 parent 440e921 commit 7a5fcbc

9 files changed

Lines changed: 143 additions & 37 deletions

File tree

sentry_sdk/integrations/anthropic.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -608,14 +608,16 @@ def _set_output_data(
608608
set_on_span(SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS, [finish_reason])
609609

610610
client = sentry_sdk.get_client()
611+
record_inputs = False
611612
record_outputs = False
612613
if has_data_collection_enabled(client.options):
613-
if client.options["data_collection"]["gen_ai"]["outputs"]:
614-
record_outputs = True
614+
record_inputs = client.options["data_collection"]["gen_ai"]["inputs"]
615+
record_outputs = client.options["data_collection"]["gen_ai"]["outputs"]
615616
elif should_send_default_pii() and integration.include_prompts:
617+
record_inputs = True
616618
record_outputs = True
617619

618-
if record_outputs:
620+
if record_inputs or record_outputs:
619621
output_messages: "dict[str, list[Any]]" = {
620622
"response": [],
621623
"tool": [],
@@ -627,15 +629,15 @@ def _set_output_data(
627629
elif output["type"] == "tool_use":
628630
output_messages["tool"].append(output)
629631

630-
if len(output_messages["tool"]) > 0:
632+
if record_inputs and len(output_messages["tool"]) > 0:
631633
set_data_normalized(
632634
span,
633635
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
634636
output_messages["tool"],
635637
unpack=False,
636638
)
637639

638-
if len(output_messages["response"]) > 0:
640+
if record_outputs and len(output_messages["response"]) > 0:
639641
set_data_normalized(
640642
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
641643
)

sentry_sdk/integrations/google_genai/streaming.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def set_span_data_for_streaming_response(
160160

161161
if accumulated_response.get("tool_calls"):
162162
if has_data_collection_enabled(client.options):
163-
if client.options["data_collection"]["gen_ai"]["outputs"]:
163+
if client.options["data_collection"]["gen_ai"]["inputs"]:
164164
set_on_span(
165165
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
166166
safe_serialize(accumulated_response["tool_calls"]),

sentry_sdk/integrations/google_genai/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ def set_span_data_for_response(
10341034
tool_calls = extract_tool_calls(response)
10351035
if tool_calls:
10361036
if has_data_collection_enabled(client.options):
1037-
if client.options["data_collection"]["gen_ai"]["outputs"]:
1037+
if client.options["data_collection"]["gen_ai"]["inputs"]:
10381038
set_on_span(
10391039
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS, safe_serialize(tool_calls)
10401040
)

sentry_sdk/integrations/openai.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,10 @@ def _set_common_output_data(
712712
}
713713

714714
if has_data_collection_enabled(client.options):
715-
if client.options["data_collection"]["gen_ai"]["outputs"]:
715+
record_inputs = client.options["data_collection"]["gen_ai"]["inputs"]
716+
record_outputs = client.options["data_collection"]["gen_ai"]["outputs"]
717+
718+
if record_inputs or record_outputs:
716719
for output in response.output:
717720
if output.type == "function_call":
718721
output_messages["tool"].append(output.dict())
@@ -726,15 +729,15 @@ def _set_common_output_data(
726729
output_message.dict()
727730
)
728731

729-
if len(output_messages["tool"]) > 0:
732+
if record_inputs and len(output_messages["tool"]) > 0:
730733
set_data_normalized(
731734
span,
732735
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
733736
output_messages["tool"],
734737
unpack=False,
735738
)
736739

737-
if len(output_messages["response"]) > 0:
740+
if record_outputs and len(output_messages["response"]) > 0:
738741
set_data_normalized(
739742
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
740743
)

sentry_sdk/integrations/openai_agents/utils.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,16 @@ def _set_output_data(
209209
span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", result: "Any"
210210
) -> None:
211211
client = sentry_sdk.get_client()
212+
record_inputs = False
213+
record_outputs = False
212214
if has_data_collection_enabled(client.options):
213-
if not client.options["data_collection"]["gen_ai"]["outputs"]:
214-
return
215-
elif not should_send_default_pii():
215+
record_inputs = client.options["data_collection"]["gen_ai"]["inputs"]
216+
record_outputs = client.options["data_collection"]["gen_ai"]["outputs"]
217+
elif should_send_default_pii():
218+
record_inputs = True
219+
record_outputs = True
220+
221+
if not record_inputs and not record_outputs:
216222
return
217223

218224
output_messages: "dict[str, list[Any]]" = {
@@ -231,7 +237,7 @@ def _set_output_data(
231237
# Unknown output message type, just return the json
232238
output_messages["response"].append(output_message.dict())
233239

234-
if len(output_messages["tool"]) > 0:
240+
if record_inputs and len(output_messages["tool"]) > 0:
235241
if isinstance(span, StreamedSpan):
236242
span.set_attribute(
237243
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
@@ -243,7 +249,7 @@ def _set_output_data(
243249
safe_serialize(output_messages["tool"]),
244250
)
245251

246-
if len(output_messages["response"]) > 0:
252+
if record_outputs and len(output_messages["response"]) > 0:
247253
set_data_normalized(
248254
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
249255
)

tests/integrations/anthropic/test_anthropic.py

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -639,41 +639,62 @@ async def test_nonstreaming_create_message_data_collection_async(
639639
@pytest.mark.parametrize("span_streaming", [True, False])
640640
@pytest.mark.parametrize("stream_gen_ai_spans", [True, False])
641641
@pytest.mark.parametrize(
642-
"data_collection,send_default_pii,include_prompts,outputs_collected",
642+
"data_collection,send_default_pii,include_prompts,outputs_collected,tool_calls_collected",
643643
[
644644
pytest.param(
645645
{"gen_ai": {"outputs": True}},
646646
False,
647647
False,
648648
True,
649+
True,
649650
id="gen-ai-outputs-enabled-overrides-pii-and-include-prompts",
650651
),
651652
pytest.param(
652653
{"gen_ai": {"outputs": False}},
653654
True,
654655
True,
655656
False,
656-
id="gen-ai-outputs-disabled-overrides-pii-and-include-prompts",
657+
True,
658+
id="gen-ai-outputs-disabled-still-collects-tool-calls-gated-on-inputs",
659+
),
660+
pytest.param(
661+
{"gen_ai": {"inputs": False}},
662+
True,
663+
True,
664+
True,
665+
False,
666+
id="gen-ai-inputs-disabled-drops-tool-calls-only",
667+
),
668+
pytest.param(
669+
{"gen_ai": {"inputs": False, "outputs": False}},
670+
True,
671+
True,
672+
False,
673+
False,
674+
id="gen-ai-inputs-and-outputs-disabled-overrides-pii-and-include-prompts",
657675
),
658676
pytest.param(
659677
{"gen_ai": {}},
660678
False,
661679
False,
662680
True,
663-
id="gen-ai-outputs-omitted-defaults-to-enabled",
681+
True,
682+
id="gen-ai-inputs-and-outputs-omitted-defaults-to-enabled",
664683
),
665684
pytest.param(
666685
None,
667686
True,
668687
True,
669688
True,
689+
True,
670690
id="legacy-pii-and-include-prompts-enabled",
671691
),
672692
pytest.param(
673693
None,
674694
False,
675695
True,
676696
False,
697+
False,
677698
id="legacy-pii-disabled",
678699
),
679700
],
@@ -686,6 +707,7 @@ def test_nonstreaming_create_message_data_collection_outputs(
686707
send_default_pii,
687708
include_prompts,
688709
outputs_collected,
710+
tool_calls_collected,
689711
stream_gen_ai_spans,
690712
span_streaming,
691713
):
@@ -743,12 +765,15 @@ def test_nonstreaming_create_message_data_collection_outputs(
743765
span_data[SPANDATA.GEN_AI_RESPONSE_TEXT]
744766
== DATA_COLLECTION_EXPECTED_RESPONSE_TEXT
745767
)
768+
else:
769+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span_data
770+
771+
if tool_calls_collected:
746772
assert (
747773
json.loads(span_data[SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS])
748774
== DATA_COLLECTION_EXPECTED_TOOL_CALLS
749775
)
750776
else:
751-
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span_data
752777
assert SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS not in span_data
753778

754779

@@ -760,41 +785,62 @@ def test_nonstreaming_create_message_data_collection_outputs(
760785
@pytest.mark.parametrize("stream_gen_ai_spans", [True, False])
761786
@pytest.mark.asyncio
762787
@pytest.mark.parametrize(
763-
"data_collection,send_default_pii,include_prompts,outputs_collected",
788+
"data_collection,send_default_pii,include_prompts,outputs_collected,tool_calls_collected",
764789
[
765790
pytest.param(
766791
{"gen_ai": {"outputs": True}},
767792
False,
768793
False,
769794
True,
795+
True,
770796
id="gen-ai-outputs-enabled-overrides-pii-and-include-prompts",
771797
),
772798
pytest.param(
773799
{"gen_ai": {"outputs": False}},
774800
True,
775801
True,
776802
False,
777-
id="gen-ai-outputs-disabled-overrides-pii-and-include-prompts",
803+
True,
804+
id="gen-ai-outputs-disabled-still-collects-tool-calls-gated-on-inputs",
805+
),
806+
pytest.param(
807+
{"gen_ai": {"inputs": False}},
808+
True,
809+
True,
810+
True,
811+
False,
812+
id="gen-ai-inputs-disabled-drops-tool-calls-only",
813+
),
814+
pytest.param(
815+
{"gen_ai": {"inputs": False, "outputs": False}},
816+
True,
817+
True,
818+
False,
819+
False,
820+
id="gen-ai-inputs-and-outputs-disabled-overrides-pii-and-include-prompts",
778821
),
779822
pytest.param(
780823
{"gen_ai": {}},
781824
False,
782825
False,
783826
True,
784-
id="gen-ai-outputs-omitted-defaults-to-enabled",
827+
True,
828+
id="gen-ai-inputs-and-outputs-omitted-defaults-to-enabled",
785829
),
786830
pytest.param(
787831
None,
788832
True,
789833
True,
790834
True,
835+
True,
791836
id="legacy-pii-and-include-prompts-enabled",
792837
),
793838
pytest.param(
794839
None,
795840
False,
796841
True,
797842
False,
843+
False,
798844
id="legacy-pii-disabled",
799845
),
800846
],
@@ -807,6 +853,7 @@ async def test_nonstreaming_create_message_data_collection_outputs_async(
807853
send_default_pii,
808854
include_prompts,
809855
outputs_collected,
856+
tool_calls_collected,
810857
stream_gen_ai_spans,
811858
span_streaming,
812859
):
@@ -864,12 +911,15 @@ async def test_nonstreaming_create_message_data_collection_outputs_async(
864911
span_data[SPANDATA.GEN_AI_RESPONSE_TEXT]
865912
== DATA_COLLECTION_EXPECTED_RESPONSE_TEXT
866913
)
914+
else:
915+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span_data
916+
917+
if tool_calls_collected:
867918
assert (
868919
json.loads(span_data[SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS])
869920
== DATA_COLLECTION_EXPECTED_TOOL_CALLS
870921
)
871922
else:
872-
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span_data
873923
assert SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS not in span_data
874924

875925

tests/integrations/google_genai/test_google_genai.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3843,11 +3843,19 @@ def test_generate_content_data_collection(
38433843
{"gen_ai": {"inputs": True, "outputs": False}},
38443844
[
38453845
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
3846+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
38463847
],
3848+
[],
3849+
id="gen-ai-inputs-enabled-outputs-disabled-tools-collected",
3850+
),
3851+
pytest.param(
3852+
{"gen_ai": {"inputs": False, "outputs": True}},
3853+
[],
38473854
[
3855+
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
38483856
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
38493857
],
3850-
id="gen-ai-inputs-enabled-outputs-disabled-available-tools-only",
3858+
id="gen-ai-inputs-disabled-outputs-enabled-tools-not-collected",
38513859
),
38523860
pytest.param(
38533861
{"gen_ai": {}},
@@ -4195,11 +4203,19 @@ def test_streaming_generate_content_data_collection(
41954203
{"gen_ai": {"inputs": True, "outputs": False}},
41964204
[
41974205
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
4206+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
41984207
],
4208+
[],
4209+
id="gen-ai-inputs-enabled-outputs-disabled-tools-collected",
4210+
),
4211+
pytest.param(
4212+
{"gen_ai": {"inputs": False, "outputs": True}},
4213+
[],
41994214
[
4215+
SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
42004216
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
42014217
],
4202-
id="gen-ai-inputs-enabled-outputs-disabled-available-tools-only",
4218+
id="gen-ai-inputs-disabled-outputs-enabled-tools-not-collected",
42034219
),
42044220
pytest.param(
42054221
{"gen_ai": {}},

0 commit comments

Comments
 (0)