Self-consistency: sample several paths, take the majority
Ask the same question N times at a temperature high enough to produce genuinely different reasoning, then take the answer the paths converge on. The vote is plain code, not another model call, because asking a model to pick the best of N restores the single point of failure the sampling removed.
Key Facts
- Level
- intermediate
- Runtime
- Python • OpenAI API
- Pattern
- Inspectable flow with visible system boundaries
- Interaction
- Live sandbox • Script
- Updated
- 27 July 2026
Navigate this example
Pattern
Doctrine relationsWhich principles this pattern expresses in code, and which its default form breaks.Library
Browse examplesReopen the wider library to compare adjacent patterns and linked learning paths.Interaction
Run sandbox nowTry the interaction directly in this example’s guided sandbox surface.Source
Open full sourceRead the real implementation, highlighted checkpoints, and runtime requirements.MCP
Call via MCPUse the same resource inside agents, deterministic exports, and MCP setup flows.
Linked principles
Doctrine relations
These are reasoning shapes: how an agent decides, when it acts, what it remembers. For how to compose the interface around it, see the AI Pattern Blueprint. For triggers, scheduling, context loading and recovery, see Agent runtime architecture.
This pattern expresses these principles directly in code.
P6 · Expose meaningful operational state, not internal complexity
The vote distribution IS the operational state, and returning it is what separates this pattern from an expensive single call. Five of five and three of five are different situations that a bare answer cannot distinguish. This implementation returns the count alongside the answer and escalates explicitly when the majority is thin or absent.
self_consistency.py:run
The textbook form of this pattern violates these by default. Each one carries the fix.
P2 · Ensure that background work remains perceptible
N samples means N times the latency, and by default nothing is emitted until all of them land. Fix, applied here: emit each sample as it completes so a five-sample run reads as progress rather than as one very slow call.
self_consistency.py:runP5 · Replace implied magic with clear mental models
The reader sees one answer and has no way to know it was contested, or that four other paths reached something else. That is a false impression of certainty, produced by a technique adopted specifically to handle uncertainty. Fix: carry the agreement count through to wherever the answer is displayed, rather than discarding it at the boundary of this function.
self_consistency.py:run
Your implementation decides these, not the pattern. No verdict is asserted.
P7 · Establish trust through inspectability
Each path produces its reasoning, so the derivation exists. Whether the dissenting paths are ever shown, which is where the interesting information usually is, is a wrapper decision. The pattern neither surfaces nor hides them.
self_consistency.py:Attempt
Doctrine relations are first-party analysis, not the output of a validation run. Run the validator on your own code to get a scored verdict.
self_consistency.py
"""
Self-consistency: sample several reasoning paths, take the majority
Ask the same question N times at a temperature high enough to get genuinely
different reasoning, then take the answer the paths agree on. Errors tend to
be idiosyncratic and scatter; correct answers tend to converge.
Question -> N independent paths -> normalise -> majority answer
The vote is plain Python, deliberately. Asking a model to pick the best of N
answers reintroduces exactly the single-point-of-failure the sampling was
meant to remove, and it lets a confidently-worded wrong answer win.
The agreement count is the useful output, not a decoration. Five of five is a
different situation from three of five, and a caller that only reads the
answer has thrown away the reason to have run this at all.
Run: python self_consistency.py
"""
from collections import Counter
from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, Field
load_dotenv()
client = OpenAI()
MODEL = "gpt-5.4-mini"
SAMPLES = 5
#: Enough spread to get different reasoning. At 0 every path is the same path
#: and the vote is theatre.
TEMPERATURE = 1.0
class Attempt(BaseModel):
reasoning: str = Field(description="Work the problem through, step by step.")
answer: str = Field(
description="The final answer alone. No units, no restatement, no prose."
)
def attempt(question: str) -> Attempt:
completion = client.beta.chat.completions.parse(
model=MODEL,
temperature=TEMPERATURE,
messages=[
{
"role": "system",
"content": (
"Work the problem through and give the final answer. Put "
"only the answer itself in `answer`, nothing else."
),
},
{"role": "user", "content": question},
],
response_format=Attempt,
)
return completion.choices[0].message.parsed
def normalise(answer: str) -> str:
"""Group answers that are the same fact written differently.
Without this the vote splits on formatting and everything ties at one
each, which looks like disagreement when the paths actually agreed.
"""
return answer.strip().lower().rstrip(".").replace(",", "").replace("$", "")
def run(question: str) -> tuple[str, int, int]:
attempts: list[Attempt] = []
for index in range(1, SAMPLES + 1):
print(f" [{index}/{SAMPLES}] sampling...")
current = attempt(question)
print(f" answer: {current.answer}")
attempts.append(current)
counts = Counter(normalise(a.answer) for a in attempts)
winner, votes = counts.most_common(1)[0]
# Surface the spread, not just the winner. Agreement IS the signal.
print(f" distribution: {dict(counts)}")
print(f" majority : {winner} ({votes}/{SAMPLES})")
if votes == 1:
print(" WARNING: every path disagreed. Treat this as no answer.")
elif votes <= SAMPLES // 2:
print(" WARNING: the majority is thin. Escalate rather than rely on it.")
return winner, votes, SAMPLES
if __name__ == "__main__":
question = (
"A team of 4 engineers takes 6 days to migrate 120 services. Two more "
"engineers join at the same rate. How many days for the next 120?"
)
print(f"\n> {question}")
run(question)
Related principles
- P2visibilityEnsure that background work remains perceptibleWhen the system is operating asynchronously or outside the user’s immediate focus, it should provide persistent and proportionate signals that work is continuing.Open principle →
- P5delegationReplace implied magic with clear mental modelsThe product should help users understand what the system can do, what it is currently doing, what it cannot do, and what conditions govern its behaviour.Open principle →
- P6visibilityExpose meaningful operational state, not internal complexityPresent the state of the system in language and structures that are relevant to the user, rather than exposing low-level internals that do not support action or understanding.Open principle →
- P7trustEstablish trust through inspectabilityUsers should be able to examine how a result was produced when confidence, accountability, or decision quality is important.Open principle →