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 Symbol – Expression 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
linkerattribute. - 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
Expressionfromsymai.symbolto create lazy neuro-symbolic operations that integrate with the SymbolicAI graph engine. - Implement
forwardto define the operation logic, accepting and returningSymbolinstances; this method is invoked automatically when the expression is called. - Set
_sym_return_typein__init__to control automatic type casting of results, enabling method chaining with specific symbol types. - Leverage decorators like
@core.promptfromsymai/core.pyto replace Python logic with LLM-driven implementations without changing the expression interface. - Use
ExpressionBuilderfromsymai/components.pyfor functional composition of expression chains without explicit subclassing. - Access the graph via the
linkerattribute and_root_linkmechanism 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →