Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/openai/lib/_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import inspect
from typing import Any, TypeVar
from urllib.parse import unquote
from typing_extensions import TypeGuard

import pydantic
Expand Down Expand Up @@ -119,7 +120,7 @@ def resolve_ref(*, root: dict[str, object], ref: str) -> object:
if not ref.startswith("#/"):
raise ValueError(f"Unexpected $ref format {ref!r}; Does not start with #/")

path = ref[2:].split("/")
path = [unquote(key).replace("~1", "/").replace("~0", "~") for key in ref[2:].split("/")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decode the fragment before splitting pointer tokens

When a valid URI fragment percent-encodes a JSON Pointer separator, such as #/$defs%2FGroup%2FItem, percent-decoding produces the pointer /$defs/Group/Item. Splitting the raw fragment first instead treats $defs/Group/Item as one dictionary key and raises KeyError rather than traversing the nested schema. Decode the fragment before tokenization; literal slashes in a reference token remain represented by JSON Pointer's ~1 escape.

Useful? React with 👍 / 👎.

resolved = root
for key in path:
value = resolved[key]
Expand Down
35 changes: 34 additions & 1 deletion tests/lib/test_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,44 @@

import openai
from openai._compat import PYDANTIC_V1
from openai.lib._pydantic import to_strict_json_schema
from openai.lib._pydantic import resolve_ref, to_strict_json_schema, _ensure_strict_json_schema

from .schema_types.query import Query


def test_resolve_ref_decodes_json_pointer_tokens() -> None:
schema: dict[str, object] = {
"$defs": {
"path/to model~v1": {"type": "string"},
}
}

assert resolve_ref(root=schema, ref="#/$defs/path~1to%20model~0v1") == {"type": "string"}


def test_strict_schema_inlines_escaped_ref() -> None:
schema: dict[str, object] = {
"$defs": {"path/to model~v1": {"type": "object", "properties": {"value": {"type": "string"}}}},
"type": "object",
"properties": {
"result": {
"$ref": "#/$defs/path~1to%20model~0v1",
"description": "A custom result",
}
},
}

strict_schema = _ensure_strict_json_schema(schema, path=(), root=schema)

assert strict_schema["properties"]["result"] == {
"type": "object",
"properties": {"value": {"type": "string"}},
"required": ["value"],
"description": "A custom result",
"additionalProperties": False,
}


def test_most_types() -> None:
if not PYDANTIC_V1:
assert openai.pydantic_function_tool(Query)["function"] == snapshot(
Expand Down