Skip to content

DSL containers leave date/time/datetime unquoted, producing output json.loads rejects #1960

Description

@bharadwaj-pendyala

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions