The Problem Nobody Talks About in AI On-Chain
Say you write a smart contract that asks an LLM, "Is this tweet positive or negative?" You deploy it across a network of validators, each running the same code. On a normal blockchain, every node must produce a byte-for-byte identical result, or the chain halts.
But LLMs don't work that way. Ask the same question twice and you might get "Positive." once and "This is positive." the next. Fetch a web page from two machines and the ads, timestamps, or ordering shift. Both answers are correct, but they aren't identical.
This is the core tension GenLayer has to solve: how do you get a decentralized network to agree on the output of something that is fundamentally non-deterministic? The answer is the Equivalence Principle.
The Mental Model: Leader Proposes, Validators Judge
Instead of demanding everyone compute the same exact bytes, GenLayer splits the work into two roles:
Leader → runs the non-deterministic operation, proposes a result
Validators → independently check whether that result is acceptable
The leader does the heavy lifting once — it calls the LLM, fetches the page, whatever the contract needs. It then proposes its answer. Each validator independently decides whether the leader's answer is reasonable. If enough validators agree it's acceptable, consensus passes and the result is written to the chain.
Notice the shift in question. We're no longer asking "did everyone get the same bytes?" We're asking "is the leader's answer good enough that honest validators accept it?" That single reframing is what makes AI-native smart contracts possible.
The Key Decision: Can Validators Reproduce the Output?
Every time you use a non-deterministic operation, you have to tell GenLayer how validators should judge the leader. The deciding question is simple:
Can validators reproduce the exact same normalized output?
-
Yes → use
strict_eq(strict equality) - No → write a custom validator that judges with tolerance or reasoning
Let's walk through both paths.
Path 1: Strict Equality (strict_eq)
Use strict_eq when the output is deterministic or can be canonicalized into a stable form. Think blockchain RPC responses, stable REST APIs, or JSON you can sort with sort_keys=True. The validators rerun the operation and require an exact match against the leader.
# { "Depends": "py-genlayer:test" }
from genlayer import *
class BlockHeightOracle(gl.Contract):
height: u256
def __init__(self):
self.height = u256(0)
@gl.public.write
def refresh(self):
def fetch_height() -> str:
page = gl.nondet.web.render(
"https://some-stable-rpc.example/height",
mode="text",
)
return page.strip()
result = gl.eq_principle.strict_eq(fetch_height)
self.height = u256(int(result))
@gl.public.view
def current(self) -> u256:
return self.height
Here, every validator hits the same stable endpoint and expects the same number. If the leader says 19342201, validators that get anything else reject the result. Exact match is the right tool because the data is genuinely reproducible.
Path 2: When Exact Match Is Impossible
Now back to our LLM sentiment example. No two model calls will match byte-for-byte, so strict_eq would reject perfectly good answers. GenLayer ships two convenience helpers built for fuzzy outputs:
| Helper | What validators do |
|---|---|
prompt_comparative |
An LLM compares the leader's output to the validator's own and decides if they mean the same thing |
prompt_non_comparative |
Each validator evaluates the leader's output against a rule, without rerunning the task |
The difference is subtle but important. Comparative validation reruns the task and asks "are these two answers equivalent?" Non-comparative validation doesn't rerun anything — it just checks "does the leader's answer satisfy this criterion?"
A Practical Example: Sentiment with Comparative Validation
# { "Depends": "py-genlayer:test" }
from genlayer import *
class SentimentTagger(gl.Contract):
last_label: str
def __init__(self):
self.last_label = ""
@gl.public.write
def tag(self, text: str):
def analyze() -> str:
prompt = f"""Classify the sentiment of the text below.
Respond with exactly one word: Positive, Negative, or Neutral.
Text: {text}"""
return gl.nondet.llm.call(prompt).strip()
# Validators use an LLM to judge if their result
# "means the same" as the leader's, instead of exact match.
self.last_label = gl.eq_principle.prompt_comparative(
analyze,
task="Classify text sentiment as Positive, Negative, or Neutral.",
)
@gl.public.view
def result(self) -> str:
return self.last_label
Even if the leader returns Positive and a validator's own run returns positive sentiment, the comparative LLM judge can recognize these as equivalent and accept the result. That tolerance is exactly what you want for natural-language output.
When to Reach for a Custom Validator
The convenience helpers cover most cases, but sometimes you need full control. If your output is a number that should match within a tolerance (a price oracle that accepts ±0.5%), or a complex object where only a few fields are stable, you write a custom validator function. There you control the entire logic: rerun and compare with tolerances, extract stable fields, derive a status, or evaluate the leader's output directly without rerunning at all.
The rule of thumb:
- Reproducible exact output →
strict_eq - Natural language that "means the same" →
prompt_comparative - Leader output that must satisfy a rule →
prompt_non_comparative - Anything needing custom tolerance or field logic → write your own validator
Key Takeaways
- Non-determinism is the whole point. GenLayer embraces LLM calls and web reads instead of banning them, and the Equivalence Principle is the mechanism that makes them safe.
- Leader proposes, validators judge. The leader runs the operation once; validators independently decide whether the result is acceptable.
-
Pick your validation method by reproducibility. If validators can reproduce the exact output, use
strict_eq. If not, use an LLM-based or custom validator. -
prompt_comparativevsprompt_non_comparative. Comparative reruns the task and checks equivalence; non-comparative checks the leader's output against a criterion without rerunning. - Custom validators give you full control for numeric tolerances, partial field matching, and other real-world fuzziness.
Next Steps
The fastest way to feel this click is to deploy a contract and watch validators vote. Head to GenLayer Studio — a zero-setup web IDE where you can write an Intelligent Contract, deploy it, and inspect each validator's decision in your browser. No Docker, no local install.
When you're ready for a full local workflow, read The Equivalence Principle in the official docs and grab the GenLayer Project Boilerplate to start building for real.
Top comments (0)