savanty: Bridging Natural Language and Mathematical Solvers in 2026

How savanty turns English problem descriptions into mathematically guaranteed solutions. The case for LLM-to-formal-solver pipelines over pure LLM output.

The problem

Pure LLM output cannot guarantee optimality, feasibility, or even correctness for combinatorial optimisation problems. Ask an LLM to solve a constraint satisfaction problem, and the best you can hope for is a plausible solution — one that might be feasible, might be optimal, and cannot be verified without re-solving from scratch.

The right architecture is LLM-to-formal-solver pipeline: the LLM translates the natural-language problem into a formal solver input (Z3, OR-Tools, MiniZinc, Gurobi), and the formal solver returns a mathematically guaranteed solution. The output inherits the solver’s mathematical guarantees.

savanty is our implementation of this architecture.

What savanty is

savanty is a Python library that:

  1. Takes a natural-language description of an optimisation problem.
  2. Uses an LLM to translate it into a formal solver input.
  3. Solves the problem with a formal solver.
  4. Returns the solution with the solver’s certificate (proof of optimality or proof of infeasibility).

The LLM is the translator; the formal solver is the guarantor. The split is deliberate: LLMs are good at language, formal solvers are good at mathematics. Composing them gives the best of both.

The competitive landscape

savanty is not the only project in this space. The major approaches:

ApproachProject / paperWhat it does
Pure LLM(many)Asks the LLM to solve directly. No guarantees.
LLM → DSL → Solversavanty, LLM-based OR, NL2OPT researchLLM translates English to a solver DSL, solver runs
LLM → Python → External Solver(research)LLM writes Python that calls a solver
LLM → SMT directly(research)LLM writes SMT-LIB
Specialised compilers(domain-specific)E.g. scheduling compilers that have a fixed schema

savanty’s design is the LLM → DSL → Solver architecture, with a focus on robust translation (a small DSL, schema-validated output, and a fallback solver path).

The savanty design

A savanty problem is structured as:

from savanty import Problem, Variable, Constraint, Objective

problem = Problem(
    name="schedule_workflows",
    description="Schedule 10 workflows across 3 workers, "
                "minimising total makespan, with each worker "
                "limited to 8 hours per day.",
    variables=[
        Variable("worker_for_w1", domain=["alice", "bob", "carol"]),
        Variable("worker_for_w2", domain=["alice", "bob", "carol"]),
        ...
    ],
    constraints=[
        Constraint("no_overload", "sum(hours_per_worker) <= 8 * days"),
        Constraint("all_assigned", "all(w.is_assigned() for w in workflows)"),
    ],
    objective="minimize(makespan)",
)

savanty then:

  1. Uses the LLM to translate the description into a structured Problem object.
  2. Compiles the Problem to a solver-specific format (MiniZinc, OR-Tools CP-SAT, Z3 SMT-LIB, etc.).
  3. Runs the solver.
  4. Returns the solution with the certificate.

The LLM is the only “soft” component. The translation is schema-validated; the solver run is deterministic; the output is provable.

The case for formal solvers

Why not just ask the LLM? Because:

  • LLMs hallucinate. Ask an LLM to solve a 10-variable CSP and it might return a solution that violates a constraint.
  • LLMs cannot certify optimality. “This looks optimal” is not a proof.
  • LLMs scale poorly. As the problem size grows, LLM accuracy drops. Formal solvers scale to billions of variables with proven complexity bounds.

The right architecture uses the LLM for what it’s good at (translating intent) and the solver for what it’s good at (finding and certifying the answer).

A concrete example: scheduling

A typical scheduling problem in plain English:

“I have 10 jobs that need to be done on 3 machines. Each machine can only do one job at a time. Job 1 takes 2 hours on machine A, 4 hours on machine B, 3 hours on machine C. [etc.] I want to finish all jobs as quickly as possible.”

In savanty, you express this as:

problem = Problem(
    name="schedule_jobs",
    description="<the above>",
    variables=[
        Variable(f"job_{i}_start", domain=range(0, 24)),
        Variable(f"job_{i}_machine", domain=["A", "B", "C"]),
        for i in range(1, 11)
    ],
    constraints=[
        Constraint("no_overlap", "for all m, jobs on m don't overlap"),
        Constraint("machine_duration", "for all j, m, job j on m takes duration[j][m] hours"),
    ],
    objective="minimize(makespan)",
)
result = savanty.solve(problem)
print(result.solution)     # start time and machine for each job
print(result.certificate)  # proof of optimality

savanty’s solver runs in milliseconds for this problem size. The LLM translation takes 1-2 seconds. Total latency: ~2 seconds. Compare to asking an LLM to solve directly: ~5 seconds, no certificate, often wrong.

When savanty is the right answer

  • The problem has a formal structure (CSP, SAT, SMT, ILP, CP).
  • The cost of a wrong answer is high (medical scheduling, logistics, financial portfolio rebalancing).
  • You need a certificate (compliance, audit, debugging).
  • The user is comfortable with the LLM “failing gracefully” on problems that don’t fit the supported schemas (and the schemas are extensible).

When savanty is NOT the right answer

  • The problem is generative, not constraint-based (creative writing, image generation, code generation). Use a different architecture.
  • The problem has no formal structure (e.g. “make this product description better”). Use a pure LLM.
  • The latency budget is sub-100ms. The LLM call alone takes 1-2 seconds.