Skip to content

Commit

Permalink
Add concepts docs
Browse files Browse the repository at this point in the history
  • Loading branch information
panregedit committed Nov 11, 2024
1 parent 478e962 commit 961e558
Show file tree
Hide file tree
Showing 14 changed files with 577 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ It is recommended to deploy Conductor using Docker:
```bash
docker compose -f docker/conductor/docker-compose.yml up -d
```
- Once deployed, you can access the Conductor UI at `http://localhost:5000`.
- Once deployed, you can access the Conductor UI at `http://localhost:5001`. (Note: Mac system will occupy port 5000 by default, so we use 5001 here. You can specify other ports when deploying Conductor.)
- The Conductor API can be accessed via `http://localhost:8080`.

### 2. Install OmAgent
Expand Down
2 changes: 1 addition & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ OmAgent 使用 [Conductor](https://github.com/conductor-oss/conductor) 作为工
```bash
docker compose -f docker/conductor/docker-compose.yml up -d
```
- 部署完成后可以通过访问 `http://localhost:5000` 访问Conductor UI。
- 部署完成后可以通过访问 `http://localhost:5001` 访问Conductor UI。(注:Mac系统默认会占用5000端口,因此我们使用5001端口,你可以在部署Conductor的时候指定其它端口。)
- 通过 `http://localhost:8080` 调用Conductor API。

### 2. 安装OmAgent
Expand Down
2 changes: 1 addition & 1 deletion docker/conductor/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
- internal
ports:
- 8080:8080
- 5000:5000
- 5001:5000
healthcheck:
test: ["CMD", "curl","-I" ,"-XGET", "http://localhost:8080/health"]
interval: 60s
Expand Down
92 changes: 92 additions & 0 deletions docs/concepts/container.md
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 removed docs/concepts/register.md
Empty file.
53 changes: 53 additions & 0 deletions docs/concepts/registry.md
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.

157 changes: 157 additions & 0 deletions docs/concepts/task.md
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
Loading

0 comments on commit 961e558

Please sign in to comment.