99from pydantic .fields import FieldInfo
1010
1111from dspy .adapters .chat_adapter import ChatAdapter , FieldInfoWithName
12+ from dspy .adapters .types .tool import ToolCalls
1213from dspy .adapters .utils import (
1314 format_field_value ,
1415 get_annotation_name ,
1819)
1920from dspy .clients .lm import LM
2021from dspy .signatures .signature import Signature , SignatureMeta
22+ from dspy .utils .callback import BaseCallback
2123from dspy .utils .exceptions import AdapterParseError
2224
2325logger = logging .getLogger (__name__ )
@@ -37,6 +39,10 @@ def _has_open_ended_mapping(signature: SignatureMeta) -> bool:
3739
3840
3941class JSONAdapter (ChatAdapter ):
42+ def __init__ (self , callbacks : list [BaseCallback ] | None = None , use_native_function_calling : bool = True ):
43+ # JSONAdapter uses native function calling by default.
44+ super ().__init__ (callbacks = callbacks , use_native_function_calling = use_native_function_calling )
45+
4046 def _json_adapter_call_common (self , lm , lm_kwargs , signature , demos , inputs , call_fn ):
4147 """Common call logic to be used for both sync and async calls."""
4248 provider = lm .model .split ("/" , 1 )[0 ] or "openai"
@@ -45,7 +51,10 @@ def _json_adapter_call_common(self, lm, lm_kwargs, signature, demos, inputs, cal
4551 if not params or "response_format" not in params :
4652 return call_fn (lm , lm_kwargs , signature , demos , inputs )
4753
48- if _has_open_ended_mapping (signature ):
54+ has_tool_calls = any (field .annotation == ToolCalls for field in signature .output_fields .values ())
55+ if _has_open_ended_mapping (signature ) or (not self .use_native_function_calling and has_tool_calls ):
56+ # We found that structured output mode doesn't work well with dspy.ToolCalls as output field.
57+ # So we fall back to json mode if native function calling is disabled and ToolCalls is present.
4958 lm_kwargs ["response_format" ] = {"type" : "json_object" }
5059 return call_fn (lm , lm_kwargs , signature , demos , inputs )
5160
@@ -62,7 +71,9 @@ def __call__(
6271 return result
6372
6473 try :
65- structured_output_model = _get_structured_outputs_response_format (signature )
74+ structured_output_model = _get_structured_outputs_response_format (
75+ signature , self .use_native_function_calling
76+ )
6677 lm_kwargs ["response_format" ] = structured_output_model
6778 return super ().__call__ (lm , lm_kwargs , signature , demos , inputs )
6879 except Exception :
@@ -91,16 +102,6 @@ async def acall(
91102 lm_kwargs ["response_format" ] = {"type" : "json_object" }
92103 return await super ().acall (lm , lm_kwargs , signature , demos , inputs )
93104
94- def _call_preprocess (
95- self ,
96- lm : "LM" ,
97- lm_kwargs : dict [str , Any ],
98- signature : Type [Signature ],
99- inputs : dict [str , Any ],
100- use_native_function_calling : bool = True ,
101- ) -> dict [str , Any ]:
102- return super ()._call_preprocess (lm , lm_kwargs , signature , inputs , use_native_function_calling )
103-
104105 def format_field_structure (self , signature : Type [Signature ]) -> str :
105106 parts = []
106107 parts .append ("All interactions will be structured in the following way, with the appropriate values filled in." )
@@ -206,7 +207,10 @@ def format_finetune_data(
206207 raise NotImplementedError
207208
208209
209- def _get_structured_outputs_response_format (signature : SignatureMeta ) -> type [pydantic .BaseModel ]:
210+ def _get_structured_outputs_response_format (
211+ signature : SignatureMeta ,
212+ use_native_function_calling : bool = True ,
213+ ) -> type [pydantic .BaseModel ]:
210214 """
211215 Builds a Pydantic model from a DSPy signature's output_fields and ensures the generated JSON schema
212216 is compatible with OpenAI Structured Outputs (all objects have a "required" key listing every property,
@@ -227,6 +231,9 @@ def _get_structured_outputs_response_format(signature: SignatureMeta) -> type[py
227231 fields = {}
228232 for name , field in signature .output_fields .items ():
229233 annotation = field .annotation
234+ if use_native_function_calling and annotation == ToolCalls :
235+ # Skip ToolCalls field if native function calling is enabled.
236+ continue
230237 default = field .default if hasattr (field , "default" ) else ...
231238 fields [name ] = (annotation , default )
232239
0 commit comments