Source code for haive.core.common.mixins.structured_output_mixin
"""Mixin for handling structured output in LLM configurations.This mixin provides functionality to configure and manage structured output modelswith support for both v1 (parser-based) and v2 (tool-based) approaches."""fromtypingimportAny,Literal,castfromlangchain_core.output_parsersimportBaseOutputParser,PydanticOutputParserfrompydanticimportBaseModelStructuredOutputVersion=Literal["v1","v2"]
[docs]classStructuredOutputMixin:"""Mixin to provide structured output functionality for LLM configurations. This mixin adds support for: - Configuring structured output models with v1 (parser) or v2 (tool) approaches - Automatic format instruction generation - Tool-based structured output forcing """# These fields must be defined by subclasses# We use type annotations to indicate expected typesstructured_output_model:type[BaseModel]|Nonestructured_output_version:StructuredOutputVersion|Noneinclude_format_instructions:booloutput_parser:BaseOutputParser|Noneparser_type:str|Nonepartial_variables:dict[str,Any]tools:list[Any]pydantic_tools:list[type[BaseModel]]force_tool_use:boolforce_tool_choice:bool|str|list[str]|Nonetool_choice_mode:strbind_tools_kwargs:dict[str,Any]_tool_name_mapping:dict[str,str]_format_instructions_text:str|None_is_processing_validation:bool
[docs]defwith_structured_output(self,model:type[BaseModel],include_instructions:bool=True,version:str="v2",)->"StructuredOutputMixin":"""Configure with Pydantic structured output. Args: model: The Pydantic model to use for structured output include_instructions: Whether to include format instructions version: Version of structured output ("v1" for parser-based, "v2" for tool-based) Returns: Self for method chaining """# Set the structured output modelself.structured_output_model=modelself.include_format_instructions=include_instructions# Validate versionifversionnotin["v1","v2"]:version="v2"self.structured_output_version=cast(StructuredOutputVersion,version)# Trigger validation if the subclass has itif(hasattr(self,"comprehensive_validation_and_setup")andnotself._is_processing_validation):self.comprehensive_validation_and_setup()returnself
def_setup_v2_structured_output(self):"""Setup v2 (tool-based) approach - force tool usage with format instructions, NO parsing."""ifnotself.structured_output_model:return# Ensure the model is in tools listifself.structured_output_modelnotinself.tools:self.tools=list(self.tools)ifself.toolselse[]self.tools.append(self.structured_output_model)# Add to pydantic_tools for trackingifself.structured_output_modelnotinself.pydantic_tools:self.pydantic_tools.append(self.structured_output_model)# Explicitly set parser_type to None for v2self.parser_type=None# Explicitly set output_parser to None for v2self.output_parser=None# Configure tool usage - FORCE this specific toolself.force_tool_use=Trueself.tool_choice_mode="required"# The actual tool name used in binding is the class name (exact case)model_class_name=self.structured_output_model.__name__actual_tool_name=model_class_name# Update tool name mappingself._tool_name_mapping[model_class_name]=actual_tool_name# Set force_tool_choice to the actual tool nameself.force_tool_choice=actual_tool_name# Add format instructions for the model using PydanticOutputParserifself.include_format_instructions:try:parser=PydanticOutputParser(pydantic_object=self.structured_output_model)instructions=parser.get_format_instructions()self.partial_variables["format_instructions"]=instructionsself._format_instructions_text=instructionsexceptException:pass# Silently handle errors# Update bind_tools_kwargs to use the correct tool choice formatself._update_bind_tools_kwargs_for_v2()def_setup_v1_structured_output(self):"""Setup v1 (traditional) structured output."""ifnotself.structured_output_model:returnself.parser_type="pydantic"self.output_parser=PydanticOutputParser(pydantic_object=self.structured_output_model)def_update_bind_tools_kwargs_for_v2(self):"""Update bind_tools_kwargs specifically for v2 structured output."""ifself.structured_output_version=="v2"andself.force_tool_choice:self.bind_tools_kwargs["tool_choice"]={"type":"function","function":{"name":self.force_tool_choice},}elifself.tool_choice_mode=="required":self.bind_tools_kwargs["tool_choice"]="required"def_create_structured_output_tool(self,name:str,description:str,**kwargs)->Any:"""Create a tool from the structured output model."""ifnotself.structured_output_model:raiseValueError("No structured output model configured")# For structured output models, return the model class itself# but add tool metadatatool_class=self.structured_output_model# Add metadata if subclass has set_tool_route methodifhasattr(self,"set_tool_route"):# Use sanitized tool name to match what LangChain bind_tools producesfromhaive.core.utils.namingimportsanitize_tool_namesanitized_name=sanitize_tool_name(name)metadata={"llm_config":getattr(self,"name","anonymous"),"version":self.structured_output_version,"tool_type":"structured_output",}self.set_tool_route(sanitized_name,"parse_output",metadata)returntool_classdef_mark_structured_output_tools(self):"""Mark tools that are structured output models with metadata."""ifnotself.structured_output_model:returnstructured_model_name=self.structured_output_model.__name__fortoolinself.tools:ifhasattr(tool,"__name__")andtool.__name__==structured_model_name:# Add metadata if this is the subclass's set_tool_route methodifhasattr(self,"set_tool_route"):# Use sanitized tool name to match what LangChain bind_tools producesfromhaive.core.utils.namingimportsanitize_tool_namesanitized_tool_name=sanitize_tool_name(tool.__name__)metadata={"is_structured_output":True,"version":self.structured_output_version,}self.set_tool_route(sanitized_tool_name,"parse_output",metadata)