From 67f0b4b280bc44af71cb199ab065211b96726601 Mon Sep 17 00:00:00 2001 From: Hudson Cooper Date: Mon, 16 Sep 2024 13:15:39 -0700 Subject: [PATCH 1/3] Allow json-loads-able strings to be passed as schema --- guidance/library/_json.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guidance/library/_json.py b/guidance/library/_json.py index a7d3354b4..205598de8 100644 --- a/guidance/library/_json.py +++ b/guidance/library/_json.py @@ -1,4 +1,4 @@ -from json import dumps as json_dumps +from json import dumps as json_dumps, loads as json_loads from enum import Enum import math from typing import ( @@ -876,6 +876,7 @@ def json( *, schema: Union[ None, + str, JSONSchema, Type["pydantic.BaseModel"], "pydantic.TypeAdapter", @@ -937,7 +938,9 @@ def json( # Default schema is empty, "anything goes" schema # TODO: consider default being `{"type": "object"}` schema = {} - elif isinstance(schema, (Mapping, bool)): + elif isinstance(schema, (Mapping, bool, str)): + if isinstance(schema, str): + schema = cast(JSONSchema, json_loads(schema)) # Raises jsonschema.exceptions.SchemaError or ValueError # if schema is not valid jsonschema.validators.Draft202012Validator.check_schema(schema) From 35e7010d1a2e08216de11cf6deca55eed1a7a8a3 Mon Sep 17 00:00:00 2001 From: Hudson Cooper Date: Tue, 29 Oct 2024 09:29:51 -0700 Subject: [PATCH 2/3] test --- tests/unit/library/test_json.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/unit/library/test_json.py b/tests/unit/library/test_json.py index cad0d6742..1151624b2 100644 --- a/tests/unit/library/test_json.py +++ b/tests/unit/library/test_json.py @@ -1,15 +1,15 @@ import json from functools import partial -from typing import Any, Dict, Set, Union, Optional +from typing import Any, Set, Union, Optional import pytest from jsonschema import validate, ValidationError -from json import dumps as json_dumps +from json import dumps as json_dumps, loads as json_loads from guidance import json as gen_json from guidance import models -from guidance.library._json import IGNORED_KEYS +from guidance.library._json import IGNORED_KEYS, JSONSchema from ...utils import check_match_failure as _check_match_failure from ...utils import check_run_with_temperature @@ -17,8 +17,11 @@ def generate_and_check( - target_obj: Any, schema_obj, desired_temperature: Optional[float] = None + target_obj: Any, schema_obj: Union[str, JSONSchema], desired_temperature: Optional[float] = None ): + if isinstance(schema_obj, str): + schema_obj = json_loads(schema_obj) + # Sanity check what we're being asked validate(instance=target_obj, schema=schema_obj) prepared_json = json_dumps(target_obj) @@ -46,7 +49,7 @@ def check_match_failure( good_bytes: Optional[bytes] = None, failure_byte: Optional[bytes] = None, allowed_bytes: Optional[Set[bytes]] = None, - schema_obj: Dict[str, Any], + schema_obj: Union[str, JSONSchema], ): grammar = gen_json(schema=schema_obj) @@ -3161,3 +3164,17 @@ def test_whitespace_flexibility(self, indent, separators, schema, obj): assert grammar.match(prepared_json, raise_exceptions=True) is not None model = models.Mock(f"{prepared_json}".encode()) assert str(model + grammar) == prepared_json + + +class TestStringSchema: + def test_good(self): + schema = """{"type": "object", "properties": {"a": {"type": "string"}}}""" + target_obj = {"a": "hello"} + generate_and_check(target_obj, schema) + + def test_bad(self): + schema = """{"type": "object", "properties": {"a": {"type": "string"}}}""" + check_match_failure( + bad_string='{"a": 42}', + schema_obj=schema, + ) From 1fcd3f070fa4514c48b0cfd6827400d818c56e05 Mon Sep 17 00:00:00 2001 From: Hudson Cooper Date: Tue, 29 Oct 2024 09:49:31 -0700 Subject: [PATCH 3/3] docstring --- guidance/library/_json.py | 1 + 1 file changed, 1 insertion(+) diff --git a/guidance/library/_json.py b/guidance/library/_json.py index e5b7abcbf..eb9c4a7bc 100644 --- a/guidance/library/_json.py +++ b/guidance/library/_json.py @@ -966,6 +966,7 @@ def json( schema : Union[None, Mapping[str, Any], Type[pydantic.BaseModel], pydantic.TypeAdapter] One of: - None, in which case any valid JSON will be generated + - A string representing a JSON schema which will be parsed using ``json.loads()`` - A JSON schema object. This is a JSON schema string which has been passed to ``json.loads()`` - A subclass of ``pydantic.BaseModel`` - An instance of ``pydantic.TypeAdapter``