SymbolicAI Documentation

repository·main·Indexed 23 days ago

https://github.com/extensityai/symbolicai

A 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.

Tokens
52.6K
Snippets
117
Records
175
Agent score
80%

What's inside SymbolicAI

  1. What is SymbolicAI?

    main

    SymbolicAI 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.
  2. What is a @contract in SymbolicAI?

    main

    The @contract is a class decorator applied to custom classes inheriting from symai.Expression. It wraps the class's forward method 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 internal LLMDataModel wrappers 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 forward method 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), and post (post-conditions) methods.
  3. Understand SymbolicAI configuration priority

    main

    SymbolicAI uses a priority-based configuration loading system. When looking for settings, the system checks locations in the following order of priority:

    1. Debug Mode (Highest Priority): Looks for symai.config.json in the Current Working Directory. Use this for local development and testing.
    2. Environment-Specific Config (Second Priority): Located in {python_env}/.symai/. Use this for settings specific to a particular Python environment or project.
    3. 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.

  4. Access engine-specific metrics via RuntimeInfo.extras

    main

    The RuntimeInfo object contains an extras dictionary for metrics that do not fit standard token fields. For example, ParallelEngine uses this to store sku_search and sku_extract_excerpts. When aggregating multiple RuntimeInfo objects using the + operator, numeric values in extras are 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)}")
  5. Core concepts of SymbolicAI

    main

    SymbolicAI is built upon four fundamental abstractions that allow for the construction of complex computational graphs:

    1. 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.
    2. Operations: Contextualized functions that take symbols as input, manipulate them, and return new symbols.
    3. Expressions: Non-terminal symbols that represent computations that can be further evaluated, enabling the creation of complex computational graphs.
    4. Engines: The backends that power computations. Examples include OpenAI, Anthropic, WolframAlpha, OCR, Qdrant, and various image generation providers.
  6. Syntactic vs. Semantic Symbols in SymbolicAI

    main

    Symbol objects in SymbolicAI exist in two modes:

    1. Syntactic (Default): Behaves like standard Python values (strings, lists, ints). Operators perform literal operations (e.g., string splitting or exact matching).
    2. 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=True to the Symbol constructor.
    • On demand: Use the .sem projection to access the semantic view, or .syn to return to the syntactic view. Projections return the same underlying object with different behavioral logic.
  7. Use Contracts for LLM correctness

    main

    SymbolicAI uses Design by Contract to ensure LLM outputs meet specific requirements. You define data models using LLMDataModel (compatible with Pydantic's BaseModel) and wrap your logic in an Expression class decorated with @contract.

    Contract Configuration

    The @contract decorator accepts several parameters to handle errors and retries:

    • pre_remedy: If True, the system tries to fix bad inputs automatically.
    • post_remedy: If True, the system tries to fix bad LLM outputs automatically.
    • accumulate_errors: If True, 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 Expression class typically implements:

    1. prompt: A static description of the task (mandatory).
    2. pre: Sanity-check inputs (optional).
    3. act: Mutate state (optional).
    4. LLM: The generation step (handled by the engine).
    5. post: Ensure the answer meets semantic rules (optional).
    6. 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
        pass
  8. Define Input and Output Data Models with LLMDataModel

    main

    Contracts 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 the TypeValidationFunction understand 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 in LLMDataModel wrappers and unwrap them on return.
    • Structure: Define separate models for Input, Intermediate (if using act), and Output.
    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.")
  9. Use LLMDataModel for structured prompting and validation

    main

    The LLMDataModel class 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 Field descriptions 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.
  10. Organize prompts using dot-separated keys

    main

    When loading a folder into a PromptRegistry, every .jinja file is registered under a dot-separated key derived from its path relative to the root folder. Subfolders are converted into dotted segments.

    Example Mapping:

    FileKey
    system.jinjasystem
    task.jinjatask
    stages/plan.jinjastages.plan
    stages/review.jinjastages.review

    Example folder structure:

    prompts/
    ├── manifest.toml
    ├── system.jinja
    ├── task.jinja
    └── stages/
        ├── plan.jinja
        └── review.jinja
  11. How neuro-symbolic programming works in SymbolicAI

    main

    SymbolicAI 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.