#1940·outlines

DSL regex for List[T] and variadic Tuple[T, ...] forbids the empty collection, diverging from Dict and the JSON-schema path

Author: ErenAta16Created Jul 20, 2026Updated Sep 4, 2026

What

The DSL regex for List[T] (and the variadic Tuple[T, ...]) requires at least one element, so a valid empty collection can never be generated. _handle_list / _handle_tuple build [item(, item)*], with no way to match []. This diverges both from _handle_dict in the same file (which correctly allows {}) and from the JSON-schema path, which allows an empty array for the same type.

Where

src/outlines/types/dsl.py:

_handle_list:

python
return Sequence(
    [
        String("["),
        item_type,
        KleeneStar(Sequence([String(", "), item_type])),
        String("]"),
    ]
)

_handle_tuple (variadic Tuple[T, ...] branch):

python
return Sequence(
    [
        String("("),
        item_term,
        KleeneStar(Sequence([String(", "), item_term])),
        String(")"),
    ]
)

Both place item before the KleeneStar, so at least one element is mandatory. Compare _handle_dict right below them, which wraps the whole content in Optional(...) and therefore accepts {}:

python
return Sequence(
    [
        String("{"),
        Optional(
            Sequence(
                [key_type, String(":"), value_type, KleeneStar(...)]
            )
        ),
        String("}"),
    ]
)

Reproduction

python
import re
from typing import Dict, List, Tuple
from outlines.types.dsl import python_types_to_terms, to_regex

for tp, empty in [(List[int], "[]"), (Tuple[int, ...], "()"), (Dict[str, int], "{}")]:
    pattern = to_regex(python_types_to_terms(tp))
    print(f"{tp}: matches {empty!r} -> {re.fullmatch(pattern, empty) is not None}")

Output:

typing.List[int]: matches '[]' -> False
typing.Tuple[int, ...]: matches '()' -> False
typing.Dict[str, int]: matches '{}' -> True

So within the DSL itself, dicts allow empty but lists and variadic tuples don't.

The divergence from the JSON-schema path is just as clear. A pydantic model with a List[int] field goes through JsonSchema -> build_regex_from_schema instead, and that regex does allow the empty array:

python
from pydantic import BaseModel
from outlines.types.dsl import JsonSchema, to_regex

class M(BaseModel):
    items: List[int]

pattern = to_regex(JsonSchema(M))
print(re.fullmatch(pattern, '{"items": []}') is not None)   # True

So list[int] as a bare output type forbids [], but the same List[int] as a pydantic field allows it.

Actual vs expected

[] is a valid value for list[int] (and () for tuple[int, ...]) in both Python and JSON, with no min_items constraint in play. Structured generation should be able to produce it. Right now, using a bare list[int]/tuple[int, ...] output type, the model is forced to emit at least one element, which can push it into inventing spurious items when the correct answer is an empty list.

Suggested fix

Wrap the list/tuple content in Optional, exactly as _handle_dict already does:

python
# _handle_list
return Sequence(
    [
        String("["),
        Optional(
            Sequence(
                [item_type, KleeneStar(Sequence([String(", "), item_type]))]
            )
        ),
        String("]"),
    ]
)

and the analogous change for the variadic-tuple branch of _handle_tuple. This makes both allow the empty collection while keeping the one-or-more forms unchanged, and brings them in line with _handle_dict and the JSON-schema path. Fixed-length tuples (Tuple[int, str]) are unaffected, since they don't go through this branch. I can open a PR with this plus a regression test.

Environment

Reproduced against dottxt-ai/outlines main (current). Pure Python, no model/backend needed.