Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ dependencies = [
"typing_extensions",
"outlines_core==0.2.14",
"genson",
"jsonpath_ng",
]
dynamic = ["version"]

Expand Down Expand Up @@ -180,7 +179,6 @@ module = [
"tf-keras",
"tf-keras.*",
"mkdocs_gen_files.*",
"jsonpath_ng.*",
"llguidance.*",
"xgrammar.*",
]
Expand Down
44 changes: 26 additions & 18 deletions src/outlines/models/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import jsonpath_ng


def set_additional_properties_false_json_schema(schema: dict) -> dict:
"""Set additionalProperties to False to all objects in the schema using jsonpath.
"""Set additionalProperties to False on all object schemas.

Recursively walks the JSON schema and, for every object schema, sets
``additionalProperties`` to False unless it is already present. An object
schema is one whose ``type`` is ``"object"`` or, for a nullable object, a
list containing ``"object"`` (e.g. ``["object", "null"]``).

Parameters
----------
Expand All @@ -14,20 +16,26 @@ def set_additional_properties_false_json_schema(schema: dict) -> dict:
dict
The modified schema with additionalProperties set to False
"""
# Get all nodes
jsonpath_expr = jsonpath_ng.parse('$..*')
matches = jsonpath_expr.find(schema)

# Go over all nodes and set additionalProperties to False if it's an
# object. `type` can either be the bare string "object" or, per the JSON
# Schema spec, a list of type names (e.g. ["object", "null"]) used to
# express nullable objects, so both forms must be checked.
for match in matches:
is_object_type = match.value == 'object' or (
isinstance(match.value, list) and 'object' in match.value
)
if is_object_type:
if 'additionalProperties' not in match.context.value:
match.context.value['additionalProperties'] = False
def _walk(node):
if isinstance(node, dict):
# Only an object *schema* should get ``additionalProperties``. Keying
# off the string ``"object"`` appearing anywhere would wrongly add the
# keyword to non-object schemas, e.g. a string field with
# ``const``/``default``/``title`` equal to ``"object"``, which the
# OpenAI API then rejects. ``type`` may be the bare string or a list
# such as ``["object", "null"]`` for a nullable object.
node_type = node.get("type")
is_object = node_type == "object" or (
isinstance(node_type, list) and "object" in node_type
)
if is_object and "additionalProperties" not in node:
node["additionalProperties"] = False
for value in node.values():
_walk(value)
elif isinstance(node, list):
for item in node:
_walk(item)

_walk(schema)
return schema
26 changes: 25 additions & 1 deletion tests/models/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_set_additional_properties_false_json_schema():


def test_set_additional_properties_false_json_schema_nested_object():
# nested object with a plain "type": "object" gets additionalProperties too
# nested object schemas get additionalProperties too
schema = {
"type": "object",
"properties": {
Expand Down Expand Up @@ -88,3 +88,27 @@ def test_set_additional_properties_false_json_schema_nullable_type_array():
modified_schema = set_additional_properties_false_json_schema(schema)
assert modified_schema["additionalProperties"] is False
assert modified_schema["properties"]["address"]["additionalProperties"] is False


def test_set_additional_properties_false_json_schema_string_value_object():
# A non-object schema whose value happens to equal the string "object"
# (e.g. a Literal["object"] field emitted by pydantic as `const`) must NOT
# get `additionalProperties`, which the OpenAI API rejects on non-objects.
schema = {
"type": "object",
"properties": {
"kind": {"type": "string", "const": "object", "title": "object"},
},
"required": ["kind"],
}
modified_schema = set_additional_properties_false_json_schema(schema)
target_schema = {
"type": "object",
"properties": {
"kind": {"type": "string", "const": "object", "title": "object"},
},
"required": ["kind"],
"additionalProperties": False,
}
assert modified_schema == target_schema
assert "additionalProperties" not in modified_schema["properties"]["kind"]
23 changes: 0 additions & 23 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading