From 843bf4858788195b284cd742d7c0c7a5aad2e945 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Mar 2026 22:51:19 -0700 Subject: [PATCH] feat: Support union types in schema generation and add deps for interface builder Changes for the custom interface builder in LSE (humansignal/label-studio-enterprise#10511): - Handle JSON Schema union types (["string", "null"] -> Optional[str]) - Default None for optional Pydantic fields so they aren't required - Fix response_model type annotation in PromptImprovementSkill - Add dependencies: lxml, xmljson, jsf, datamodel-code-generator Co-Authored-By: Claude Opus 4.6 --- adala/skills/collection/prompt_improvement.py | 4 ++-- adala/utils/pydantic_generator.py | 15 +++++++++++++-- pyproject.toml | 7 ++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/adala/skills/collection/prompt_improvement.py b/adala/skills/collection/prompt_improvement.py index b5a006ba..c164bebe 100644 --- a/adala/skills/collection/prompt_improvement.py +++ b/adala/skills/collection/prompt_improvement.py @@ -9,7 +9,7 @@ AfterValidator, ) from adala.skills import Skill -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Type, Union from typing_extensions import Annotated from adala.skills import AnalysisSkill from adala.utils.parse import parse_template @@ -67,7 +67,7 @@ class PromptImprovementSkill(AnalysisSkill): input_prefix: str = "" # Used to provide additional context for the input input_separator: str = "\n" - response_model = PromptImprovementSkillResponseModel + response_model: Type[BaseModel] = PromptImprovementSkillResponseModel @model_validator(mode="after") def validate_prompts(self): diff --git a/adala/utils/pydantic_generator.py b/adala/utils/pydantic_generator.py index 52c43e67..ad19b264 100644 --- a/adala/utils/pydantic_generator.py +++ b/adala/utils/pydantic_generator.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Type, Union, Tuple, Literal, Set +from typing import Any, Dict, List, Optional, Type, Union, Tuple, Literal, Set, get_origin, get_args from enum import Enum from datetime import datetime from pydantic import BaseModel, Field, create_model @@ -95,8 +95,12 @@ def json_schema_to_pydantic_field(json_schema: Dict[str, Any]) -> Tuple[Any, Fie if constraint in json_schema: field_params[constraint] = json_schema[constraint] + # Use None default for Optional types so they aren't required + is_optional = get_origin(type_) is Union and type(None) in get_args(type_) + default = None if is_optional else ... + # Create a Field object with the type and optional parameters. - return type_, Field(..., **field_params) + return type_, Field(default, **field_params) def json_schema_to_pydantic_type( @@ -115,6 +119,13 @@ def json_schema_to_pydantic_type( type_ = json_schema.get("type") + # Handle union types: ["string", "null"] → Optional[str] + if isinstance(type_, list): + non_null = [t for t in type_ if t != 'null'] + has_null = len(non_null) < len(type_) + inner = json_schema_to_pydantic_type({**json_schema, 'type': non_null[0]}) + return Optional[inner] if has_null else inner + if type_ == "string": if "format" in json_schema: format_ = json_schema["format"] diff --git a/pyproject.toml b/pyproject.toml index 744ff9d3..5ed7b114 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,12 @@ dependencies = [ "pandarallel (>=1.6.5,<2.0.0)", "instructor (==1.4.3)", "async-lru (>=2.0.5,<3.0.0)", - "jinja2 (>=3.1.6,<4.0)" + "jinja2 (>=3.1.6,<4.0)", + "lxml>=6.0.2", + "appdirs>=1.4.4", + "xmljson>=0.2.1", + "jsf>=0.11.2", + "datamodel-code-generator>=0.54.1" ] [project.urls]