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:
- Takes a natural-language description of an optimisation problem.
- Uses an LLM to translate it into a formal solver input.
- Solves the problem with a formal solver.
- 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:
| Approach | Project / paper | What it does |
|---|---|---|
| Pure LLM | (many) | Asks the LLM to solve directly. No guarantees. |
| LLM → DSL → Solver | savanty, LLM-based OR, NL2OPT research | LLM 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:
- Uses the LLM to translate the
descriptioninto a structuredProblemobject. - Compiles the
Problemto a solver-specific format (MiniZinc, OR-Tools CP-SAT, Z3 SMT-LIB, etc.). - Runs the solver.
- 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.
What to read next
- From English to Optimal: How savanty Bridges Natural Language and Constraint Solvers — the full savanty architecture
- Better Rankings with Fewer Comparisons: Multi-Armed Bandits for Efficient Ordering — the bandit ranking companion
- savanty repository
- MiniZinc — the savanty default backend
- Z3 — the SMT solver
- OR-Tools CP-SAT — the constraint programming solver