@@ -2242,9 +2242,9 @@ def test_model_profile_strict_not_supported():
22422242 )
22432243
22442244 m = OpenAIChatModel ('gpt-4o' , provider = OpenAIProvider (api_key = 'foobar' ))
2245- tool_param = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
2245+ tool_definition = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
22462246
2247- assert tool_param == snapshot (
2247+ assert tool_definition == snapshot (
22482248 {
22492249 'type' : 'function' ,
22502250 'function' : {
@@ -2262,9 +2262,9 @@ def test_model_profile_strict_not_supported():
22622262 provider = OpenAIProvider (api_key = 'foobar' ),
22632263 profile = OpenAIModelProfile (openai_supports_strict_tool_definition = False ).update (openai_model_profile ('gpt-4o' )),
22642264 )
2265- tool_param = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
2265+ tool_definition = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
22662266
2267- assert tool_param == snapshot (
2267+ assert tool_definition == snapshot (
22682268 {
22692269 'type' : 'function' ,
22702270 'function' : {
@@ -2947,25 +2947,19 @@ def test_deprecated_openai_model(openai_api_key: str):
29472947 OpenAIModel ('gpt-4o' , provider = provider ) # type: ignore[reportDeprecated]
29482948
29492949
2950- def test_model_profile_freeform_function_calling_support ():
2951- """Test that model profiles correctly indicate freeform function calling support."""
2952- # GPT-5 models should support freeform function calling
2953- gpt5_profile = openai_model_profile ('gpt-5' )
2954- assert gpt5_profile .openai_supports_freeform_function_calling is True
2950+ @pytest .mark .parametrize ('model_name' , ['gpt-5' , 'gpt-5-mini' , 'gpt-5-nano' ])
2951+ def test_model_profile_gpt5_freeform_function_calling_support (model_name : str ):
2952+ profile : OpenAIModelProfile = openai_model_profile (model_name )
2953+ assert profile .openai_supports_freeform_function_calling
29552954
2956- gpt5_turbo_profile = openai_model_profile ('gpt-5-turbo' )
2957- assert gpt5_turbo_profile .openai_supports_freeform_function_calling is True
29582955
2959- # Other models should not support freeform function calling
2960- gpt4_profile = openai_model_profile ('gpt-4o' )
2961- assert gpt4_profile .openai_supports_freeform_function_calling is False
2956+ @pytest .mark .parametrize ('model_name' , ['gpt-4.1' , 'gpt-4o' , 'gpt-o4-mini' ])
2957+ def test_model_profile_gpt4_freeform_function_calling_support (model_name : str ):
2958+ gpt4_profile = openai_model_profile (model_name )
2959+ assert not gpt4_profile .openai_supports_freeform_function_calling
29622960
2963- gpt35_profile = openai_model_profile ('gpt-3.5-turbo' )
2964- assert gpt35_profile .openai_supports_freeform_function_calling is False
29652961
2966-
2967- def test_tool_mapping_with_freeform_text_mode ():
2968- """Test that OpenAI Chat model ignores text_format and maps as regular function."""
2962+ def test_chat_model_ignores_text_mode_text_when_tool_mapping ():
29692963 my_tool = ToolDefinition (
29702964 name = 'analyze_text' ,
29712965 description = 'Analyze the provided text' ,
@@ -2978,11 +2972,10 @@ def test_tool_mapping_with_freeform_text_mode():
29782972 text_format = 'text' ,
29792973 )
29802974
2981- # OpenAI Chat model ignores text_format and maps as regular function tool
2982- m = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
2983- tool_param = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
2975+ model = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
2976+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
29842977
2985- assert tool_param == snapshot (
2978+ assert tool_definition == snapshot (
29862979 {
29872980 'type' : 'function' ,
29882981 'function' : {
@@ -2999,8 +2992,7 @@ def test_tool_mapping_with_freeform_text_mode():
29992992 )
30002993
30012994
3002- def test_tool_mapping_with_freeform_lark_grammar ():
3003- """Test that OpenAI Chat model ignores lark CFG and maps as regular function."""
2995+ def test_chat_model_ignores_text_mode_lark_when_tool_mapping ():
30042996 my_tool = ToolDefinition (
30052997 name = 'parse_data' ,
30062998 description = 'Parse structured data' ,
@@ -3013,11 +3005,10 @@ def test_tool_mapping_with_freeform_lark_grammar():
30133005 text_format = FunctionTextFormat (syntax = 'lark' , grammar = 'start: "hello" " " "world"' ),
30143006 )
30153007
3016- # OpenAI Chat model ignores text_format and maps as regular function tool
3017- m = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3018- tool_param = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
3008+ model = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3009+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
30193010
3020- assert tool_param == snapshot (
3011+ assert tool_definition == snapshot (
30213012 {
30223013 'type' : 'function' ,
30233014 'function' : {
@@ -3034,8 +3025,7 @@ def test_tool_mapping_with_freeform_lark_grammar():
30343025 )
30353026
30363027
3037- def test_tool_mapping_with_freeform_regex ():
3038- """Test that OpenAI Chat model ignores regex CFG and maps as regular function."""
3028+ def test_chat_model_ignores_text_mode_regex_when_tool_mapping ():
30393029 my_tool = ToolDefinition (
30403030 name = 'extract_pattern' ,
30413031 description = 'Extract data matching pattern' ,
@@ -3048,11 +3038,10 @@ def test_tool_mapping_with_freeform_regex():
30483038 text_format = FunctionTextFormat (syntax = 'regex' , grammar = r'\d{4}-\d{2}-\d{2}' ),
30493039 )
30503040
3051- # OpenAI Chat model ignores text_format and maps as regular function tool
3052- m = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3053- tool_param = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
3041+ model = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3042+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
30543043
3055- assert tool_param == snapshot (
3044+ assert tool_definition == snapshot (
30563045 {
30573046 'type' : 'function' ,
30583047 'function' : {
@@ -3069,8 +3058,85 @@ def test_tool_mapping_with_freeform_regex():
30693058 )
30703059
30713060
3072- def test_tool_mapping_regular_function_unchanged ():
3073- """Test that regular tools without text_format are still mapped to function params."""
3061+ def test_responses_model_uses_text_mode_text_when_tool_mapping ():
3062+ my_tool = ToolDefinition (
3063+ name = 'analyze_text' ,
3064+ description = 'Analyze the provided text' ,
3065+ parameters_json_schema = {
3066+ 'type' : 'object' ,
3067+ 'properties' : {'content' : {'type' : 'string' }},
3068+ 'required' : ['content' ],
3069+ 'additionalProperties' : False ,
3070+ },
3071+ text_format = 'text' ,
3072+ )
3073+
3074+ model = OpenAIResponsesModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3075+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
3076+
3077+ assert tool_definition == snapshot (
3078+ {
3079+ 'name' : 'analyze_text' ,
3080+ 'type' : 'custom' ,
3081+ 'description' : 'Analyze the provided text' ,
3082+ 'format' : {'type' : 'text' },
3083+ }
3084+ )
3085+
3086+
3087+ def test_responses_model_uses_text_mode_lark_when_tool_mapping ():
3088+ my_tool = ToolDefinition (
3089+ name = 'parse_data' ,
3090+ description = 'Parse structured data' ,
3091+ parameters_json_schema = {
3092+ 'type' : 'object' ,
3093+ 'properties' : {'data' : {'type' : 'string' }},
3094+ 'required' : ['data' ],
3095+ 'additionalProperties' : False ,
3096+ },
3097+ text_format = FunctionTextFormat (syntax = 'lark' , grammar = 'start: "hello" " " "world"' ),
3098+ )
3099+
3100+ model = OpenAIResponsesModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3101+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
3102+
3103+ assert tool_definition == snapshot (
3104+ {
3105+ 'name' : 'parse_data' ,
3106+ 'type' : 'custom' ,
3107+ 'description' : 'Parse structured data' ,
3108+ 'format' : {'type' : 'grammar' , 'syntax' : 'lark' , 'definition' : 'start: "hello" " " "world"' },
3109+ }
3110+ )
3111+
3112+
3113+ def test_responses_model_uses_text_mode_regex_when_tool_mapping ():
3114+ my_tool = ToolDefinition (
3115+ name = 'extract_pattern' ,
3116+ description = 'Extract data matching pattern' ,
3117+ parameters_json_schema = {
3118+ 'type' : 'object' ,
3119+ 'properties' : {'text' : {'type' : 'string' }},
3120+ 'required' : ['text' ],
3121+ 'additionalProperties' : False ,
3122+ },
3123+ text_format = FunctionTextFormat (syntax = 'regex' , grammar = r'\d{4}-\d{2}-\d{2}' ),
3124+ )
3125+
3126+ model = OpenAIResponsesModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3127+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
3128+
3129+ assert tool_definition == snapshot (
3130+ {
3131+ 'name' : 'extract_pattern' ,
3132+ 'type' : 'custom' ,
3133+ 'description' : 'Extract data matching pattern' ,
3134+ 'format' : {'type' : 'grammar' , 'syntax' : 'regex' , 'definition' : '\\ d{4}-\\ d{2}-\\ d{2}' },
3135+ }
3136+ )
3137+
3138+
3139+ def test_chat_model_tool_mapping_regular_function_unchanged ():
30743140 my_tool = ToolDefinition (
30753141 name = 'regular_tool' ,
30763142 description = 'A regular tool' ,
@@ -3081,10 +3147,10 @@ def test_tool_mapping_regular_function_unchanged():
30813147 },
30823148 )
30833149
3084- m = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3085- tool_param = m ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
3150+ model = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3151+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
30863152
3087- assert tool_param == snapshot (
3153+ assert tool_definition == snapshot (
30883154 {
30893155 'type' : 'function' ,
30903156 'function' : {
@@ -3096,12 +3162,8 @@ def test_tool_mapping_regular_function_unchanged():
30963162 )
30973163
30983164
3099- def test_parallel_tool_calling_with_regular_tools ():
3100- """Test that OpenAI Chat model handles parallel tool calling normally with regular tools."""
3101- from pydantic_ai .models import ModelRequestParameters
3102-
3103- # Regular tool
3104- regular_tool = ToolDefinition (
3165+ def test_responses_model_tool_mapping_regular_function_unchanged ():
3166+ my_tool = ToolDefinition (
31053167 name = 'regular_tool' ,
31063168 description = 'A regular tool' ,
31073169 parameters_json_schema = {
@@ -3111,21 +3173,21 @@ def test_parallel_tool_calling_with_regular_tools():
31113173 },
31123174 )
31133175
3114- m = OpenAIChatModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3115-
3116- # With only regular tools - should use model settings default
3117- params_regular_only = ModelRequestParameters (function_tools = [regular_tool ])
3118- # OpenAI Chat model doesn't have _get_parallel_tool_calling method like Responses model
3119- # This test verifies the model can handle regular tools without issues
3120-
3176+ model = OpenAIResponsesModel ('gpt-5' , provider = OpenAIProvider (api_key = 'foobar' ))
3177+ tool_definition = model ._map_tool_definition (my_tool ) # type: ignore[reportPrivateUsage]
31213178
3122- # OpenAI Chat model doesn't validate text_format, so no error tests needed
3123- # Error validation tests are in the OpenAI Responses model section
3179+ assert tool_definition == snapshot (
3180+ {
3181+ 'name' : 'regular_tool' ,
3182+ 'parameters' : {'type' : 'object' , 'properties' : {'param' : {'type' : 'string' }}, 'required' : ['param' ]},
3183+ 'type' : 'function' ,
3184+ 'description' : 'A regular tool' ,
3185+ 'strict' : False ,
3186+ }
3187+ )
31243188
31253189
3126- def test_tool_definition_single_string_argument_detection ():
3127- """Test ToolDefinition correctly detects single string argument tools."""
3128- # Valid single string argument
3190+ def test_tool_definition_single_string_argument ():
31293191 valid_tool = ToolDefinition (
31303192 name = 'valid_tool' ,
31313193 description = 'Valid single string tool' ,
@@ -3136,26 +3198,28 @@ def test_tool_definition_single_string_argument_detection():
31363198 'additionalProperties' : False ,
31373199 },
31383200 )
3139- assert valid_tool .only_takes_string_argument is True
3201+ assert valid_tool .only_takes_string_argument
31403202 assert valid_tool .single_string_argument_name == 'content'
31413203
3142- # Multiple parameters - invalid
3204+
3205+ def test_tool_definition_multiple_argument_single_string_argument ():
31433206 multi_param_tool = ToolDefinition (
31443207 name = 'multi_tool' ,
31453208 description = 'Multi param tool' ,
31463209 parameters_json_schema = {
31473210 'type' : 'object' ,
3211+ 'param1' : {'type' : 'string' },
31483212 'properties' : {
3149- 'param1' : {'type' : 'string' },
31503213 'param2' : {'type' : 'string' },
31513214 },
31523215 'required' : ['param1' , 'param2' ],
31533216 },
31543217 )
3155- assert multi_param_tool .only_takes_string_argument is False
3218+ assert not multi_param_tool .only_takes_string_argument
31563219 assert multi_param_tool .single_string_argument_name is None
31573220
3158- # Non-string parameter - invalid
3221+
3222+ def test_tool_definition_single_non_string_argument_single_string_argument ():
31593223 non_string_tool = ToolDefinition (
31603224 name = 'non_string_tool' ,
31613225 description = 'Non-string param tool' ,
@@ -3165,9 +3229,5 @@ def test_tool_definition_single_string_argument_detection():
31653229 'required' : ['count' ],
31663230 },
31673231 )
3168- assert non_string_tool .only_takes_string_argument is False
3232+ assert not non_string_tool .only_takes_string_argument
31693233 assert non_string_tool .single_string_argument_name is None
3170-
3171-
3172- # OpenAI Chat model doesn't support custom tool calls, so no custom tool call tests needed here
3173- # Those tests are in the OpenAI Responses model section
0 commit comments