DEV Community

Programming Central
Programming Central

Posted on

Stop Guessing, Start Proving: Eradicating LLM Hallucinations with Schema-Driven Fact Verification and TypeScript

We’ve all been there. You build a sleek, production-ready Generative AI application. You prompt your Large Language Model (LLM) with careful system instructions, hook it up to a vector database, and deploy it to production. For a few days, it’s magic. It summarizes PDFs, answers user queries with frightening eloquence, and writes clean boilerplate code.

Then, disaster strikes. A high-value enterprise client asks a routine question about your software tiers. The LLM responds with absolute, unwavering confidence—and completely hallucinates a non-existent feature combination. It tells the client that your most expensive enterprise security feature is included in the free tier.

Welcome to the Epistemic Crisis of Generative Models.

As software engineers, systems architects, and technical leaders, we are building mission-critical architectures on top of probabilistic engines. LLMs do not "know" things; they sample from high-dimensional probability distributions, optimizing for linguistic plausibility rather than objective truth. When you ask an LLM a question, it is essentially running a high-stakes game of autocomplete.

In earlier architectural phases of Neuro-Symbolic AI, we relied heavily on vector-based Retrieval-Augmented Generation (RAG) to ground LLM outputs. But vector similarity search operates purely on semantic proximity, not logical entailment. If your vector database retrieves contradictory, outdated, or subtly false information, your downstream LLM will synthesize those flaws into a fluent, authoritative-sounding falsehood.

To eliminate hallucinations entirely, we must pivot from probabilistic association to deterministic verification. We need an architectural paradigm where generative outputs are intercepted, parsed into atomic logical predicates, mapped to formal domain ontologies, and evaluated against a structured Knowledge Graph using rigorous graph theory and formal logic.

In this deep dive, we will explore Schema-Driven Fact Verification—a production-grade methodology for trapping LLM hallucinations before they ever touch your business logic, complete with a practical TypeScript implementation.


The Web Development Analogy: API Gateways and Strong Typing

To understand why schema-driven fact verification is necessary for Large Language Models, we can draw a direct parallel to the evolution of modern web application development. Think about the transition from loosely typed, dynamic front-ends communicating with unpredictable back-ends to strictly typed, contract-first architectures.

Imagine an enterprise web application where the front-end is written in vanilla JavaScript and communicates with a legacy microservice architecture returning raw, unvalidated JSON strings. In this legacy setup, the front-end developer reads documentation, assumes the shape of the user payload ({ id: string, email: string, roles: string[] }), and writes UI code rendering the user profile.

One day, a backend developer updates the service to return { userId: number, mailAddress: string, userRoles: object[] } without updating the documentation. The front-end application does not throw a compile-time error because JavaScript is dynamically typed. Instead, it silently fails at runtime: user.email evaluates to undefined, the UI breaks, and the user encounters a blank screen or a cryptic runtime exception in production.

Now, contrast this with a modern, enterprise TypeScript stack utilizing Zod schemas and an API Gateway enforcing strict interface contracts. Here, every incoming payload from an external service must pass through a rigorous runtime validation barrier. If the incoming JSON does not precisely match the compiled Zod schema, the gateway intercepts the payload, rejects it, and logs a validation error before it ever touches the business logic layer.

In this web development analogy:

  • The LLM is the unpredictable legacy backend. It generates arbitrary text structures based on statistical weights, often omitting fields, changing terminology, or hallucinating entities that do not exist in reality.
  • The Zod Schema acts as our strict structural contract, transforming unstructured or semi-structured model outputs into fully typed TypeScript objects at runtime.
  • The Knowledge Graph is our source-of-truth database, ensuring that even if a payload passes structural validation, its semantic assertions are cross-referenced against absolute historical and ontological facts.

The Anatomy of a Knowledge Graph and Ontological Alignment

A Knowledge Graph (KG) is a directed labeled graph where nodes represent entities (concepts, objects, individuals) and edges represent semantic relationships between those entities. Formally, a Knowledge Graph is defined as a tuple G=(V,E,R)\mathcal{G} = (\mathcal{V}, \mathcal{E}, \mathcal{R}) , where V\mathcal{V} is the set of vertices (entities), R\mathcal{R} is the set of relation types, and EV×R×V\mathcal{E} \subseteq \mathcal{V} \times \mathcal{R} \times \mathcal{V} is the set of directed edges connecting entities via specific relations.

In the context of Schema-Driven Fact Verification, raw LLM outputs cannot be directly compared against G\mathcal{G} because natural language is inherently ambiguous, polysemous, and prone to synonymy. An LLM might state, "The CEO of Acme Corp acquired a major stake in Beta LLC on Tuesday." A Knowledge Graph, however, stores precise ontological triples: (AcmeCorp, hasExecutive, JohnDoe), (JohnDoe, holdsPosition, ChiefExecutiveOfficer), and (AcmeCorp, investedIn, BetaLLC).

To bridge this semantic gap, we rely on Ontologies. An ontology is a formal specification of a conceptualization—a shared vocabulary that defines the types of entities that exist, the properties they possess, and the permitted relationships between them. When we apply model compression techniques (such as quantization or pruning) to deploy smaller, faster LLMs on edge nodes or local inference servers, these resource-constrained models are even more prone to hallucination due to reduced parameter capacities. Consequently, the ontology acts as an immutable external anchor, preventing the compressed model from drifting into semantic incoherence.


The Delegation Strategy: Supervisor Nodes and Worker Agents

When architecting a deterministic verification pipeline, a monolithic script that passes text directly from an LLM to a database query will invariably fail due to the complexity of natural language parsing. Instead, we must employ a Delegation Strategy within a multi-agent or modular software pipeline.

A Delegation Strategy is a specific architectural pattern where a Supervisor Node orchestrates the verification workflow by breaking down an unstructured LLM generation into discrete sub-tasks, delegating those sub-tasks to specialized Worker Agents, and enforcing strict structural boundaries using JSON schemas and Zod validation.

  1. The Ingestion & Extraction Worker: Responsible for parsing raw text and extracting atomic propositions (Subject-Predicate-Object triples).
  2. The Schema Validation Worker: Responsible for enforcing runtime type safety, transforming raw extractions into strictly typed objects using Zod schemas.
  3. The Graph Query Worker: Responsible for translating typed objects into deterministic graph database queries (e.g., Cypher or SPARQL) and executing them against the GraphDB.
  4. The Supervisor Decision Node: Evaluates the Boolean result of the graph query and applies the zero-hallucination guardrail policy (accepting, correcting, or rejecting the LLM inference).

By decoupling these responsibilities, the system achieves modularity and testability. If the extraction worker produces malformed entities, the Zod validation layer catches the anomaly immediately, preventing corrupt data from ever querying the underlying Knowledge Graph.


Theoretical Limits of Probabilistic vs. Deterministic Systems

To fully grasp the necessity of Schema-Driven Fact Verification, one must analyze the epistemological divergence between probabilistic generation and deterministic verification.

An LLM functions via statistical pattern matching. It does not "know" that Paris is the capital of France in the logical sense; rather, it knows that the token "Paris" has an exceptionally high co-occurrence probability with the tokens "capital", "France", and "is". Because this mechanism is probabilistic, it exhibits an irreducible baseline entropy. No matter how large the model, how rigorous the reinforcement learning from human feedback (RLHF), or how sophisticated the prompting technique, an LLM will occasionally generate statistically probable falsehoods that mimic truth.

Conversely, a Knowledge Graph operating on formal logic (such as Description Logics or first-order predicate calculus) is strictly deterministic. A query evaluating whether (Paris, isCapitalOf, France) evaluates to true or false based entirely on explicit, stored edges and deductive inference rules. There is no probability, no temperature setting, and no stylistic variation.

Schema-Driven Fact Verification acts as the translation membrane between these two fundamentally incompatible paradigms. It accepts the generative fluidity of the probabilistic layer for natural language comprehension and synthesis, but forces every output through a narrow, strictly typed, deterministic bottleneck before allowing downstream execution.


Deep Dive: The Mechanics of Zero-Hallucination Guardrails

A zero-hallucination guardrail is not a filter that catches errors after they damage a system; it is an active, blocking architectural boundary. When designing such a guardrail in TypeScript, developers must consider three distinct operational phases: Interception, Validation, and Resolution.

1. Interception

The guardrail must sit directly in the execution path between the LLM inference engine and the business logic execution environment. Whether the LLM is generating SQL queries, API payloads, financial transactions, or automated code, the output must be captured as a raw string before execution.

2. Validation

Using declarative libraries like Zod, the raw string (parsed from JSON or extracted via structured outputs) is validated against a strict schema. This schema enforces not only primitive types (strings, numbers, booleans) but also domain-specific constraints:

  • Enumerated relationship types that correspond strictly to the edges defined in the Knowledge Graph ontology.
  • UUID formats for entity identifiers to prevent shadow entities from being invented by the model.
  • Bounded confidence scores, requiring the model to explicitly state its internal uncertainty, which the guardrail can use to trigger secondary verification paths.

3. Resolution

Once a claim is validated structurally, the guardrail queries the Knowledge Graph. Depending on the query result, the guardrail executes one of three resolution strategies:

  • Accept: The Knowledge Graph confirms the triple exists ( eE\exists e \in \mathcal{E} ), and the downstream process proceeds safely.
  • Correct: The Knowledge Graph returns a partial match or an outdated entity ID, allowing the guardrail to automatically substitute the canonical identifier from the graph.
  • Reject: The Knowledge Graph explicitly disproves the claim ( ¬eE\neg \exists e \in \mathcal{E} ), or the Zod validation fails. The LLM output is discarded, and an error or fallback response is returned.

Through this multi-layered theoretical framework, developers transition from hoping an LLM tells the truth to mathematically proving it. By combining strict schema validation in TypeScript with deterministic Knowledge Graph traversals, applications achieve absolute factual grounding, eliminating hallucinations at scale.


Putting It Into Practice: Building a Zero-Hallucination Guardrail in TypeScript

To ground these concepts in a practical implementation, let’s build a production-grade TypeScript program demonstrating a zero-hallucination guardrail for a SaaS customer support analytics platform. In this architecture, an LLM extracts product feature claims from unstructured user feedback, but its output is intercepted and validated at runtime using a Zod schema and cross-checked against an in-memory Knowledge Graph simulator. If a claim violates the structural ontology or references a non-existent entity, it is caught and rejected before execution.

import { z } from 'zod';

/**
 * @file FactVerificationPipeline.ts
 * @description Implements a schema-driven fact verification guardrail in TypeScript.
 * Intercepts LLM extraction outputs, validates via Zod, and queries a deterministic
 * Knowledge Graph to eradicate hallucinations in a SaaS analytics context.
 */

// ==========================================
// 1. ONTOLOGY & KNOWLEDGE GRAPH DEFINITION
// ==========================================

interface KGNode {
    id: string;
    label: string;
    properties: Record<string, string | number | boolean>;
}

interface KGEdge {
    source: string;
    target: string;
    relation: string;
}

class KnowledgeGraphDB {
    private nodes: Map<string, KGNode> = new Map();
    private edges: KGEdge[] = [];

    public addNode(node: KGNode): void {
        this.nodes.set(node.id, node);
    }

    public addEdge(edge: KGEdge): void {
        this.edges.push(edge);
    }

    /**
     * Deterministically checks if a specific relation exists between two entities.
     */
    public verifyFact(sourceId: string, relation: string, targetId: string): boolean {
        if (!this.nodes.has(sourceId) || !this.nodes.has(targetId)) {
            return false;
        }

        return this.edges.some(
            (edge) =>
                edge.source === sourceId &&
                edge.relation === relation &&
                edge.target === targetId
        );
    }
}

// Initialize and seed our GraphDB with known product truths
const productKG = new KnowledgeGraphDB();

productKG.addNode({ id: 'feature_sso', label: 'Feature', properties: { name: 'Single Sign-On', tier: 'Enterprise' } });
productKG.addNode({ id: 'tier_enterprise', label: 'PricingTier', properties: { name: 'Enterprise' } });
productKG.addNode({ id: 'feature_csv_export', label: 'Feature', properties: { name: 'CSV Export', tier: 'Pro' } });
productKG.addNode({ id: 'tier_pro', label: 'PricingTier', properties: { name: 'Pro' } });

productKG.addEdge({ source: 'feature_sso', relation: 'BELONGS_TO_TIER', target: 'tier_enterprise' });
productKG.addEdge({ source: 'feature_csv_export', relation: 'BELONGS_TO_TIER', target: 'tier_pro' });

// ==========================================
// 2. RUNTIME SCHEMA VALIDATION (ZOD)
// ==========================================

const ClaimExtractionSchema = z.object({
    subjectId: z.string().min(1, { message: "Subject ID cannot be empty" }),
    relation: z.string().min(1, { message: "Relation predicate cannot be empty" }),
    targetId: z.string().min(1, { message: "Target ID cannot be empty" }),
    rawStatement: z.string(),
    confidenceScore: z.number().min(0).max(1),
});

type ExtractedClaim = z.infer<typeof ClaimExtractionSchema>;

// ==========================================
// 3. ZERO-HALLUCINATION GUARDRAIL PIPELINE
// ==========================================

function simulateLLMExtraction(feedback: string): unknown {
    if (feedback.includes('SSO')) {
        return {
            subjectId: 'feature_sso',
            relation: 'BELONGS_TO_TIER',
            targetId: 'tier_enterprise',
            rawStatement: 'SSO is included in the Enterprise plan.',
            confidenceScore: 0.98,
        };
    } else {
        return {
            subjectId: 'feature_sso',
            relation: 'BELONGS_TO_TIER',
            targetId: 'tier_pro', // Hallucination: SSO does not belong to Pro!
            rawStatement: 'SSO is included in the Pro tier.',
            confidenceScore: 0.85,
        };
    }
}

function processCustomerFeedbackPipeline(customerFeedback: string): void {
    console.log(`\n----------------------------------------`);
    console.log(`Processing Feedback: "${customerFeedback}"`);
    console.log(`----------------------------------------`);

    // Step 1: Obtain raw generation from LLM
    const rawLLMOutput = simulateLLMExtraction(customerFeedback);

    // Step 2: Validate structural integrity using Zod
    const validationResult = ClaimExtractionSchema.safeParse(rawLLMOutput);

    if (!validationResult.success) {
        console.error(`[Guardrail REJECTED] Structural schema validation failed:`, validationResult.error.format());
        return;
    }

    const claim: ExtractedClaim = validationResult.data;
    console.log(`[Validation PASSED] Structural schema verified for claim:`, claim.rawStatement);

    // Step 3: Cross-check claims against the deterministic Knowledge Graph
    const isFactuallyCorrect = productKG.verifyFact(
        claim.subjectId,
        claim.relation,
        claim.targetId
    );

    // Step 4: Enforce Zero-Hallucination Guardrail Decision
    if (isFactuallyCorrect) {
        console.log(`[Guardrail ACCEPTED] Fact successfully verified against Knowledge Graph.`);
    } else {
        console.error(`[Guardrail HALTED] Hallucination detected! Claim contradicts Knowledge Graph:`);
        console.error(`   Failed Triple: ({% katex inline %}{claim.subjectId}) -[:{% endkatex %}{claim.relation}]-> (${claim.targetId})`);
    }
}

// ==========================================
// 4. EXECUTION DEMONSTRATION
// ==========================================

processCustomerFeedbackPipeline("Can you confirm if SSO is available?");
processCustomerFeedbackPipeline("I heard we can get SSO on the Pro plan right?");
Enter fullscreen mode Exit fullscreen mode

Detailed Breakdown of the Implementation

To master schema-driven fact verification, let’s dissect the code architecture we just built:

  1. The Knowledge Graph Core: We established explicit TypeScript interfaces (KGNode and KGEdge) to model entities and relationships. The KnowledgeGraphDB class acts as our deterministic database layer, exposing an O(1)O(1) node lookup and an edge scan via verifyFact(). This simulates how production Graph databases like Neo4j evaluate graph queries.
  2. Runtime Validation via Zod: The ClaimExtractionSchema forces unstructured LLM extractions to conform to an explicit shape. If an LLM drops a field, introduces an unexpected data type, or injects a malformed identifier, Zod catches it instantly. Furthermore, z.infer synchronizes our runtime validation schema with our compile-time TypeScript types, preventing codebase drift.
  3. The Guardrail Pipeline: processCustomerFeedbackPipeline orchestrates the entire workflow. It treats the LLM as an untrusted external actor, forces its output through the Zod validation gate, and immediately submits the parsed triple to the Knowledge Graph. If the graph returns false, the execution halts, protecting downstream systems from propagating false data.

Conclusion: Moving Beyond Prompt Engineering

Prompt engineering and Retrieval-Augmented Generation have taken us far, but they have hit a structural ceiling. As long as we treat Large Language Models as autonomous arbiters of truth, our production systems will remain vulnerable to unpredictable, plausible-sounding hallucinations.

By combining Schema-Driven Fact Verification, strict runtime validation via Zod schemas, and deterministic graph traversals across an immutable Knowledge Graph, we bridge the gap between probabilistic creativity and mathematical certainty. We stop hoping our models tell the truth, and start proving it.

The concepts and code demonstrated here are drawn directly from the comprehensive roadmap laid out in the book Neuro-Symbolic AI & Knowledge Graphs, you can find it here. Check also the many other ebooks.

Top comments (0)