fix: refactor enable strict mode for structured output (#8840)

* base

* test
This commit is contained in:
jnjpng
2026-01-16 12:52:42 -08:00
committed by Sarah Wooders
parent b62ce02930
commit a98bc31bf3
4 changed files with 383 additions and 51 deletions

View File

@@ -12,8 +12,9 @@ from pydantic import BaseModel
from letta.functions.functions import derive_openai_json_schema
from letta.functions.schema_generator import validate_google_style_docstring
from letta.helpers.tool_execution_helper import enable_strict_mode
from letta.llm_api.helpers import convert_to_structured_output
from letta.schemas.tool import Tool, ToolCreate
from letta.schemas.tool import MCP_TOOL_METADATA_SCHEMA_STATUS, Tool, ToolCreate
def _clean_diff(d1, d2):
@@ -674,3 +675,253 @@ def test_complex_nested_anyof_schema_to_structured_output():
except Exception as e:
pytest.fail(f"Failed to convert complex nested anyOf schema to structured output: {str(e)}")
# ========== enable_strict_mode tests ==========
def test_enable_strict_mode_adds_all_properties_to_required():
"""Test that enable_strict_mode adds all properties to required array."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"required_field": {"type": "string"},
"optional_field": {"type": "integer"},
},
"required": ["required_field"],
},
}
result = enable_strict_mode(schema, strict=True)
assert result["strict"] is True
assert set(result["parameters"]["required"]) == {"required_field", "optional_field"}
def test_enable_strict_mode_makes_optional_fields_nullable():
"""Test that optional fields are made nullable."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"required_field": {"type": "string"},
"optional_field": {"type": "integer"},
},
"required": ["required_field"],
},
}
result = enable_strict_mode(schema, strict=True)
# Required field should NOT be made nullable
assert result["parameters"]["properties"]["required_field"]["type"] == "string"
# Optional field should be made nullable
assert result["parameters"]["properties"]["optional_field"]["type"] == ["integer", "null"]
def test_enable_strict_mode_recursive_nested_objects():
"""Test recursive handling of nested objects."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"config": {
"type": "object",
"properties": {
"nested_field": {"type": "string"},
"another_nested": {"type": "integer"},
},
},
},
"required": ["config"],
},
}
result = enable_strict_mode(schema, strict=True)
nested = result["parameters"]["properties"]["config"]
assert nested["additionalProperties"] is False
assert set(nested["required"]) == {"nested_field", "another_nested"}
def test_enable_strict_mode_recursive_arrays():
"""Test recursive handling of arrays with object items."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"item_field": {"type": "string"},
},
},
},
},
"required": ["items"],
},
}
result = enable_strict_mode(schema, strict=True)
array_items = result["parameters"]["properties"]["items"]["items"]
assert array_items["additionalProperties"] is False
assert array_items["required"] == ["item_field"]
def test_enable_strict_mode_strict_false_no_modification():
"""Test that strict=False doesn't modify schema structure."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"field": {"type": "string"},
},
"required": [],
},
}
result = enable_strict_mode(schema, strict=False)
assert "strict" not in result
assert result["parameters"]["required"] == []
# Verify the field type is unchanged
assert result["parameters"]["properties"]["field"]["type"] == "string"
def test_enable_strict_mode_non_strict_only_tool():
"""Test that NON_STRICT_ONLY tools are not modified."""
schema = {
"name": "test_tool",
MCP_TOOL_METADATA_SCHEMA_STATUS: "NON_STRICT_ONLY",
"parameters": {
"type": "object",
"properties": {
"field": {"type": "string"},
},
"required": [],
},
}
result = enable_strict_mode(schema, strict=True)
# Strict mode should not be applied
assert "strict" not in result
# Metadata should be removed
assert MCP_TOOL_METADATA_SCHEMA_STATUS not in result
# Required should be unchanged
assert result["parameters"]["required"] == []
def test_enable_strict_mode_preserves_existing_required():
"""Test that fields already in required are not made nullable."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"already_required": {"type": "string"},
"optional_field": {"type": "integer"},
},
"required": ["already_required"],
},
}
result = enable_strict_mode(schema, strict=True)
# already_required should NOT be made nullable (it was already required)
assert result["parameters"]["properties"]["already_required"]["type"] == "string"
# optional_field should be made nullable
assert result["parameters"]["properties"]["optional_field"]["type"] == ["integer", "null"]
# Both should now be in required
assert set(result["parameters"]["required"]) == {"already_required", "optional_field"}
def test_enable_strict_mode_handles_anyof():
"""Test that anyOf structures are recursively processed."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"config": {
"anyOf": [
{
"type": "object",
"properties": {
"nested_field": {"type": "string"},
},
},
{"type": "null"},
],
},
},
"required": ["config"],
},
}
result = enable_strict_mode(schema, strict=True)
# The object inside anyOf should have additionalProperties and required set
anyof_options = result["parameters"]["properties"]["config"]["anyOf"]
object_option = next(opt for opt in anyof_options if opt.get("type") == "object")
assert object_option["additionalProperties"] is False
assert object_option["required"] == ["nested_field"]
def test_enable_strict_mode_handles_type_array_nullable():
"""Test that fields with type array (already nullable) are handled correctly."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"already_nullable": {"type": ["string", "null"]},
"not_nullable": {"type": "integer"},
},
"required": [],
},
}
result = enable_strict_mode(schema, strict=True)
# Already nullable field should not get duplicate null
already_nullable_type = result["parameters"]["properties"]["already_nullable"]["type"]
assert already_nullable_type.count("null") == 1
# Not nullable should become nullable
assert result["parameters"]["properties"]["not_nullable"]["type"] == ["integer", "null"]
def test_enable_strict_mode_does_not_mutate_original():
"""Test that the original schema is not mutated."""
schema = {
"name": "test_tool",
"parameters": {
"type": "object",
"properties": {
"field": {"type": "string"},
},
"required": [],
},
}
original_required = schema["parameters"]["required"].copy()
original_field_type = schema["parameters"]["properties"]["field"]["type"]
result = enable_strict_mode(schema, strict=True)
# Original should be unchanged
assert schema["parameters"]["required"] == original_required
assert schema["parameters"]["properties"]["field"]["type"] == original_field_type
assert "strict" not in schema
# Result should be different
assert result["strict"] is True
assert len(result["parameters"]["required"]) == 1