What
Inside a DSL container (list[date], dict[str, date], tuple[date, ...]), datetime.date, datetime.time and datetime.datetime render without quotes. JSON has no literal syntax for a date, so the generated regex forces the model to emit something json.loads rejects.
The clearest way to see it is dict, because the key and the value of the same type disagree:
import datetime, re
from typing import Dict, List
from outlines.types.dsl import python_types_to_terms, to_regex
pattern = to_regex(python_types_to_terms(List[datetime.date]))
print(pattern)
print(' matches ["2024-01-01"] :', re.fullmatch(pattern, '["2024-01-01"]') is not None)
print(' matches [2024-01-01] :', re.fullmatch(pattern, '[2024-01-01]') is not None)
value = to_regex(python_types_to_terms(Dict[str, datetime.date]))
key = to_regex(python_types_to_terms(Dict[datetime.date, str]))
print('dict[str, date] matches {"d":2024-01-01} :', re.fullmatch(value, '{"d":2024-01-01}') is not None)
print('dict[date, str] matches {"2024-01-01":"x"} :', re.fullmatch(key, '{"2024-01-01":"x"}') is not None)
Output on main (be2cd15):
\[((\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))(,\ ((\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])))*\]
matches ["2024-01-01"] : False
matches [2024-01-01] : True
dict[str, date] matches {"d":2024-01-01} : True
dict[date, str] matches {"2024-01-01":"x"} : True
So dict[date, str] quotes the date when it is a key, and dict[str, date] leaves the same date bare when it is a value. list[date] only matches the unparseable form:
json.loads('[2024-01-01]') -> Expecting ',' delimiter: line 1 column 6 (char 5)
time and datetime behave the same way.
Where it comes from
_ensure_json_quoted quotes String terms always, and Regex terms only when quote_regex=True. _handle_dict passes quote_regex=True for keys, because JSON object keys must be strings. Values, list items and tuple items don't. types.date is a bare Regex whose pattern carries no quotes, unlike types.string, so it falls through the value path untouched.
The key half was already handled in #1917. The value half looks like it was just missed.
Why I think this is a bug and not intended
Standalone date staying unquoted is clearly deliberate, same as types.boolean being Python-style, and I'm not proposing to touch that. The container path is different: everything else in a DSL container is JSON-shaped, the brackets are JSON, string literals get quoted, and dict keys of temporal type already get quoted. A date value is the one thing that comes out in a form nothing can parse.
I have a fix and tests ready, scoped to _ensure_json_quoted, matching the built-in temporal terms by identity so a user's own Regex that happens to share the pattern is left alone. Opening the issue first. PR to follow.
Environment
Outlines main at be2cd15, Python 3.13, macOS.
What
Inside a DSL container (
list[date],dict[str, date],tuple[date, ...]),datetime.date,datetime.timeanddatetime.datetimerender without quotes. JSON has no literal syntax for a date, so the generated regex forces the model to emit somethingjson.loadsrejects.The clearest way to see it is
dict, because the key and the value of the same type disagree:Output on
main(be2cd15):So
dict[date, str]quotes the date when it is a key, anddict[str, date]leaves the same date bare when it is a value.list[date]only matches the unparseable form:timeanddatetimebehave the same way.Where it comes from
_ensure_json_quotedquotesStringterms always, andRegexterms only whenquote_regex=True._handle_dictpassesquote_regex=Truefor keys, because JSON object keys must be strings. Values, list items and tuple items don't.types.dateis a bareRegexwhose pattern carries no quotes, unliketypes.string, so it falls through the value path untouched.The key half was already handled in #1917. The value half looks like it was just missed.
Why I think this is a bug and not intended
Standalone
datestaying unquoted is clearly deliberate, same astypes.booleanbeing Python-style, and I'm not proposing to touch that. The container path is different: everything else in a DSL container is JSON-shaped, the brackets are JSON, string literals get quoted, and dict keys of temporal type already get quoted. A date value is the one thing that comes out in a form nothing can parse.I have a fix and tests ready, scoped to
_ensure_json_quoted, matching the built-in temporal terms by identity so a user's ownRegexthat happens to share the pattern is left alone. Opening the issue first. PR to follow.Environment
Outlines
mainat be2cd15, Python 3.13, macOS.