from typing import Type
from pydantic import create_model, Field as PydanticField, BaseModel
from belso.schemas import Schema, Field
from belso.utils.logging import get_logger
from belso.utils.schema_helpers import create_fallback_schema
# Replace standard logger with our custom logger
logger = get_logger(__name__)
[docs]
def to_openai(schema: Type[Schema]) -> Type:
"""
Translate a standard schema to OpenAI GPT format (Pydantic model).\n
---
### Args
- `schema`: the schema to translate.\n
---
### Returns
- `Type`: the translated schema as a Pydantic model.
"""
try:
schema_name = schema.__name__ if hasattr(schema, "__name__") else "unnamed"
logger.debug(f"Starting translation of schema '{schema_name}' to OpenAI format...")
field_definitions = {}
# Build field definitions for Pydantic model
for field in schema.fields:
field_type = field.type
logger.debug(f"Processing field '{field.name}' of type '{field_type.__name__}'...")
if not field.required and field.default is not None:
logger.debug(f"Field '{field.name}' is optional with default value: {field.default}.")
field_definitions[field.name] = (field_type, PydanticField(default=field.default, description=field.description))
else:
required_status = "required" if field.required else "optional without default"
logger.debug(f"Field '{field.name}' is {required_status}.")
field_definitions[field.name] = (field_type, PydanticField(description=field.description))
# Create a Pydantic model dynamically
model_name = schema.__name__ if hasattr(schema, "__name__") else "DynamicModel"
logger.debug(f"Creating Pydantic model '{model_name}' with {len(field_definitions)} fields...")
pydantic_model = create_model(model_name, **field_definitions)
logger.debug(f"Successfully created OpenAI schema as Pydantic model '{pydantic_model.__name__}'.")
return pydantic_model
except Exception as e:
logger.error(f"Error translating schema to OpenAI format: {e}")
logger.debug("Translation error details", exc_info=True)
# Return a simple fallback model if translation fails
logger.warning("Returning fallback model due to translation error.")
return create_model("FallbackModel", text=(str, ...))
[docs]
def from_openai(schema: Type[BaseModel]) -> Type[Schema]:
"""
Convert an OpenAI schema (Pydantic model) to belso Schema format.\n
---
### Args
- `schema`: the schema to convert.\n
---
### Returns
- `Type`: the converted schema as a standard Schema subclass.
"""
try:
schema_name = schema.__name__ if hasattr(schema, "__name__") else "unnamed"
logger.debug(f"Starting conversion from OpenAI schema '{schema_name}' to belso format...")
# Create a new Schema class
class ConvertedSchema(Schema):
name = schema.__name__ if hasattr(schema, "__name__") else "ConvertedFromOpenAI"
fields = []
# Get model fields from Pydantic model
model_fields = schema.model_fields if hasattr(schema, "model_fields") else {}
# For older Pydantic versions
if not model_fields and hasattr(schema, "__fields__"):
logger.debug("Using legacy Pydantic v1 field access.")
model_fields = schema.__fields__
logger.debug(f"Found {len(model_fields)} fields in Pydantic model.")
# Process each field
for name, field_info in model_fields.items():
logger.debug(f"Processing field '{name}'.")
# Extract field type
if hasattr(field_info, "annotation"):
field_type = field_info.annotation
logger.debug(f"Field '{name}' has type annotation: {field_type}")
elif hasattr(field_info, "type_"):
field_type = field_info.type_
logger.debug(f"Field '{name}' has type_: {field_type}")
else:
field_type = str # Default to string
logger.debug(f"Field '{name}' has no type information, defaulting to str.")
# Extract description
description = ""
if hasattr(field_info, "description"):
description = field_info.description
logger.debug(f"Field '{name}' has description: '{description}'.")
# Extract required status
required = True
if hasattr(field_info, "default") and field_info.default is not None:
required = False
logger.debug(f"Field '{name}' is optional with default value.")
# Extract default value
default = None
if hasattr(field_info, "default") and field_info.default is not None:
default = field_info.default
logger.debug(f"Field '{name}' has default value: {default}.")
ConvertedSchema.fields.append(
Field(
name=name,
type=field_type,
description=description,
required=required,
default=default
)
)
logger.debug(f"Successfully converted OpenAI schema to belso schema with {len(ConvertedSchema.fields)} fields.")
return ConvertedSchema
except Exception as e:
logger.error(f"Error converting OpenAI schema to belso format: {e}")
logger.debug("Conversion error details", exc_info=True)
# Return a minimal schema if conversion fails
return create_fallback_schema()