feat: update version to 0.6.1-beta and ensure array schemas have items in JSON responses

This commit is contained in:
sudipnext 2026-03-02 17:11:25 +05:45
parent 790ce3458b
commit 34eed2413f
6 changed files with 101 additions and 35 deletions

View file

@ -1,12 +1,12 @@
{
"name": "presenton",
"version": "0.6.0-beta",
"version": "0.6.1-beta",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "presenton",
"version": "0.6.0-beta",
"version": "0.6.1-beta",
"hasInstallScript": true,
"dependencies": {
"@tailwindcss/cli": "^4.1.5",

View file

@ -1,7 +1,7 @@
{
"name": "presenton",
"productName": "Presenton Open Source",
"version": "0.6.0-beta",
"version": "0.6.1-beta",
"main": "app_dist/main.js",
"description": "Open-Source AI Presentation Generator",
"homepage": "https://presenton.ai",

View file

@ -66,6 +66,7 @@ from utils.set_env import (
from utils.llm_provider import get_llm_provider, get_model
from utils.parsers import parse_bool_or_none
from utils.schema_utils import (
ensure_array_schemas_have_items,
ensure_strict_json_schema,
flatten_json_schema,
remove_titles_from_schema,
@ -702,6 +703,7 @@ class LLMClient:
path=(),
root=response_schema,
)
response_schema = ensure_array_schemas_have_items(response_schema)
if use_tool_calls_for_structured_output and depth == 0:
if all_tools is None:
all_tools = []
@ -1599,6 +1601,7 @@ class LLMClient:
path=(),
root=response_schema,
)
response_schema = ensure_array_schemas_have_items(response_schema)
if use_tool_calls_for_structured_output and depth == 0:
if all_tools is None:
@ -1793,28 +1796,16 @@ class LLMClient:
"""
client: AsyncOpenAI = self._client
response_schema = response_format
# Apply strict schema once at root
# Apply strict schema once at root (includes array "items" fix at lines 135155).
if strict and depth == 0:
response_schema = ensure_strict_json_schema(
response_schema,
path=(),
root=response_schema,
)
# Codex Responses API requires all array schemas to specify `items`.
def _fix_arrays(node: Any) -> Any:
if isinstance(node, dict):
# Add default items for arrays missing them
if node.get("type") == "array" and "items" not in node:
node["items"] = {"type": "string"}
for key, value in list(node.items()):
node[key] = _fix_arrays(value)
elif isinstance(node, list):
for idx, value in enumerate(node):
node[idx] = _fix_arrays(value)
return node
response_schema = _fix_arrays(response_schema)
# When we didn't run ensure_strict_json_schema, fix arrays for Codex API (strict=False or depth > 0).
else:
response_schema = ensure_array_schemas_have_items(response_schema)
# Responses API tool format: flat {type, name, description, parameters}
response_schema_tool = {

View file

@ -134,11 +134,25 @@ def ensure_strict_json_schema(
# arrays
# { 'type': 'array', 'items': {...} }
# OpenAI requires array schemas to have "items". Zod tuples may emit prefixItems only.
items = json_schema.get("items")
if isinstance(items, dict):
json_schema["items"] = ensure_strict_json_schema(
items, path=(*path, "items"), root=root
)
elif typ == "array":
prefix_items = json_schema.get("prefixItems")
if (
isinstance(prefix_items, list)
and len(prefix_items) > 0
and isinstance(prefix_items[0], dict)
):
json_schema["items"] = ensure_strict_json_schema(
prefix_items[0], path=(*path, "items"), root=root
)
json_schema.pop("prefixItems", None)
else:
json_schema["items"] = {"type": "string"}
# unions
any_of = json_schema.get("anyOf")
@ -281,6 +295,34 @@ def flatten_json_schema(schema: dict) -> dict:
return result
def ensure_array_schemas_have_items(schema: dict) -> dict[str, Any]:
"""
Recursively ensure every JSON schema node with type="array" has an "items" key.
Codex Responses API requires array schemas to specify items. Mutates a deep copy.
"""
result = deepcopy(schema)
def _is_array_schema_type(type_value: Any) -> bool:
if type_value == "array":
return True
if isinstance(type_value, list):
return "array" in type_value
return False
def _ensure(node: Any) -> Any:
if isinstance(node, dict):
if _is_array_schema_type(node.get("type")) and "items" not in node:
node["items"] = {"type": "string"}
for key, value in list(node.items()):
node[key] = _ensure(value)
elif isinstance(node, list):
for idx, value in enumerate(node):
node[idx] = _ensure(value)
return node
return _ensure(result)
def remove_titles_from_schema(schema: dict) -> dict[str, Any]:
def _strip_titles(node: Any) -> Any:

View file

@ -66,6 +66,7 @@ from utils.set_env import (
from utils.llm_provider import get_llm_provider, get_model
from utils.parsers import parse_bool_or_none
from utils.schema_utils import (
ensure_array_schemas_have_items,
ensure_strict_json_schema,
flatten_json_schema,
remove_titles_from_schema,
@ -702,6 +703,7 @@ class LLMClient:
path=(),
root=response_schema,
)
response_schema = ensure_array_schemas_have_items(response_schema)
if use_tool_calls_for_structured_output and depth == 0:
if all_tools is None:
all_tools = []
@ -1599,6 +1601,7 @@ class LLMClient:
path=(),
root=response_schema,
)
response_schema = ensure_array_schemas_have_items(response_schema)
if use_tool_calls_for_structured_output and depth == 0:
if all_tools is None:
@ -1793,28 +1796,16 @@ class LLMClient:
"""
client: AsyncOpenAI = self._client
response_schema = response_format
# Apply strict schema once at root
# Apply strict schema once at root (includes array "items" fix in ensure_strict_json_schema).
if strict and depth == 0:
response_schema = ensure_strict_json_schema(
response_schema,
path=(),
root=response_schema,
)
# Codex Responses API requires all array schemas to specify `items`.
def _fix_arrays(node: Any) -> Any:
if isinstance(node, dict):
# Add default items for arrays missing them
if node.get("type") == "array" and "items" not in node:
node["items"] = {"type": "string"}
for key, value in list(node.items()):
node[key] = _fix_arrays(value)
elif isinstance(node, list):
for idx, value in enumerate(node):
node[idx] = _fix_arrays(value)
return node
response_schema = _fix_arrays(response_schema)
# When we didn't run ensure_strict_json_schema, fix arrays for Codex API (strict=False or depth > 0).
else:
response_schema = ensure_array_schemas_have_items(response_schema)
# Responses API tool format: flat {type, name, description, parameters}
response_schema_tool = {

View file

@ -185,6 +185,20 @@ def ensure_strict_json_schema(
items, path=(*path, "items"), root=root
)
elif typ == "array":
prefix_items = json_schema.get("prefixItems")
if (
isinstance(prefix_items, list)
and len(prefix_items) > 0
and isinstance(prefix_items[0], dict)
):
json_schema["items"] = ensure_strict_json_schema(
prefix_items[0], path=(*path, "items"), root=root
)
json_schema.pop("prefixItems", None)
else:
json_schema["items"] = {"type": "string"}
# unions
any_of = json_schema.get("anyOf")
if isinstance(any_of, list):
@ -326,6 +340,34 @@ def flatten_json_schema(schema: dict) -> dict:
return result
def ensure_array_schemas_have_items(schema: dict) -> dict[str, Any]:
"""
Recursively ensure every JSON schema node with type="array" has an "items" key.
Codex Responses API requires array schemas to specify items. Mutates a deep copy.
"""
result = deepcopy(schema)
def _is_array_schema_type(type_value: Any) -> bool:
if type_value == "array":
return True
if isinstance(type_value, list):
return "array" in type_value
return False
def _ensure(node: Any) -> Any:
if isinstance(node, dict):
if _is_array_schema_type(node.get("type")) and "items" not in node:
node["items"] = {"type": "string"}
for key, value in list(node.items()):
node[key] = _ensure(value)
elif isinstance(node, list):
for idx, value in enumerate(node):
node[idx] = _ensure(value)
return node
return _ensure(result)
def remove_titles_from_schema(schema: dict) -> dict[str, Any]:
def _strip_titles(node: Any) -> Any: