Strict typing for loose models.
PyPI - Version PyPi - Python Version Github - Open Issues PyPi - Downloads (Monthly) Github - Commits
Typus is a Python library for Grammar Constrained Decoding (GCD).
Large Language Models (LLMs) are powerful but chaotic. They hallucinate invalid syntax, invent non-existent functions, and break JSON formatting. Typus solves this by generating strict Context-Free Grammars (CFGs) that constrain the LLM's token generation process.
Instead of writing complex EBNF strings by hand, you use a Pythonic Interface to define your constraintsβfrom simple regex patterns to entire semantic domains.
- π« No More Hallucinations: Force the LLM to output exactly what you need (valid JSON, SQL, Python, or CSV).
- π Python-First: Define grammars using standard Python operators (
|,+) and types (dataclasses,int,str). - π§© Universal: Define your grammar once and compile it for any target:
- Llama.cpp / GGUF: Compiles to GBNF.
- Validators: Compiles to Regex.
- Parsers: Compiles to Lark (EBNF).
- π§ Cognitive Control: Use Templates to force "Chain of Thought" reasoning before actions.
pip install typus-dsl
# or with uv
uv add typus-dslTypus offers three layers of abstraction depending on your needs.
For when you need pixel-perfect control over every character.
Use standard Python operators to define rules.
from typus import Grammar g = Grammar() # Define primitives g.digit = g.regex(r"[0-9]") g.number = g.digit + g.maybe("." + g.some(g.digit)) # Define structure g.coordinate = "(" + g.number + ", " + g.number + ")" g.root = "Point: " + g.coordinate
For when you want to structure the LLM's thought process.
Mix static prompts with dynamic grammar constraints using Python f-string syntax.
# Force the model to think before it answers g.root = g.template( "Thought: {thought}\nAction: {action}", thought=g.some(g.regex(r"[^\n]+"), sep="\n"), action="SEARCH" | "CALCULATE" )
For when you want to project a "Problem Domain" into a target language.
Define your types and functions in Python, and Typus generates the grammar that enforces them in JSON, SQL, HTML, or Python.
# Define the domain once @dataclass class User: ... # Generate a JSON Schema grammar Json().build(Domain(User)) # Generate a SQL Query grammar SQLQuery().build(Domain(User))
Ensure the model generates valid SemVer strings like v1.0.2 or v2.10.0-rc1.
from typus import Grammar g = Grammar() g.digits = g.regex(r"(0|[1-9][0-9]*)") g.version = g.digits + "." + g.digits + "." + g.digits g.tag = g.maybe("-" + g.regex(r"[0-9a-z-]+")) g.root = "v" + g.version + g.tag print(g.compile("gbnf"))
Force an Agent to provide a reasoning trace before emitting a final JSON answer. This prevents "blind" answering.
from typus import Grammar g = Grammar() g.thought = g.some(g.regex(r"[^\n]+"), sep="\n") g.json_blob = g.regex(r"\{.*\}") # Simplified JSON regex # The template constrains the structure g.root = g.template( "Reasoning:\n{steps}\n\nFinal Answer:\n{result}", steps=g.thought, result=g.json_blob )
Define your data schema using Pydantic or Dataclasses, and get a grammar that guarantees the output matches that schema.
from dataclasses import dataclass from typing import List from typus.languages import Json @dataclass class Product: id: int name: str tags: List[str] # Generate a strict GBNF grammar for this schema # The LLM cannot miss a comma, quote, or bracket. g = Json().build(Product) print(g.compile("gbnf"))
Expose a specific set of "Allowed Queries" to the LLM. The grammar ensures the model can only generate valid SELECT statements for your specific table columns, preventing SQL Injection and hallucinations.
from typus import Grammar, Domain from typus.languages import SQLQuery # 1. Define the Schema @dataclass class Users: name: str age: int # 2. Define allowed access patterns def get_users_by_name(name: str) -> Users: ... def get_users_by_age(min_age: int) -> Users: ... # 3. Build the Grammar # The LLM can generate: "SELECT name, age FROM Users WHERE name = '...'" # It CANNOT generate: "DROP TABLE Users" g = SQLQuery().build( Domain(Users, entrypoints=[get_users_by_name, get_users_by_age]) )
Typus is compiler-agnostic. You build the AST once, and export it to:
| Backend | Command | Use Case |
|---|---|---|
| GBNF | g.compile("gbnf") |
Llama.cpp, llama-python, local GGUF models. |
| Regex | g.compile("regex", max_depth=3) |
Guidance, Outlines, Standard API validators. Supports bounded recursion. |
| Lark | g.compile("lark") |
Python Parsing. Use Typus to parse the string the LLM just generated. |
| JSON Schema | (Via Json language) |
OpenAI / Anthropic structured output APIs. |
- v0.1: Core DSL (
Sequence,Choice,Regex), GBNF Compiler. - v0.2: Templates (
g.template), Regex Compiler with unrolling. - v0.3: Lark Compiler (Round-trip parsing).
- v0.4: The Domain Engine (
typus.domainreflection). - v0.5: Language Strategies (
Json,SQLQuery,Markdown,Python).
MIT License. See LICENSE for details.