@@ -137,6 +137,26 @@ async def __call__(self, *args, **kwargs):
137137 }
138138]
139139
140+ EXAMPLE_COMPLETIONS_TOOLS = [
141+ {
142+ "type" : "function" ,
143+ "function" : {
144+ "name" : "get_current_weather" ,
145+ "description" : "Get the current weather in a given location" ,
146+ "parameters" : {
147+ "type" : "object" ,
148+ "properties" : {
149+ "location" : {
150+ "type" : "string" ,
151+ "description" : "The city and state, e.g. San Francisco, CA" ,
152+ },
153+ },
154+ "required" : ["location" ],
155+ },
156+ },
157+ }
158+ ]
159+
140160
141161@pytest .mark .skipif (
142162 OPENAI_VERSION <= (2 , 10 , 0 ),
@@ -639,6 +659,154 @@ def test_nonstreaming_chat_completion(
639659 assert span ["data" ]["gen_ai.usage.total_tokens" ] == 30
640660
641661
662+ @pytest .mark .skipif (
663+ OPENAI_VERSION <= (1 , 1 , 0 ),
664+ reason = "OpenAI versions <=1.1.0 do not support the tools parameter." ,
665+ )
666+ @pytest .mark .parametrize ("span_streaming" , [True , False ])
667+ @pytest .mark .parametrize ("stream_gen_ai_spans" , [True , False ])
668+ @pytest .mark .parametrize (
669+ "data_collection,include_prompts,expected_present,expected_absent" ,
670+ [
671+ pytest .param (
672+ {"gen_ai" : {"inputs" : True }},
673+ True ,
674+ {
675+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS : json .dumps (
676+ [{"type" : "text" , "content" : "You are a helpful assistant." }]
677+ ),
678+ SPANDATA .GEN_AI_REQUEST_MESSAGES : safe_serialize (
679+ [{"role" : "user" , "content" : "hello" }]
680+ ),
681+ SPANDATA .GEN_AI_TOOL_DEFINITIONS : safe_serialize (EXAMPLE_TOOLS ),
682+ },
683+ [],
684+ id = "inputs-enabled" ,
685+ ),
686+ pytest .param (
687+ {"gen_ai" : {"inputs" : False }},
688+ True ,
689+ {},
690+ [
691+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS ,
692+ SPANDATA .GEN_AI_REQUEST_MESSAGES ,
693+ SPANDATA .GEN_AI_TOOL_DEFINITIONS ,
694+ ],
695+ id = "inputs-disabled" ,
696+ ),
697+ pytest .param (
698+ {},
699+ True ,
700+ {
701+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS : json .dumps (
702+ [{"type" : "text" , "content" : "You are a helpful assistant." }]
703+ ),
704+ SPANDATA .GEN_AI_REQUEST_MESSAGES : safe_serialize (
705+ [{"role" : "user" , "content" : "hello" }]
706+ ),
707+ SPANDATA .GEN_AI_TOOL_DEFINITIONS : safe_serialize (EXAMPLE_TOOLS ),
708+ },
709+ [],
710+ id = "gen-ai-omitted-defaults-to-enabled" ,
711+ ),
712+ pytest .param (
713+ {"gen_ai" : {"inputs" : True }},
714+ False ,
715+ {},
716+ [
717+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS ,
718+ SPANDATA .GEN_AI_REQUEST_MESSAGES ,
719+ SPANDATA .GEN_AI_TOOL_DEFINITIONS ,
720+ ],
721+ id = "include-prompts-disabled-overrides-inputs-enabled" ,
722+ ),
723+ ],
724+ )
725+ def test_completions_api_data_collection (
726+ sentry_init ,
727+ capture_events ,
728+ capture_items ,
729+ data_collection ,
730+ include_prompts ,
731+ expected_present ,
732+ expected_absent ,
733+ nonstreaming_chat_completions_model_response ,
734+ stream_gen_ai_spans ,
735+ span_streaming ,
736+ ):
737+ sentry_init (
738+ integrations = [OpenAIIntegration (include_prompts = include_prompts )],
739+ disabled_integrations = [StdlibIntegration ],
740+ traces_sample_rate = 1.0 ,
741+ _experiments = {"data_collection" : data_collection },
742+ stream_gen_ai_spans = stream_gen_ai_spans ,
743+ trace_lifecycle = "stream" if span_streaming else "static" ,
744+ )
745+
746+ client = OpenAI (api_key = "z" )
747+ client .chat .completions ._post = mock .Mock (
748+ return_value = nonstreaming_chat_completions_model_response (
749+ response_id = "chat-id" ,
750+ response_model = "gpt-3.5-turbo" ,
751+ message_content = "the model response" ,
752+ created = 10000000 ,
753+ usage = CompletionUsage (
754+ prompt_tokens = 20 ,
755+ completion_tokens = 10 ,
756+ total_tokens = 30 ,
757+ ),
758+ )
759+ )
760+
761+ create_kwargs = {
762+ "model" : "some-model" ,
763+ "messages" : [
764+ {"role" : "system" , "content" : "You are a helpful assistant." },
765+ {"role" : "user" , "content" : "hello" },
766+ ],
767+ "max_tokens" : 100 ,
768+ "presence_penalty" : 0.1 ,
769+ "frequency_penalty" : 0.2 ,
770+ "temperature" : 0.7 ,
771+ "top_p" : 0.9 ,
772+ "tools" : EXAMPLE_COMPLETIONS_TOOLS ,
773+ }
774+
775+ if span_streaming or stream_gen_ai_spans :
776+ items = capture_items ("span" )
777+
778+ with start_transaction (name = "openai tx" ):
779+ client .chat .completions .create (** create_kwargs )
780+
781+ sentry_sdk .flush ()
782+ (span ,) = (item .payload for item in items )
783+ span_data = span ["attributes" ]
784+ else :
785+ events = capture_events ()
786+
787+ with start_transaction (name = "openai tx" ):
788+ client .chat .completions .create (** create_kwargs )
789+
790+ (transaction ,) = events
791+ (span ,) = transaction ["spans" ]
792+ span_data = span ["data" ]
793+
794+ assert span_data [SPANDATA .GEN_AI_OPERATION_NAME ] == "chat"
795+ assert span_data [SPANDATA .GEN_AI_REQUEST_MODEL ] == "some-model"
796+ assert span_data [SPANDATA .GEN_AI_REQUEST_MAX_TOKENS ] == 100
797+ assert span_data [SPANDATA .GEN_AI_REQUEST_PRESENCE_PENALTY ] == 0.1
798+ assert span_data [SPANDATA .GEN_AI_REQUEST_FREQUENCY_PENALTY ] == 0.2
799+ assert span_data [SPANDATA .GEN_AI_REQUEST_TEMPERATURE ] == 0.7
800+ assert span_data [SPANDATA .GEN_AI_REQUEST_TOP_P ] == 0.9
801+ assert span_data [SPANDATA .GEN_AI_SYSTEM ] == "openai"
802+
803+ for key , value in expected_present .items ():
804+ assert span_data [key ] == value
805+
806+ for key in expected_absent :
807+ assert key not in span_data
808+
809+
642810@pytest .mark .parametrize ("span_streaming" , [True , False ])
643811@pytest .mark .parametrize ("stream_gen_ai_spans" , [True , False ])
644812@pytest .mark .asyncio
0 commit comments