1414import json
1515import os
1616import time
17- import warnings
1817from typing import Any , Dict , List , Optional , Type , Union , cast
1918
2019from openai import AsyncStream , Stream
@@ -636,6 +635,37 @@ def _convert_anthropic_stream_to_openai_chunk(
636635 usage = usage ,
637636 )
638637
638+ @staticmethod
639+ def _add_additional_properties_false (schema : Dict [str , Any ]) -> None :
640+ r"""Recursively add additionalProperties: false to all object types."""
641+ if schema .get ("type" ) == "object" :
642+ schema ["additionalProperties" ] = False
643+ for value in schema .values ():
644+ if isinstance (value , dict ):
645+ AnthropicModel ._add_additional_properties_false (value )
646+ elif isinstance (value , list ):
647+ for item in value :
648+ if isinstance (item , dict ):
649+ AnthropicModel ._add_additional_properties_false (item )
650+
651+ @staticmethod
652+ def _build_output_config (
653+ response_format : Type [BaseModel ],
654+ ) -> Dict [str , Any ]:
655+ r"""Convert a Pydantic model to Anthropic's output_config format."""
656+ schema = response_format .model_json_schema ()
657+ # Remove unsupported fields
658+ schema .pop ("$defs" , None )
659+ schema .pop ("definitions" , None )
660+ # Anthropic requires additionalProperties: false on all object types
661+ AnthropicModel ._add_additional_properties_false (schema )
662+ return {
663+ "format" : {
664+ "type" : "json_schema" ,
665+ "schema" : schema ,
666+ }
667+ }
668+
639669 def _convert_openai_tools_to_anthropic (
640670 self , tools : Optional [List [Dict [str , Any ]]]
641671 ) -> Optional [List [Dict [str , Any ]]]:
@@ -678,7 +708,7 @@ def _run(
678708 messages (List[OpenAIMessage]): Message list with the chat history
679709 in OpenAI API format.
680710 response_format (Optional[Type[BaseModel]]): The format of the
681- response. (Not supported by Anthropic API directly)
711+ response.
682712 tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
683713 use for the request.
684714
@@ -687,15 +717,6 @@ def _run(
687717 `ChatCompletion` in the non-stream mode, or
688718 `Stream[ChatCompletionChunk]` in the stream mode.
689719 """
690- if response_format is not None :
691- warnings .warn (
692- "The 'response_format' parameter is not supported by the "
693- "Anthropic API and will be ignored. Consider using tools "
694- "for structured output instead." ,
695- UserWarning ,
696- stacklevel = 2 ,
697- )
698-
699720 # Update Langfuse trace with current agent session and metadata
700721 agent_session_id = get_current_agent_session_id ()
701722 if agent_session_id :
@@ -764,6 +785,12 @@ def _run(
764785 if key in self .model_config_dict :
765786 request_params [key ] = self .model_config_dict [key ]
766787
788+ # Add structured output via output_config
789+ if response_format is not None :
790+ request_params ["output_config" ] = self ._build_output_config (
791+ response_format
792+ )
793+
767794 # Convert tools
768795 anthropic_tools = self ._convert_openai_tools_to_anthropic (tools )
769796 if anthropic_tools :
@@ -803,7 +830,7 @@ async def _arun(
803830 messages (List[OpenAIMessage]): Message list with the chat history
804831 in OpenAI API format.
805832 response_format (Optional[Type[BaseModel]]): The format of the
806- response. (Not supported by Anthropic API directly)
833+ response.
807834 tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
808835 use for the request.
809836
@@ -812,15 +839,6 @@ async def _arun(
812839 `ChatCompletion` in the non-stream mode, or
813840 `AsyncStream[ChatCompletionChunk]` in the stream mode.
814841 """
815- if response_format is not None :
816- warnings .warn (
817- "The 'response_format' parameter is not supported by the "
818- "Anthropic API and will be ignored. Consider using tools "
819- "for structured output instead." ,
820- UserWarning ,
821- stacklevel = 2 ,
822- )
823-
824842 # Update Langfuse trace with current agent session and metadata
825843 agent_session_id = get_current_agent_session_id ()
826844 if agent_session_id :
@@ -889,6 +907,12 @@ async def _arun(
889907 if key in self .model_config_dict :
890908 request_params [key ] = self .model_config_dict [key ]
891909
910+ # Add structured output via output_config
911+ if response_format is not None :
912+ request_params ["output_config" ] = self ._build_output_config (
913+ response_format
914+ )
915+
892916 # Convert tools
893917 anthropic_tools = self ._convert_openai_tools_to_anthropic (tools )
894918 if anthropic_tools :
0 commit comments