How SymbolicAI Implements Design by Contract for LLMs: A Deep Dive into the @contract Decorator

SymbolicAI implements Design by Contract for LLMs through the @contract decorator in symai/strategy.py, which wraps large language model calls in a rigorous lifecycle of pre-condition validation, post-condition verification, and self-healing retry logic with exponential back-off.

SymbolicAI is a neuro-symbolic programming framework that bridges traditional software engineering and large language models. By applying Design by Contract for LLMs, the framework treats every LLM interaction as a formal contract with explicit pre-conditions, post-conditions, and automatic recovery mechanisms. This approach ensures that probabilistic model outputs meet deterministic software requirements without sacrificing the flexibility of natural language processing.

The Contract Lifecycle for LLM Operations

When a class inherits from Expression (defined in symai/components.py) and applies the @contract decorator to its forward method, SymbolicAI enforces a strict six-stage lifecycle for every LLM invocation.

Pre-condition Validation

Before the LLM call executes, the decorator checks for an optional pre() method on the expression class. This method validates inputs, reshapes data, or verifies that required context is present. If pre() raises a ContractViolation, the contract lifecycle immediately enters the remedy phase.

The wrapper in symai/strategy.py calls expr.pre() (if present) and catches violations before proceeding to the engine call.

LLM Invocation via Forward

The actual LLM request occurs through the forward method. The decorator forwards arguments to the engine retrieved from symai/backend/engine_repository.py, which returns a (result, metadata) tuple. This stage is transparent to the contract logic, allowing any backend engine to be used without modification.

Engine selection and invocation happen in symai/backend/engine_repository.py, while the wrapper simply orchestrates the call.

Post-condition Verification

After receiving the LLM response, the decorator executes an optional post() method if defined. This method receives the result and can verify its type, shape, or content against expected constraints. Like pre(), a ContractViolation raised here triggers the remedy and retry mechanism.

The @contract wrapper executes post() after receiving the engine response but before returning to the caller.

Remedy and Automatic Retry

If any contract stage fails, SymbolicAI implements a self-healing retry loop. The decorator retries the operation up to 8 times using exponential back-off calculated as base_delay * 2 ** attempt. Users may define a remedy() method that executes between retries to attempt automatic fixes, such as adjusting prompts, sanitizing outputs, or applying fallback logic.

The retry logic lives entirely within the @contract implementation in symai/strategy.py.

Metadata Enrichment

Throughout the lifecycle, the decorator enriches the metadata dictionary with contract-specific telemetry, including timestamps, attempt counts, and error traces. This enables observability and debugging without requiring manual instrumentation of every LLM call.

Implementation Details of the @contract Decorator

The core logic resides in symai/strategy.py, where the @contract decorator is implemented as a higher-order function wrapping Expression subclasses.

ContractContext and State Tracking

The decorator instantiates a ContractContext data class to track the current attempt number and store exception details across retries. This context object ensures state isolation between concurrent contract executions, preventing race conditions when multiple LLM calls run simultaneously.

Exponential Back-off Strategy

Retry delays follow the formula base_delay * 2 ** attempt, with a configurable maximum delay to prevent excessive wait times. This strategy balances quick recovery from transient LLM failures with protection against overwhelming the backend service during outages.

Exception Hierarchy

All contract violations inherit from ContractError, a base exception class that distinguishes between contract failures (which trigger retries) and other runtime errors (which fail fast). This hierarchy allows upstream components to implement fine-grained error handling strategies.

Practical Example: Validating LLM Arithmetic

The following example demonstrates a complete contract implementation for an LLM-powered addition operation:


# example.py

from symai import Symbol, Expression
from symai.strategy import contract

class AddNumbers(Expression):
    """Add two numbers using an LLM prompt."""

    def __init__(self, a: Symbol, b: Symbol):
        self.a = a
        self.b = b

    @contract
    def forward(self):
        # Prompt the LLM to compute a + b

        prompt = f"Add {self.a.value} and {self.b.value} and return the sum as an integer."
        return self.engine.forward(prompt)

    # Optional pre‑condition: ensure inputs are ints

    def pre(self):
        if not isinstance(self.a.value, int) or not isinstance(self.b.value, int):
            raise ValueError("Inputs must be integers")

    # Optional post‑condition: result must be an int and correct

    def post(self, result):
        if not isinstance(result, int):
            raise TypeError("LLM did not return an integer")
        # simple sanity check

        if result != self.a.value + self.b.value:
            raise AssertionError("Incorrect sum returned")

    # Optional remedy: fall back to Python arithmetic on first failure

    def remedy(self, attempt):
        if attempt == 0:
            # Bypass the LLM entirely

            return self.a.value + self.b.value

Usage requires only instantiating the class and calling it:


# usage.py

from symai import Symbol
from example import AddNumbers

x = Symbol(7)
y = Symbol(5)

adder = AddNumbers(x, y)
result, meta = adder()
print("Result:", result)   # → 12

print("Metadata:", meta)   # contains attempt count, timestamps, etc.

The @contract decorator handles all validation, retry logic, and metadata collection without additional boilerplate.

Key Source Files

File Role
symai/strategy.py Implements the @contract decorator, retry logic, and ContractContext.
symai/components.py Defines Expression, the base class for contract-enabled operations.
symai/backend/engine_repository.py Manages LLM engine selection and invocation for contract calls.
symai/models/base.py Provides LLMDataModel for structured output validation in contracts.
symai/ops/ Contains primitive operators that leverage contract safety for LLM calls.

Summary

  • SymbolicAI implements Design by Contract for LLMs through the @contract decorator in symai/strategy.py, wrapping LLM calls in a safety lifecycle.
  • The contract lifecycle includes pre-condition validation, LLM invocation, post-condition verification, and automatic remedy/retry with exponential back-off.
  • Self-healing retries occur up to 8 times using the formula base_delay * 2 ** attempt, with optional custom remedy() logic for automatic recovery.
  • Rich metadata is automatically collected during execution, enabling observability without manual instrumentation.
  • Minimal boilerplate is required: developers simply inherit from Expression and optionally implement pre(), post(), or remedy() methods.

Frequently Asked Questions

What is Design by Contract in the context of LLMs?

Design by Contract (DbC) in SymbolicAI applies traditional software engineering contracts—pre-conditions, post-conditions, and invariants—to large language model interactions. It treats LLM calls as formal operations where inputs must satisfy specific constraints before execution, and outputs must meet validation criteria before being returned to the application. This bridges the gap between deterministic software requirements and probabilistic model outputs.

How does the @contract decorator handle failures?

When a pre() or post() method raises a ContractViolation, or when the LLM call itself fails, the @contract decorator initiates a retry loop. It attempts the operation up to 8 times with exponential back-off delays calculated as base_delay * 2 ** attempt. If a remedy() method is defined, it executes between retries to attempt automatic fixes, such as adjusting prompts or sanitizing malformed outputs. Only after exhausting all retries does the decorator raise a ContractError.

Can I customize the retry behavior in SymbolicAI?

Yes, developers can customize retry behavior through multiple mechanisms. The exponential back-off base delay and maximum retry count (default 8) are configurable parameters in the contract implementation. More importantly, you can override the remedy() method in your Expression subclass to implement domain-specific recovery logic—such as falling back to Python calculations, reformulating prompts, or switching to alternative LLM engines—before the next retry attempt occurs.

Where is the contract logic implemented in the source code?

The core contract logic resides in symai/strategy.py, which defines the @contract decorator, ContractContext, and the retry orchestration. The base class Expression that enables contract functionality is defined in symai/components.py. Engine selection for LLM calls happens in symai/backend/engine_repository.py, while structured output validation support is provided by LLMDataModel in symai/models/base.py. Primitive operators that leverage contracts are located in the symai/ops/ directory.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →