TL;DR
LLMs reason inconsistently: they lean on surface heuristics, fumble negation, and fall apart on long logical chains or out-of-domain questions. Proof of Thought (PoT) keeps the LLM for what it’s good at — reading the world and proposing relevant facts and rules — but moves the actual deciding into a symbolic theorem prover that gives mathematical guarantees. The LLM writes its “thoughts” as a JSON Domain-Specific Language (sorts, functions, constants, a knowledge base, rules, and verifications); a custom interpreter translates that JSON into first-order logic; Z3 then returns SAT/UNSAT (true/false) plus a proof or counterexample. A feedback loop feeds compiler/type errors back to the LLM so it can self-correct. On StrategyQA it compiled 82.4% of programs with 91.4% recall; on a hard multimodal OSHA-hazard benchmark the feedback loop drove compile errors from 14.6% to 0% and lifted the win rate from 72% to 81.55%. The payoff isn’t raw accuracy — bigger CoT models already crush these benchmarks — it’s that every answer comes with a human-readable, auditable chain of logic.
Problem & Motivation
The concrete pain: when you put an LLM in a high-stakes loop (safety inspection, compliance, healthcare triage), you get an answer and a paragraph of plausible-sounding prose, but no guarantee the prose actually entails the answer. The reasoning is opaque, so when it’s wrong you can’t see why, and you can’t certify it for an auditor or a regulator.
Existing fixes are all variations on “make the LLM think out loud more”:
- Chain-of-Thought (CoT) adds intermediate steps.
- Self-Consistency (CoT-SC) samples many chains and votes.
- Tree/Graph of Thoughts (ToT/GoT) search over branching reasoning structures.
These raise benchmark scores but the authors’ complaint is precise: the mechanism of improvement is opaque, failure modes are not understood, and there is no verifiability. A voting ensemble of five hallucinations is still five hallucinations. None of these methods can hand you a proof. And in low-data, long-tail domains (the paper’s example: spotting OSHA violations in messy Reddit photos) you can’t just train a specialist model — there’s no labeled data — so you’re stuck relying on the LLM’s commonsense, which is exactly the part you can’t trust.
If you can’t state the pain in one sentence: LLM answers are unverifiable, so you can’t safely automate decisions where being wrong is expensive.
What’s New (Core Contribution)
Three real contributions, separating signal from packaging:
-
A JSON-based DSL as the neurosymbolic interface.
- Before: neurosymbolic systems either trained differentiable logic end-to-end (DeepProbLog, Scallop) or had LLMs emit raw Prolog/Z3 Python — brittle, expert-only, and hard for the model to get right.
- Now: the LLM emits near-English JSON with named sections (
sorts,functions,constants,knowledge_base,rules,verifications, optionaloptimization,actions). It rides the structured-output guarantees that OpenAI/Google already provide, so no model retraining is required. JSON is both machine-parseable and human-readable, so a domain expert can audit it.
-
A typed interpreter with sort management.
- Before: logic from LLMs often mixed up entity types silently (applying an equipment predicate to a person), producing nonsense the prover would still “solve.”
- Now: a real type system with sorts (Person, Equipment, Time, plus Z3 primitives Bool/Int/Real) catches semantic errors before proving. This is the paper’s claimed key differentiator — reasoning over human-level concepts with type safety.
-
Explicit separation of facts vs. rules vs. goals, plus an error-feedback loop.
- Before: monolithic reasoning blobs where you can’t tell what’s assumed from what’s inferred.
- Now:
knowledge_base(axioms/facts),rules(inferential implications), andverifications(what to prove) are separate sections. When the interpreter or Z3 throws an error, the diagnostic is fed back to the LLM for up to 2 retries — this is what pushes compile success way up.
What’s not new: the idea of LLM→theorem-prover is older (and tools like LeanDojo, SatLM exist). The genuine novelty is the accessible, retraining-free JSON DSL + typed interpreter + feedback loop as a packaged, general-purpose pipeline.
How It Works (Technically)
The whole framework is three boxes wired in series. The paper formalizes it as:
L = G(x; pθ); ϕ = I(L); Verification Result = T(ϕ)
Translate that notation to mechanism:
- x is your input (a question, optionally with an image).
- pθ is the pretrained LLM (parameters θ — just “the frozen model”).
- G (“Generator”) = prompt the LLM to turn x into L, the JSON DSL program. This is the only learned/neural step; it’s pure prompting, no fine-tuning.
- I (“Interpreter”) = a deterministic Python program that parses L and builds ϕ, an actual first-order-logic formula in Z3’s API. No ML here — it’s a compiler.
- T (“Theorem prover”) = Z3 checks ϕ and returns SAT (the property holds / answer is True), UNSAT (it doesn’t / False), plus a model (witness) or counterexample.
The clever part is that the “reasoning” is split: the neural side proposes what’s true and what the rules are (the fallible, world-knowledge part), and the symbolic side does the deduction (the part that must be exact). Z3 will never make an arithmetic or logical slip; if the answer is wrong, it’s because the LLM gave it wrong facts or rules — and those are sitting right there in readable JSON for you to inspect.
The DSL, section by section (this is the contract the LLM must fill in):
sorts— the types in your world.{"name": "Person", "type": "DeclareSort"}. Sorts let you reason about Persons and Equipment instead of anonymous symbols, and let the interpreter reject type errors.functions— typed relations/predicates:f: S1 × … × Sk → Sr. IfSr = Boolit’s a predicate (a yes/no fact likeWearing(person, gear)); otherwise it’s a function returning a value (jump_height(person) → Real).constants— the concrete individuals, each tagged with a sort:alice : Person.variables— placeholders bound by quantifiers, each ranging over a sort:p : Person.knowledge_base— the facts assumed true. e.g.jump_height(javier_sotomayor) == 2.45,height(average_giraffe) == 5.5.rules— the inferences, written as universally-quantified implications: “for all p:Person, e:Equipment,Using(p,e) → Wearing(p, hardHat).” This is where domain knowledge lives.verifications— the goal: the property to check. “Isjump_height(javier) >= height(giraffe)?”actions—verifyoroptimize. Tells the interpreter what to do.optimization(optional) — an objectivef_obj+ constraintsC, so Z3 can do constrained optimization, not just satisfiability.
One concrete trace — “Could Javier Sotomayor jump over the head of the average giraffe?”
- LLM reads the question, retrieves from its own knowledge that Sotomayor’s high-jump record is 2.45 m and a giraffe is ~5.5 m, and emits a JSON program: sorts
Person/Animal/Real; functionsjump_height,height; factsjump_height(javier)==2.45,height(giraffe)==5.5; verificationjump_height(javier) >= height(giraffe). - The interpreter parses this, declares the Z3 sorts/functions, asserts the two facts, and builds the goal formula.
- Z3 evaluates
2.45 >= 5.5→ UNSAT → answer False. Correct, and you can see the exact numbers and comparison that produced it.
The type system & interpreter internals. The interpreter maintains a symbol table (identifier → definition) for scope management, builds abstract syntax trees for each formula, carefully tracks bound variables under quantifiers, and supports term substitution (the mechanical heart of applying a ∀ rule to a specific constant). Before handing off to Z3 it does light pre-processing: simplification via logical identities (e.g. dropping double negations), normalization toward prenex form, and early error detection for type mismatches and contradictions.
The feedback loop is the unsung hero of the results. The interpreter emits targeted diagnostics — “type error: applied skill_level to Equipment,” “undefined symbol harness,” “syntax error in rule R3” — and these go back into the prompt for up to two more attempts. That’s what takes OSHA compile errors from 14.6% to 0%.
Architecture & data flow
flowchart LR
X["Input x<br/>(question + optional image)"] --> G["LLM Generator G<br/>(frozen, prompted)"]
G --> L["JSON DSL program L<br/>sorts / functions / facts<br/>rules / verifications"]
L --> I["Interpreter I<br/>parse, type-check,<br/>build FOL formula ϕ"]
I -->|"type / syntax error"| FB["Diagnostic message"]
FB -->|"retry (≤2x)"| G
I --> T["Z3 Theorem Prover T"]
T --> R["SAT / UNSAT<br/>+ proof or counterexample"]
R --> OUT["Verified answer<br/>+ auditable logic chain"]
Schematic of the PoT pipeline. Click "Inject error" to see how a type/syntax failure routes the diagnostic back to the LLM for a retry — the loop that drove OSHA compile errors to 0%.
The algorithm, simplified
The contribution isn’t a fancy loss or search — it’s this orchestration loop. Here it is in ~30 lines:
# PoT: turn a question into a *provable* answer via LLM -> JSON DSL -> Z3.
# Stubs: llm(prompt)->str (a frozen model call); z3_check(formula)->"sat"|"unsat".
def proof_of_thought(question, image=None, max_tries=3):
diagnostics = "" # interpreter errors from the last attempt
for attempt in range(max_tries): # paper uses 3: initial + 2 retries
# 1) NEURAL step: LLM proposes facts + rules as JSON DSL.
# On retries, we paste the previous error so it can self-correct.
dsl_json = llm(build_prompt(question, image, fix_hint=diagnostics))
# 2) SYMBOLIC compile: deterministic interpreter, NOT the model.
try:
program = parse_json(dsl_json)
check_sorts(program) # type safety: reject Person-as-Equipment etc.
formula = to_first_order_logic(program) # build Z3 sorts/funcs/asserts/goal
except InterpreterError as e:
diagnostics = e.message # e.g. "undefined symbol 'harness'"
continue # feed the error back, try again
# 3) DECIDE: Z3 is exact — no hallucination possible here.
result = z3_check(formula) # "sat" => True, "unsat" => False
answer = (result == "sat") # convention used in the verifications
return answer, program, result # program IS the auditable reasoning chain
return None, None, "failed_to_compile" # ~17.6% on StrategyQA hit this
The thing to internalize: deduction never lives in the LLM. The model’s only job is to populate a schema; correctness of inference is outsourced to Z3. That’s the whole trick.
Built on Prior Work
PoT sits at the junction of neurosymbolic AI and LLM reasoning.
| Prior idea | What it gave | What this paper changes |
|---|---|---|
| KBANN / C-ILP (Towell, d’Avila Garcez) | Encoding logic rules into neural nets | Keeps logic outside the net, in a prover — no scalability/expressiveness ceiling |
| DeepProbLog, Scallop | Differentiable probabilistic logic, end-to-end trainable | No training at all; uses a frozen LLM + symbolic prover, so it works in low-data domains |
| Chain/Tree/Graph of Thoughts | Better reasoning via more/searched NL steps | Replaces unverifiable NL with formal logic that yields a proof, not a vote |
| Visual Programming (Gupta & Kembhavi) | LLM emits a program for visual reasoning, no training | Generalizes the “emit a program” idea to typed FOL + theorem proving for verification |
| LLM code generation (Codex, AlphaCode) | LLMs reliably emit structured, valid code | Treats DSL generation as a code-gen task, leveraging structured-output guarantees |
The intellectual lineage: take “LLM emits a program” (visual programming, code-gen), aim it at “logic a prover can check” (neurosymbolic), and make the program a typed, human-readable JSON so non-experts can audit it. Z3 itself is off-the-shelf — the contribution is the interface and the loop around it.
Results & Evidence
StrategyQA (1000 multi-hop boolean questions like “Did Aristotle use a laptop?”):
- 82.4% of programs compiled and ran successfully with the 3-step feedback loop (a big jump over fewer loops).
- 91.4% recall, 58.2% precision, 71.1% F1.
- High false-positive rate (53.98%) — the system over-predicts True.
- 17.6% of questions never compiled.
Reddit-OSHA (103 curated images of hazardous workplace scenes — long-tail, low-data, exactly the regime where you can’t train a specialist):
- Feedback loop cut compile errors from 14.6% → 0%.
- Win rate on compiled programs rose 72% → 81.55%.
- The CoT/ToT/GoT baselines scored 99–100% win rates here.
Be honest about what this establishes — and what it doesn’t:
- The accuracy numbers are not a selling point. PaLM-2 with CoT+SC hits 90.2% on StrategyQA; on OSHA the prompting baselines basically max out at 100%. PoT loses on raw accuracy.
- The 53.98% false-positive rate and 58% precision are genuinely weak — the system says “True” too eagerly. For a safety/compliance use case (where False = “violation present”), over-predicting positives might actually be the safe failure direction, but it’s still poor discrimination.
- OSHA n=103 is tiny; treat its numbers as directional, not definitive.
- The real, defensible claim is interpretability + verifiability: every answer ships with a typed logic program you can read and a SAT/UNSAT proof. That’s the deliverable, not the leaderboard.
- No ablation isolating which piece (sorts? rules separation? feedback?) carries the weight — the feedback loop is shown to matter, but the type system’s contribution is asserted, not measured.
How You’d Use It
For an AI services company, PoT is a pattern for “verifiable agent decisions” — a premium tier above ordinary LLM automation.
- Compliance & safety inspection (the paper’s sweet spot). A client uploads site photos or incident reports; the LLM extracts facts (worker at height, no harness) and you keep a fixed, audited rule base (OSHA/ISO clauses encoded once as DSL
rules). Z3 produces a pass/fail with the cited rule and the counterexample. That’s an auditable compliance report, not a vibe — sellable to regulated industries that can’t accept “the model said so.” - Policy / eligibility / contract checks. Insurance eligibility, loan-covenant checks, benefits qualification: encode the policy as rules once, let the LLM populate the facts per case, and get a provable yes/no plus the reason. The rule base is your moat and your audit trail.
- A verification layer in a multi-agent system. You’ve built MAS before — drop PoT in as a “verifier agent” that other agents must pass their conclusions through. Instead of an LLM-judge (another fallible model), you get a deterministic gate that rejects logically inconsistent plans and hands back a counterexample the planning agent can use to revise. This is a much stronger guardrail than another prompt.
- Explainability deliverable. The JSON program is the explanation. For clients who need “show your work” (healthcare, legal, finance), the DSL doubles as documentation and as a human-editable knowledge base the client’s domain experts can correct directly.
Where it slots in: anywhere you currently have an LLM making a binary or constraint-satisfaction decision that someone downstream has to trust or audit. The effort to stand up a narrow vertical (one rule domain) is genuinely small — see below.
Build Your Own (Minimal Recipe)
You can get ~80% of the value in a weekend for one narrow domain.
Components, in build order:
- Pick a constrained question type. Boolean or satisfiability questions in one domain (e.g. “is this worker compliant?”). Don’t try to be general on day one.
- Define a minimal DSL schema. Start with just
sorts,functions,constants,knowledge_base,rules,verifications. Skip optimization. Write it as a JSON Schema and use structured outputs (OpenAIresponse_format/ function-calling) so the LLM must return valid shape — this kills a whole class of errors for free. - Write the interpreter (
pip install z3-solver). Walk the JSON:DeclareSortfor each sort,Function/Constfor functions/constants, assert each KB fact, turn each rule intoForAll([...], Implies(antecedent, consequent)), add the verification as the goal, callsolver.check(). ~150 lines. - Add type checking. Maintain a dict of
symbol -> sort; before building a term, confirm argument sorts match the function’s domain. Raise a clear error string on mismatch. - Add the feedback loop.
try/exceptaround parse + compile; on failure, re-prompt with the error message appended. Cap at 3 tries. This single feature is where the paper’s wins come from.
The two genuinely hard parts:
- Faithful NL→DSL extraction. Getting the LLM to surface the right facts and rules (and not invent convenient ones) is the real bottleneck — the 17.6% non-compile and the false-positive rate both trace back here. Few-shot examples in the domain help a lot.
- Parsing free-form logic strings safely. The DSL embeds expressions like
"And(StandingOn(p, pallet), Not(Using(p, harness)))". You need a small, safe expression parser that maps those onto Z3 calls — do noteval()model output. A tiny recursive-descent parser over a fixed grammar ofAnd/Or/Not/Implies/ForAll/Exists/==/>=is the move.
Reach for: z3-solver (the prover), any structured-output LLM (GPT-4o / Gemini with JSON schema), and lark or a hand-rolled parser for the embedded expressions.
How to Improve It
Limitations are leverage — here’s where you’d push past the paper:
- Fix the precision problem with abstention. The 54% false-positive rate suggests the LLM asserts facts it shouldn’t. Add a confidence/abstention mechanism: have the LLM tag low-confidence facts, and either treat them as unknown (3-valued logic) or run a “one-vs-all” check (verify both the property and its negation; if both are UNSAT or both SAT, return “uncertain” instead of guessing). The authors themselves flag one-vs-all as future work.
- Self-consistency over programs, not text. Generate k DSL programs, run all through Z3, and vote on the verified outcome. Unlike CoT-SC voting over prose, here each vote is already logically sound — you’re only averaging out the LLM’s fact-extraction noise, which is exactly the remaining error source.
- Fine-tune or RL on synthetic DSL. Generate large numbers of (question → correct, compilable DSL) pairs and supervise-fine-tune a smaller model, or use RL where the reward is “Z3 compiled and matched the gold answer” — a clean, programmatic reward signal (no human labels). The authors call this a path to scalable “System 2” thinking; the reward design is the appealing part: the prover is the verifier, so you get cheap, exact rewards.
- Go beyond boolean. Lean into the
optimizationsection (Z3 supports it) and non-boolean answers — scheduling, resource allocation, constraint problems — where symbolic guarantees are worth far more than on yes/no trivia, and where pure LLMs are genuinely bad. - Decouple and version the rule base. In production, the
rulesshouldn’t be re-generated per query — they’re your audited domain logic. Persist a curated, version-controlled rule library and let the LLM only generateknowledge_basefacts from the input. This both improves consistency and turns the rules into a maintainable, sellable asset.
Glossary
- Neurosymbolic — combining neural networks (LLMs) with symbolic logic/reasoning systems; here, LLM for proposing, prover for deciding.
- First-Order Logic (FOL) — logic with predicates, functions, and quantifiers (
∀,∃); expressive enough for “for all workers at height, harness required.” - Theorem prover — software that mechanically checks whether a logical formula is provable/satisfiable; exact, no guessing.
- Z3 — Microsoft’s SMT (Satisfiability Modulo Theories) solver; the prover used here.
pip install z3-solver. - SAT / UNSAT — “satisfiable” (a model exists making it true) / “unsatisfiable” (none exists). PoT maps these to True/False answers.
- DSL (Domain-Specific Language) — a small custom language for one purpose; here a JSON format for expressing logic problems.
- Sort — a type in the logic (Person, Equipment, Int); enables type-safe reasoning over human concepts.
- Predicate — a function returning Bool — a yes/no fact, e.g.
Wearing(worker, hardHat). - Quantifier —
ForAll(∀) /Exists(∃); lets rules apply to whole classes, not just named individuals. - Knowledge base (KB) — the set of facts/axioms assumed true at the start of reasoning.
- Chain-of-Thought (CoT) — prompting the LLM to produce intermediate reasoning steps in natural language.
- Self-Consistency (CoT-SC) — sampling many CoT chains and majority-voting the answer.
- Tree / Graph of Thoughts (ToT/GoT) — searching over branching/networked reasoning paths instead of one chain.
- Structured outputs — provider feature (OpenAI/Google) that forces model output to match a JSON schema; PoT relies on this.
- Counterexample — a concrete assignment showing a property fails; what Z3 returns on UNSAT, invaluable for debugging the reasoning.
- StrategyQA — benchmark of implicit multi-hop yes/no questions requiring unstated reasoning steps.
- Feedback loop — re-prompting the LLM with interpreter/prover error messages so it self-corrects (here: up to 2 retries).