Describe the bug
json(schema=...) rejects a pydantic model (or TypeAdapter) with a bare dict / Dict[Any, ...] field, raising TypeError: JSON does not support non-string keys, got type any. But a bare dict maps to an unconstrained JSON object, which is valid (JSON object keys are always strings) and is actually supported by the backend. The pre-check in GenerateJsonSchemaSafe is stricter than it should be.
Where
guidance/library/_pydantic.py, GenerateJsonSchemaSafe.generate_inner:
def generate_inner(self, schema):
if schema["type"] == "dict":
key_type = schema["keys_schema"]["type"]
if key_type != "str":
raise TypeError(f"JSON does not support non-string keys, got type {key_type}")
return super().generate_inner(schema)
The check is meant to catch genuinely non-string keys such as dict[int, ...], which it does correctly. But a dict with no key type (bare dict, Dict[Any, Any], Dict[str, Any] with the key unparametrized to Any) has a keys_schema of {"type": "any"}, and "any" != "str", so it is rejected too. That is a false positive: an any-keyed dict is a plain JSON object.
To Reproduce
from pydantic import BaseModel
from guidance import json
# common "arbitrary JSON metadata" pattern
class Config(BaseModel):
name: str
metadata: dict
json(schema=Config)
# TypeError: JSON does not support non-string keys, got type any
Why it's a false positive
Pydantic's own default schema generator turns a bare dict into a valid, unconstrained object schema, and the backend accepts it:
from pydantic import TypeAdapter
from guidance import json
print(TypeAdapter(dict).json_schema())
# {'additionalProperties': True, 'type': 'object'}
json(schema={'type': 'object'}) # accepted, llguidance validates fine
json(schema=TypeAdapter(dict).json_schema()) # accepted
So the schema a bare dict produces is supported end to end; it is only the GenerateJsonSchemaSafe pre-check that blocks it, and the error message ("JSON does not support non-string keys") is inaccurate for this case: a bare dict's keys are strings once serialized, they are just unconstrained.
dict[int, int] is correctly still rejected, that part of the check is doing its job.
Suggested fix
Allow the "any" key type alongside "str", since an any-keyed dict is a valid JSON object:
key_type = schema.get("keys_schema", {}).get("type", "any")
if key_type not in ("str", "any"):
raise TypeError(f"JSON does not support non-string keys, got type {key_type}")
int/float/etc. keys stay rejected. I can open a PR with this plus tests (bare dict, Dict[str, Any], Dict[Any, Any], and a bare-dict model field are accepted; dict[int, int] still raises).
Related, not included in the fix
dict[Literal["x", "y"], int] (string-literal keys) is also rejected with the same "non-string keys, got type literal" message. Those keys are strings, but pydantic represents that schema with propertyNames, which the backend reports as Unimplemented keys: ["propertyNames"]. So that case is genuinely unsupported today, just for a different reason than the message states. Flagging it here for visibility; the fix above deliberately leaves literal keys rejected rather than pushing an unsupported schema downstream.
System info
Reproduced against guidance-ai/guidance main (current). Pure Python, models.Mock / schema-validation path, no real model needed.
Describe the bug
json(schema=...)rejects a pydantic model (orTypeAdapter) with a baredict/Dict[Any, ...]field, raisingTypeError: JSON does not support non-string keys, got type any. But a bare dict maps to an unconstrained JSON object, which is valid (JSON object keys are always strings) and is actually supported by the backend. The pre-check inGenerateJsonSchemaSafeis stricter than it should be.Where
guidance/library/_pydantic.py,GenerateJsonSchemaSafe.generate_inner:The check is meant to catch genuinely non-string keys such as
dict[int, ...], which it does correctly. But a dict with no key type (baredict,Dict[Any, Any],Dict[str, Any]with the key unparametrized toAny) has akeys_schemaof{"type": "any"}, and"any" != "str", so it is rejected too. That is a false positive: an any-keyed dict is a plain JSON object.To Reproduce
Why it's a false positive
Pydantic's own default schema generator turns a bare
dictinto a valid, unconstrained object schema, and the backend accepts it:So the schema a bare dict produces is supported end to end; it is only the
GenerateJsonSchemaSafepre-check that blocks it, and the error message ("JSON does not support non-string keys") is inaccurate for this case: a bare dict's keys are strings once serialized, they are just unconstrained.dict[int, int]is correctly still rejected, that part of the check is doing its job.Suggested fix
Allow the
"any"key type alongside"str", since an any-keyed dict is a valid JSON object:int/float/etc. keys stay rejected. I can open a PR with this plus tests (bare dict,Dict[str, Any],Dict[Any, Any], and a bare-dict model field are accepted;dict[int, int]still raises).Related, not included in the fix
dict[Literal["x", "y"], int](string-literal keys) is also rejected with the same "non-string keys, got type literal" message. Those keys are strings, but pydantic represents that schema withpropertyNames, which the backend reports asUnimplemented keys: ["propertyNames"]. So that case is genuinely unsupported today, just for a different reason than the message states. Flagging it here for visibility; the fix above deliberately leavesliteralkeys rejected rather than pushing an unsupported schema downstream.System info
Reproduced against
guidance-ai/guidancemain(current). Pure Python,models.Mock/ schema-validation path, no real model needed.