Custom Engine

If you want to replace or extend the functionality of our framework, you can do so by customizing the existing engines or creating new engines. To create and use any other LLM as a backend you can for example change the neurosymbolic engine setting and register the new engine to the EngineRepository. The following example shows how to create a new neurosymbolic engine:

from symai.backend.base import Engine
from symai.functional import EngineRepository

# setup an engine
class MyEngine(Engine):
  def id(self):
    return 'neurosymbolic'

  def prepare(self, argument):
    # get input from the pre-processors output and use *args, **kwargs and prop from argument
    # argument.prop contains all your kwargs accessible via dot `.` operation and additional meta info
    # such as function signature, system relevant info etc.
    prompts = argument.prop.preprocessed_input
    args    = argument.args
    kwargs  = argument.kwargs
    # prepare the prompt statement as you want (take a look at the other engines like for GPT-4)
    ...
    # assign it to prepared_input
    argument.prop.prepared_input = ...

  def forward(self, argument):
    # get prep statement
    prompt = argument.prop.prepared_input
    # Your API / engine related call code here
    return ...

# register your engine
EngineRepository.register('neurosymbolic', engine, allow_engine_override=True)

Any engine is derived from the base class Engine and is then registered in the engines repository using its registry ID. The ID is for instance used in core.py decorators to address where to send the zero/few-shot statements using the class EngineRepository. You can find the EngineRepository defined in functional.py with the respective query method. Every engine has therefore three main methods you need to implement. The id, prepare and forward method. The id return the engine category. The prepare and forward methods have a signature variable called argument which carries all necessary pipeline relevant data. For instance, the output of the argument.prop.preprocessed_input contains the pre-processed output of the PreProcessor objects and is usually what you need to build and pass on to the argument.prop.prepared_input, which is then used in the forward call.

If you don't want to re-write the entire engine code but overwrite the existing prompt prepare logic, you can do so by subclassing the existing engine and overriding the prepare method.

Here is an example of how to initialize your own engine. We will subclass the existing GPTXChatEngine and override the prepare method. This method is called before the neural computation and can be used to modify the input prompt's parameters that will be passed in for execution. In this example, we will replace the prompt with dummy text for illustration purposes:

import os

from symai import Expression, Symbol
from symai.backend.engines.neurosymbolic.engine_openai_gptX_chat import \
    GPTXChatEngine
from symai.functional import EngineRepository


class DummyEngine(GPTXChatEngine):
    def __init__(self):
        super().__init__(model='gpt-4o-mini', api_key=os.getenv('OPENAI_API_KEY', 'your-api-key-here'))

    def prepare(self, argument):
        argument.prop.prepared_input = [
            {'role': 'system', 'content': 'Write like Jack London!'},
            {'role': 'user', 'content': 'Go wild and generate something!'}
        ]

custom_engine = DummyEngine()
sym = Symbol()
EngineRepository.register('neurosymbolic', custom_engine, allow_engine_override=True)
res = sym.compose()
print(res)

To configure an engine, we can forward commands through Expression objects by using the command method. The command method passes on configurations (as **kwargs) to the engines and change functionalities or parameters. The functionalities depend on the respective engine.

In this example, we will enable verbose mode, where the engine will print out the methods it is executing and the parameters it is using. This is useful for debugging purposes:

from symai import Expression

sym = Symbol('Hello World!')
Expression.command(engines=['neurosymbolic'], verbose=True)
res = sym.translate('German')

Finally, if you want to create a completely new engine but still maintain our workflow, you can use the query function from symai/functional.py and pass in your engine along with all other specified objects (i.e., Prompt, PreProcessor, etc).

Last updated