SymbolicAI Documentation
repository·main·Indexed 23 days ago
https://github.com/extensityai/symbolicaiA neuro-symbolic framework (version 2.0.0) that integrates classical Python programming with LLM capabilities. It features 'Symbols' for semantic and syntactic reasoning, 'Contracts' via @contract decorators to enforce data integrity and mitigate hallucinations, and a priority-based configuration system. The framework supports custom engine implementation and provides a shared REST transport for communicating with various AI providers.
What's inside SymbolicAI
- The Indexing Engine in SymbolicAI is responsible for vector search and Retrieval-Augmented Generation (RAG) operations. It utilizes Qdrant as the underlying vector database to manage data indexing and retrieval.
What is SymbolicAI?
mainSymbolicAI is a neuro-symbolic framework designed to bridge the gap between classical programming (Software 1.0) and data-driven programming (Software 2.0). It uses a divide-and-conquer strategy to break complex problems into smaller, manageable tasks that are evaluated using Large Language Models (LLMs).
Key capabilities include:
- Composable operations for complex problem-solving.
- Seamless transitions between differentiable (neural) and classical (symbolic) programming.
- Integration with various engines like OpenAI, WolframAlpha, OCR, and image generation providers.
- Support for multimodal inputs and outputs.
What is a @contract in SymbolicAI?
mainThe
@contractis a class decorator applied to custom classes inheriting fromsymai.Expression. It wraps the class'sforwardmethod with a validation and execution pipeline designed to ensure semantic correctness, especially when working with probabilistic LLM outputs.Key Features
- Type Support: Works with
symai.models.LLMDataModel(Pydantic-based) or native Python types (e.g.,str,int,list[int],dict[str, int],Optional[...],Union[...]). Primitive types are automatically wrapped into internalLLMDataModelwrappers for validation. - Self-Correction: Error messages from failed pre-conditions or post-conditions are used as corrective prompts to guide the LLM toward a valid output.
- Fallback Mechanism: If contract validation fails even after remedies, the class's original
forwardmethod is still called, allowing for fallback logic or returning default type-compliant objects. - Execution Pipeline: The decorator augments the class by implementing a sequence of
pre(pre-conditions),act(optional intermediate action), andpost(post-conditions) methods.
- Type Support: Works with
Understand SymbolicAI configuration priority
mainSymbolicAI uses a priority-based configuration loading system. When looking for settings, the system checks locations in the following order of priority:
- Debug Mode (Highest Priority): Looks for
symai.config.jsonin the Current Working Directory. Use this for local development and testing. - Environment-Specific Config (Second Priority): Located in
{python_env}/.symai/. Use this for settings specific to a particular Python environment or project. - Global Config (Lowest Priority): Located in
~/.symai/. This serves as the default fallback for all settings.
If a configuration file exists in multiple locations, the system uses the highest-priority version found. If an environment-specific config is missing or invalid, it falls back to the global configuration.
- Debug Mode (Highest Priority): Looks for
Access engine-specific metrics via RuntimeInfo.extras
mainThe
RuntimeInfoobject contains anextrasdictionary for metrics that do not fit standard token fields. For example,ParallelEngineuses this to storesku_searchandsku_extract_excerpts. When aggregating multipleRuntimeInfoobjects using the+operator, numeric values inextrasare summed, while non-numeric values are overwritten.# After tracking parallel search operations: usage_per_engine = RuntimeInfo.from_tracker(tracker, 0) for (engine_name, model_id), engine_data in usage_per_engine.items(): if engine_name == "ParallelEngine": print(f"Search calls: {engine_data.extras.get('sku_search', 0)}") print(f"Excerpt extractions: {engine_data.extras.get('sku_extract_excerpts', 0)}")Core concepts of SymbolicAI
mainSymbolicAI is built upon four fundamental abstractions that allow for the construction of complex computational graphs:
- Symbols: The fundamental data objects. All data (strings, integers, arrays, etc.) are treated as symbols, with natural language serving as the primary interface for interaction.
- Operations: Contextualized functions that take symbols as input, manipulate them, and return new symbols.
- Expressions: Non-terminal symbols that represent computations that can be further evaluated, enabling the creation of complex computational graphs.
- Engines: The backends that power computations. Examples include OpenAI, Anthropic, WolframAlpha, OCR, Qdrant, and various image generation providers.
Syntactic vs. Semantic Symbols in SymbolicAI
mainSymbolobjects in SymbolicAI exist in two modes:- Syntactic (Default): Behaves like standard Python values (strings, lists, ints). Operators perform literal operations (e.g., string splitting or exact matching).
- Semantic: Wired to the neuro-symbolic engine to understand meaning and context (e.g., recognizing that 'feline' is related to 'cat').
Switching Modes
- At creation: Pass
semantic=Trueto theSymbolconstructor. - On demand: Use the
.semprojection to access the semantic view, or.synto return to the syntactic view. Projections return the same underlying object with different behavioral logic.
Use Contracts for LLM correctness
mainSymbolicAI uses Design by Contract to ensure LLM outputs meet specific requirements. You define data models using
LLMDataModel(compatible with Pydantic'sBaseModel) and wrap your logic in anExpressionclass decorated with@contract.Contract Configuration
The
@contractdecorator accepts several parameters to handle errors and retries:pre_remedy: IfTrue, the system tries to fix bad inputs automatically.post_remedy: IfTrue, the system tries to fix bad LLM outputs automatically.accumulate_errors: IfTrue, feeds the history of errors to each retry to provide context.verbose: Enables progress display in the terminal.remedy_retry_params: A dictionary controlling retry logic (e.g.,tries,delay,max_delay,jitter,backoff).
Expression Lifecycle
An
Expressionclass typically implements:prompt: A static description of the task (mandatory).pre: Sanity-check inputs (optional).act: Mutate state (optional).LLM: The generation step (handled by the engine).post: Ensure the answer meets semantic rules (optional).forward: The return logic. If the contract succeeds, it returns the validated object; otherwise, it returns a fallback answer (mandatory).
from symai import Expression from symai.strategy import contract from symai.models import LLMDataModel from pydantic import Field, field_validator class DataModel(LLMDataModel): some_field: some_type = Field(description="very descriptive field") @field_validator('some_field') def validate_some_field(cls, v): valid_opts = ['A', 'B', 'C'] if v not in valid_opts: raise ValueError(f'Must be one of {valid_opts}, got "{v}".') return v @contract( pre_remedy=True, post_remedy=True, accumulate_errors=True, verbose=True, remedy_retry_params=dict(tries=3, delay=0.4, max_delay=4.0, jitter=0.15, backoff=1.8, graceful=False), ) class Agent(Expression): # Implementation of prompt, pre, act, post, and forward passDefine Input and Output Data Models with LLMDataModel
mainContracts rely on
LLMDataModel(subclasses of Pydantic) to define the structure and semantic intent of data.Best Practices:
- Use
Field(description="..."): This is critical. The descriptions are used to generate prompts for the LLM. Rich descriptions help theTypeValidationFunctionunderstand semantic intent during validation and remediation. - Hybrid Types: You can use native Python types (e.g.,
str,int,list[int]) in method signatures. SymbolicAI will automatically wrap them inLLMDataModelwrappers and unwrap them on return. - Structure: Define separate models for
Input,Intermediate(if usingact), andOutput.
from pydantic import Field from symai.models import LLMDataModel from typing import Optional, List class MyInput(LLMDataModel): text: str = Field(description="The input text to be processed.") max_length: Optional[int] = Field(default=None, description="Optional maximum length for processing.") class MyIntermediate(LLMDataModel): processed_text: str = Field(description="Text after initial processing by 'act'.") entities_found: List[str] = Field(default_factory=list, description="Entities identified in 'act'.") class MyOutput(LLMDataModel): result: str = Field(description="The final processed result.") is_valid: bool = Field(description="Indicates if the result is considered valid by post-conditions.")- Use
Use LLMDataModel for structured prompting and validation
mainThe
LLMDataModelclass is a thin Pydantic wrapper used to define the structure of data passed to and from LLMs. It provides:- Validation: Ensures data conforms to types and constraints (e.g.,
ge=0.0,le=1.0). - Automatic Prompt Templating: Rich
Fielddescriptions are used by the SymbolicAI engine to automatically generate context and instructions for the LLM. - Remedies: When a contract fails, the field descriptions and error messages help the engine construct corrective prompts.
- Validation: Ensures data conforms to types and constraints (e.g.,
Organize prompts using dot-separated keys
mainWhen loading a folder into a
PromptRegistry, every.jinjafile is registered under a dot-separated key derived from its path relative to the root folder. Subfolders are converted into dotted segments.Example Mapping:
File Key system.jinjasystemtask.jinjataskstages/plan.jinjastages.planstages/review.jinjastages.reviewExample folder structure:
prompts/ ├── manifest.toml ├── system.jinja ├── task.jinja └── stages/ ├── plan.jinja └── review.jinjaHow neuro-symbolic programming works in SymbolicAI
mainSymbolicAI implements a neuro-symbolic programming paradigm to combine the pattern recognition strengths of deep neural networks with the explicit reasoning capabilities of symbolic logic.
In this model:
- Neural Networks (LLMs) are used to extract information from data and handle natural language interfaces.
- Symbolic Reasoning is used to guide the generative process, provide structure, and perform explicit reasoning (like planning or causal analysis).
Computation is viewed as shifting the probability mass of an input stream toward a desired output stream. By defining a set of operations to manipulate symbols and constructing logical expressions, you can validate, steer, and control the generative behavior of LLMs to ensure they align with specific goals.