How to Create Custom Neuro-Symbolic Operations Using the Expression Class in SymbolicAI

To create custom neuro-symbolic operations in SymbolicAI, subclass the Expression class from symai.symbol, implement the abstract forward method with your operation logic, and optionally set _sym_return_type to control result casting.

Creating custom neuro-symbolic operations allows you to extend SymbolicAI's lazy evaluation engine with domain-specific logic. The Expression class serves as the primary abstraction for defining operations that integrate seamlessly into the symbolic graph, supporting both pure Python implementations and future LLM-driven replacements. This guide demonstrates how to leverage the Expression architecture to build reusable, composable neuro-symbolic primitives.

Understanding the Expression Class Architecture

The Expression class in symai/symbol.py (lines 1039-1134) extends Symbol to provide lazy evaluation capabilities. When you subclass Expression, you inherit the full graph infrastructure, metadata handling, and primitive mixing machinery.

Core Mechanisms

Inheritance from SymbolExpression derives from Symbol at line 1039, giving your custom operations access to the symbolic graph, automatic metadata tracking, and primitive mix-ins.

Lazy Evaluation via __call__ – The __call__ method (lines 1054-1068) triggers evaluation only when the expression is invoked. It executes forward, then automatically links the result to the symbolic graph using _root_link.

Result Typing with _sym_return_type – This attribute (initialized around line 5050) defines the type that the expression casts to after evaluation. By default, it returns type(self), but you can override it to return specific Symbol subclasses.

Abstract forward Method – Subclasses must implement forward(self, *args, **kwargs) -> Symbol (lines 1120-1134). This method contains the actual operation logic, whether pure Python, external API calls, or LLM prompts.

Implementing a Custom Expression

Creating a working neuro-symbolic operation requires three steps: subclassing, implementing the forward logic, and configuring the return behavior.

Step 1: Subclass Expression

Import Expression from symai.symbol and create your subclass. The inheritance automatically registers your class within the symbolic framework.

from symai import Expression, Symbol

class MyOperation(Expression):
    """Custom neuro-symbolic operation."""
    def __init__(self):
        super().__init__()
        # Optional: define return type casting

        self._sym_return_type = Symbol

Step 2: Override the forward Method

Implement the forward method to define the operation's behavior. This method receives Symbol instances as arguments and must return a Symbol.

    def forward(self, a: Symbol, b: Symbol) -> Symbol:
        # Access raw values via .value property

        result = a.value + b.value
        # Return as Symbol to maintain graph compatibility

        return Symbol(result)

Step 3: Configure Return Type

Set _sym_return_type in __init__ to automatically cast results to specific types. This enables type-specific method chaining after evaluation.

from symai import Result  # hypothetical Result subclass

class TypedOperation(Expression):
    def __init__(self):
        super().__init__()
        self._sym_return_type = Result  # Results auto-cast to Result type

Practical Examples

Example 1: Simple Arithmetic Operation

This example demonstrates a basic Expression that adds two symbols. The implementation resides in pure Python but follows the neuro-symbolic pattern.

from symai import Symbol, Expression

class Add(Expression):
    """Lazy addition of two symbols."""
    def forward(self, a: Symbol, b: Symbol) -> Symbol:
        # Unwrap Symbol values and return new Symbol

        return Symbol(a.value + b.value)

# Usage

x = Symbol(3)
y = Symbol(7)
expr = Add()(x, y)       # __call__ triggers forward and links to graph

print(expr.value)        # → 10

print(expr.linker)       # displays graph connection

Key implementation details: The Add class inherits from Expression at line 1039 of symbol.py. When instantiated and called, the __call__ method (lines 1054-1068) executes forward and automatically invokes _root_link to attach the result to the symbolic graph.

Example 2: LLM-Powered Summarization

This example uses the @core.prompt decorator to create an Expression backed by an LLM. The decorator transforms the method into a lazy evaluation that queries the language model.

from symai import Expression, Symbol, core

class Summarize(Expression):
    """Neuro-symbolic summarization using LLM."""
    @core.prompt(message="Summarize the following text:", temperature=0.2)
    def forward(self, text: Symbol) -> Symbol:
        # The decorator intercepts this method; body is placeholder

        pass

# Usage

document = Symbol("Long article content...")
summary = Summarize()(document)    # Triggers LLM call via decorator

print(summary.value)             # LLM-generated summary

Implementation context: The @core.prompt decorator is defined in symai/core.py and integrates with the Expression architecture by wrapping the forward method. The Expression base class handles the lazy evaluation and graph linking (lines 1054-1068 in symbol.py), while the decorator manages the LLM interaction.

Example 3: Functional Composition with ExpressionBuilder

For complex pipelines, ExpressionBuilder in symai/components.py provides a functional interface to chain multiple Expression steps without explicit subclassing.

from symai.components import ExpressionBuilder

# Build a computation pipeline: read → double → add constant

pipeline = ExpressionBuilder() \
    .add(lambda _: 4) \
    .add(lambda x: x * 2) \
    .add(lambda x: x + 10)

result = pipeline()        # Evaluates the chain

print(result.value)        # → 18

Architecture note: ExpressionBuilder internally creates temporary Expression objects for each step, leveraging the same lazy evaluation mechanism defined in symai/symbol.py. This approach is useful for rapid prototyping before converting stable logic into formal Expression subclasses.

Advanced Features

Runtime Primitives

You can extend symbols with custom operations at runtime using the primitive method. This adds bound methods to individual Symbol instances without modifying global classes.

from symai import Symbol

def square(self):
    return self.value ** 2

s = Symbol(5)
s.primitive("square", square)   # Defined in symbol.py lines 642-681

print(s.square())               # → 25

Graph Linking and Symbolic Graph

Every Expression evaluation automatically links results into the symbolic graph via _root_link (lines 996-1024 in symbol.py). This enables:

  • Retrieval: Later symbols can access previous computation results through the linker attribute.
  • Provenance: Full traceability of how results were derived.
  • Composition: Chaining expressions where each step references the graph state.

The _sym_return_type mechanism ensures that linked results maintain type safety, casting outputs to appropriate Symbol subclasses as defined in your Expression implementation.

Summary

  • Subclass Expression from symai.symbol to create lazy neuro-symbolic operations that integrate with the SymbolicAI graph engine.
  • Implement forward to define the operation logic, accepting and returning Symbol instances; this method is invoked automatically when the expression is called.
  • Set _sym_return_type in __init__ to control automatic type casting of results, enabling method chaining with specific symbol types.
  • Leverage decorators like @core.prompt from symai/core.py to replace Python logic with LLM-driven implementations without changing the expression interface.
  • Use ExpressionBuilder from symai/components.py for functional composition of expression chains without explicit subclassing.
  • Access the graph via the linker attribute and _root_link mechanism to maintain provenance and enable result retrieval across operations.

Frequently Asked Questions

What is the difference between Symbol and Expression in SymbolicAI?

Symbol is the base abstraction for all symbolic objects in SymbolicAI, providing the graph infrastructure, metadata handling, and primitive mix-ins. Expression is a specialized subclass of Symbol defined in symai/symbol.py that adds lazy evaluation capabilities through the __call__ method and requires subclasses to implement the forward method. While all Expression objects are Symbol instances, not all Symbol objects are lazy-evaluated expressions.

How do I convert a regular Python function into an Expression?

To convert a Python function into a neuro-symbolic Expression, create a subclass of Expression and move your function logic into the forward method. Wrap the arguments and return values as Symbol instances to maintain graph compatibility. For example, if you have a function def add(a, b): return a + b, convert it by subclassing Expression and implementing forward to accept Symbol instances, extract their .value attributes, perform the addition, and return a new Symbol with the result.

Can I use decorators to create Expressions?

Yes, SymbolicAI provides decorators in symai/core.py that convert functions into Expression-backed operations. The @core.prompt decorator transforms a method into an LLM-driven expression, where the decorator parameters configure the language model call. When applied to a method within an Expression subclass, the decorator intercepts the forward invocation and replaces the Python logic with an LLM query, while still maintaining the lazy evaluation and graph linking provided by the Expression base class.

Where are the core Expression classes defined?

The core Expression class is defined in symai/symbol.py starting at line 1039, where it inherits from Symbol. The lazy evaluation logic resides in the __call__ method (lines 1054-1068), while the abstract forward method is defined at lines 1120-1134. Helper utilities for building expression pipelines are located in symai/components.py, including the ExpressionBuilder class. Decorators for LLM integration are found in symai/core.py, and primitive operations are defined in symai/ops/primitives.py.

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 →