-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
478e962
commit 961e558
Showing
14 changed files
with
577 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# Container | ||
|
||
The Container module is a dependency injection and service container implementation that manages components and their dependencies in the OmAgent core system. It follows the IoC (Inversion of Control) pattern to handle component registration, configuration, and retrieval. | ||
|
||
## Key Features | ||
### 1. Component Management | ||
- Registers and manages different types of components (connections, memories, callbacks, etc.) | ||
- Handles component dependencies automatically | ||
- Provides type-safe access to registered components | ||
|
||
### 2. Connector Management | ||
Manages service connectors that components might depend on | ||
- Automatically injects required connectors into components | ||
|
||
### 3. Special Component Types | ||
- STM (Short-term Memory) | ||
- LTM (Long-term Memory) | ||
- Callback handlers | ||
- Input handlers | ||
|
||
### 4. Configuration Management | ||
- Can compile configurations to YAML | ||
- Loads configurations from YAML files | ||
- Supports environment variables and descriptions in configs | ||
|
||
|
||
## Register | ||
Examples of registering: | ||
```python | ||
from omagent_core.utils.container import container | ||
from omagent_core.services.handlers.redis_stream_handler import RedisStreamHandler | ||
|
||
# Register a connector using component name | ||
container.register_connector(RedisConnector, name="redis_client") | ||
|
||
# Register a component using component class | ||
container.register_component(RedisStreamHandler) | ||
|
||
# Register STM component | ||
container.register_stm("RedisSTM") | ||
|
||
# Register LTM component | ||
container.register_ltm("MilvusLTM") | ||
|
||
# Register callback and input handlers | ||
container.register_callback("AppCallback") | ||
container.register_input("AppInput") | ||
``` | ||
|
||
## Configuration Management | ||
|
||
1. **Compile Configuration**: Container can automatically generate YAML configuration template files. You can change the values of the parameters in the template files which will take effect when loading the configuration. The ```env_var``` indicates the environment variable names for the parameters, don't change it because it is just for demonstration. | ||
```python | ||
from pathlib import Path | ||
container.compile_config(Path('./config_dir')) | ||
``` | ||
|
||
|
||
|
||
2. **Load Configuration**: Load settings from YAML files. This will update the container with the settings in the YAML file. | ||
```python | ||
container.from_config('container.yaml') | ||
``` | ||
|
||
## Component Retrieval | ||
|
||
Access registered components: | ||
```python | ||
# Get a connector | ||
redis_client = container.get_connector("redis_client") | ||
|
||
# Get STM component | ||
stm = container.stm | ||
|
||
# Get LTM component | ||
ltm = container.ltm | ||
|
||
# Get callback handler | ||
callback = container.callback | ||
|
||
# Get input handler | ||
input_handler = container.input | ||
``` | ||
|
||
|
||
## Best Practices | ||
|
||
1. **Early Registration**: Register all components at application startup | ||
2. **Configuration Files**: Use YAML configuration files for better maintainability | ||
3. **Compile Configuration**: Prepare a separated script to compile container configuration before application startup. | ||
4. **Update Container**: Update the container with the settings in project entry file. Do register default Special Components (STM, LTM, Callback, Input) before update. | ||
5. **Single Instance**: Use the global container instance provided by the framework |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Register | ||
|
||
The Registry module is a powerful tool for managing and organizing different types of modules in your application. It supports registration and retrieval of various categories like prompts, LLMs, workers, tools, encoders, connectors, and components. | ||
|
||
## Registration | ||
You can register classes using either decorators or direct registration: | ||
```python | ||
from omagent_core.utils.registry import registry | ||
|
||
# Using decorator (recommended) | ||
@registry.register_node() | ||
class MyNode: | ||
name = "MyNode" | ||
|
||
# Or with a custom name | ||
@registry.register_tool(name="custom_tool_name") | ||
class MyTool: | ||
pass | ||
|
||
# Direct registration | ||
class MyLLM: | ||
pass | ||
registry.register("llm", "my_llm")(MyLLM) | ||
``` | ||
|
||
|
||
## Retrieval | ||
Retrieve registered modules using the get methods: | ||
```python | ||
# Get registered modules | ||
my_node = registry.get_node("MyNode") | ||
my_tool = registry.get_tool("custom_tool_name") | ||
my_llm = registry.get("llm", "my_llm") | ||
``` | ||
|
||
|
||
## Auto-Import Feature | ||
The registry can automatically import modules from specified paths: | ||
```python | ||
# Import from default paths | ||
registry.import_module() | ||
|
||
# Import from custom project path | ||
registry.import_module("path/to/your/modules") | ||
|
||
# Import from multiple paths | ||
registry.import_module([ | ||
"path/to/modules1", | ||
"path/to/modules2" | ||
]) | ||
``` | ||
Note: Do use the ```registry.import_module()``` in the main function of your script so that the modules can be registered to python environment before being used. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Task | ||
|
||
Task is the basic unit of building workflow. There are two types of tasks: simple task and operator. | ||
|
||
## Simple Task | ||
The functionality of simple task is defined by binding it to a [worker](./worker.md). | ||
Here is an example of how to define a simple task: | ||
```python | ||
from omagent_core.engine.worker.base import BaseWorker | ||
from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow | ||
from omagent_core.engine.workflow.task.simple_task import simple_task | ||
from omagent_core.utils.registry import registry | ||
|
||
# Define a worker | ||
@registry.register_worker() | ||
class SimpleWorker(BaseWorker): | ||
def _run(self, my_name: str): | ||
return {} | ||
|
||
# Define a workflow | ||
workflow = ConductorWorkflow(name='my_exp') | ||
|
||
# Define a simple task | ||
task = simple_task(task_def_name='SimpleWorker', task_reference_name='ref_name', inputs={'my_name': workflow.input('my_name')}) | ||
|
||
workflow >> task | ||
``` | ||
Specify the task definition name(```task_def_name```) and the task reference name(```task_reference_name```). The task definition name should be the name of the corresponding worker class. The task reference name is used to identify the task in the workflow. | ||
Specify the inputs of the task. Inputs may be either values or references to a workflow's initial inputs or the outputs of preceding tasks. | ||
See [workflow](./workflow.md) for workflow details. | ||
|
||
## Operators | ||
Operators are the build-in tasks provided by the workflow engine. They handle the workflow control logic. | ||
### 1. Switch Task | ||
Switch task is used to make a decision based on the value of a given field. | ||
```python | ||
from omagent_core.engine.workflow.task.switch_task import SwitchTask | ||
from omagent_core.engine.worker.base import BaseWorker | ||
from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow | ||
from omagent_core.engine.workflow.task.simple_task import simple_task | ||
from omagent_core.utils.registry import registry | ||
|
||
@registry.register_worker() | ||
class SimpleWorker1(BaseWorker): | ||
def _run(self): | ||
print('worker1') | ||
return {} | ||
|
||
@registry.register_worker() | ||
class SimpleWorker2(BaseWorker): | ||
def _run(self): | ||
print('worker2') | ||
return {} | ||
|
||
@registry.register_worker() | ||
class SimpleWorker3(BaseWorker): | ||
def _run(self): | ||
print('worker3') | ||
return {} | ||
|
||
workflow = ConductorWorkflow(name='switch_test') | ||
|
||
# Create some example tasks (replace with your actual tasks) | ||
task1 = simple_task(task_def_name='SimpleWorker1', task_reference_name='ref_name1') | ||
task2 = simple_task(task_def_name='SimpleWorker2', task_reference_name='ref_name2') | ||
task3 = simple_task(task_def_name='SimpleWorker3', task_reference_name='ref_name3') | ||
|
||
# 1. Create a switch task with a value-based condition | ||
switch = SwitchTask( | ||
task_ref_name="my_switch", | ||
case_expression=workflow.input('switch_case_value'), # This will evaluate the switch_case_value from workflow input | ||
) | ||
|
||
# 2. Add cases | ||
switch.switch_case("w1", [task1]) | ||
switch.switch_case("w2", [task2]) | ||
|
||
# 3. Add default case (optional) | ||
switch.default_case([task3]) | ||
|
||
workflow >> switch | ||
|
||
workflow.register(overwrite=True) | ||
``` | ||
This will create a basic workflow with a switch task shown below. | ||
<p align="center"> | ||
<img src="../images/switch_task.png" width="300"/> | ||
</p> | ||
You can also chaining the switch cases as follows: | ||
|
||
```python | ||
switch.switch_case("w1", [task1]).switch_case("w2", [task2]).default_case([task3]) | ||
``` | ||
|
||
### 2. Fork-Join Task | ||
The fork-join task is used to execute multiple tasks in parallel. | ||
```python | ||
from omagent_core.engine.workflow.task.fork_task import ForkTask | ||
from omagent_core.engine.worker.base import BaseWorker | ||
from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow | ||
from omagent_core.engine.workflow.task.simple_task import simple_task | ||
from omagent_core.utils.registry import registry | ||
|
||
|
||
@registry.register_worker() | ||
class SimpleWorker1(BaseWorker): | ||
def _run(self): | ||
print("worker1") | ||
return {} | ||
|
||
|
||
@registry.register_worker() | ||
class SimpleWorker2(BaseWorker): | ||
def _run(self): | ||
print("worker2") | ||
return {} | ||
|
||
|
||
@registry.register_worker() | ||
class SimpleWorker3(BaseWorker): | ||
def _run(self): | ||
print("worker3") | ||
return {} | ||
|
||
|
||
# Create the main workflow | ||
workflow = ConductorWorkflow(name="fork_join_test") | ||
|
||
# Create tasks for parallel execution | ||
task1 = simple_task(task_def_name="SimpleWorker1", task_reference_name="parallel_task1") | ||
task2 = simple_task(task_def_name="SimpleWorker2", task_reference_name="parallel_task2") | ||
task3 = simple_task(task_def_name="SimpleWorker3", task_reference_name="parallel_task3") | ||
|
||
# Create parallel execution paths | ||
path1 = [task1] # First parallel path | ||
path2 = [task2] # Second parallel path | ||
path3 = [task3] # Third parallel path | ||
|
||
# Create the fork task with multiple parallel paths | ||
fork_task = ForkTask( | ||
task_ref_name="parallel_execution", | ||
forked_tasks=[path1, path2, path3], | ||
# The join will wait for the last task in each path | ||
join_on=["parallel_task1", "parallel_task2", "parallel_task3"] | ||
) | ||
|
||
# Add the fork task to the workflow | ||
workflow.add(fork_task) | ||
|
||
workflow.register(overwrite=True) | ||
``` | ||
This will create a basic workflow with a fork-join task shown below. | ||
<p align="center"> | ||
<img src="../images/fork_task.png" width="300"/> | ||
</p> | ||
|
||
### 3. Do-While Task |
Oops, something went wrong.