Skip to content

Commit 290ced6

Browse files
committed
feat: Implement module trigger mechanism
1 parent 883d02f commit 290ced6

File tree

6 files changed

+311
-22
lines changed

6 files changed

+311
-22
lines changed

docs/README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# ABI Documentation
2+
3+
Welcome to the ABI (Agent-Based Intelligence) documentation. This repository contains comprehensive documentation about the ABI system, its architecture, components, and usage guidelines.
4+
5+
## Table of Contents
6+
7+
### Core Documentation
8+
- [Modules](./modules.md) - Comprehensive guide to ABI's modular architecture
9+
- [What is a Module?](./modules.md#what-is-a-module)
10+
- [How Modules Work](./modules.md#how-modules-work)
11+
- [Module Loading Process](./modules.md#module-loading-process)
12+
- [Module Structure](./modules.md#module-structure)
13+
- [How Components Are Loaded](./modules.md#how-components-are-loaded)
14+
- [Creating a New Module](./modules.md#creating-a-new-module)
15+
- [Step-by-Step Guide](./modules.md#step-by-step-guide)
16+
- [Disabling a Module](./modules.md#disabling-a-module)
17+
- [Best Practices](./modules.md#best-practices)
18+
- [Troubleshooting](./modules.md#troubleshooting)
19+
20+
- [Module Triggers](./modules.triggers.md) - Event-driven programming with ontology triggers
21+
- [What are Triggers?](./modules.triggers.md#what-are-triggers)
22+
- [Why Use Triggers?](./modules.triggers.md#why-use-triggers)
23+
- [How Triggers Work](./modules.triggers.md#how-triggers-work)
24+
- [Example Use Case](./modules.triggers.md#example-use-case)
25+
- [How to Register Triggers in a Module](./modules.triggers.md#how-to-register-triggers-in-a-module)
26+
- [How Triggers are Loaded](./modules.triggers.md#how-triggers-are-loaded)
27+
- [Best Practices for Using Triggers](./modules.triggers.md#best-practices-for-using-triggers)
28+
- [Advanced Usage: Trigger Patterns](./modules.triggers.md#advanced-usage-trigger-patterns)
29+
- [Conclusion](./modules.triggers.md#conclusion)
30+
31+
## How to Use This Documentation
32+
33+
1. **New to ABI?** Start with the [Modules](./modules.md) documentation to understand the core architecture.
34+
2. **Building a reactive system?** Learn about [Module Triggers](./modules.triggers.md) to create event-driven workflows.
35+
3. **Looking for specific information?** Use the table of contents above to navigate directly to the relevant section.
36+
37+
## Contributing to Documentation
38+
39+
If you'd like to contribute to this documentation:
40+
41+
1. Fork the repository
42+
2. Make your changes
43+
3. Submit a pull request with a clear description of your improvements
44+
45+
When adding new documentation:
46+
- Use Markdown format
47+
- Follow the existing structure and style
48+
- Add appropriate links to your new content in this README
49+
50+
## Need Help?
51+
52+
If you can't find the information you need in this documentation, please:
53+
- Check the project's main [README.md](../README.md)
54+
- Open an issue in the repository with your question
55+
- Reach out to the project maintainers
56+
57+
---
58+
59+
*This documentation is maintained by the ABI team and contributors.*

docs/modules.md

+52-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
## What is a Module?
44

5-
A module in the ABI system is a self-contained, standalone component that encapsulates related functionality. Modules are designed to be pluggable, meaning they can be added or removed from the system without modifying other parts of the codebase. Each module resides in its own directory under `src/modules/`.
5+
A module in the ABI system is a self-contained, standalone component that encapsulates related functionality. Modules are designed to be pluggable, meaning they can be added or removed from the system without modifying other parts of the codebase. Modules are now organized into two categories:
6+
7+
1. **Core Modules**: Located in `src/core/modules/` - These are essential modules that provide the core functionality of the ABI system.
8+
2. **Custom Modules**: Located in `src/custom/modules/` - These are user-created modules that extend the system with additional capabilities.
9+
10+
This separation makes the system architecture easier to understand and maintain, with a clear distinction between core functionality and custom extensions.
611

712
Modules provide a way to organize and structure your code in a modular fashion, making it easier to maintain, extend, and reuse functionality. They can contain various components such as:
813

@@ -17,21 +22,35 @@ Modules provide a way to organize and structure your code in a modular fashion,
1722

1823
The ABI system automatically discovers and loads modules at runtime. Here's how the process works:
1924

20-
1. The system scans the `src/modules/` directory for subdirectories
25+
1. The system scans both the `src/core/modules/` and `src/custom/modules/` directories for subdirectories
2126
2. Each subdirectory (except `__pycache__` and those containing "disabled" in their name) is considered a module
2227
3. The module is imported using Python's import system
2328
4. An `IModule` instance is created for the module
2429
5. The module's components (assistants, workflows, pipelines) are loaded
2530
6. The loaded module is added to the system's registry
2631

27-
This automatic loading mechanism means that you can add new functionality to the system simply by creating a new module directory with the appropriate structure.
32+
This automatic loading mechanism means that you can add new functionality to the system simply by creating a new module directory with the appropriate structure in either the core or custom modules directory.
2833

2934
### Module Structure
3035

3136
A typical module has the following directory structure:
3237

3338
```
34-
src/modules/your_module_name/
39+
src/core/modules/your_core_module_name/
40+
├── assistants/ # Contains AI agents
41+
│ └── YourAssistant.py # An agent implementation
42+
├── workflows/ # Contains workflow implementations
43+
│ └── YourWorkflow.py # A workflow implementation
44+
├── pipelines/ # Contains pipeline implementations
45+
│ └── YourPipeline.py # A pipeline implementation
46+
└── tests/ # Contains tests for the module
47+
└── test_module.py # Test implementations
48+
```
49+
50+
Or for custom modules:
51+
52+
```
53+
src/custom/modules/your_custom_module_name/
3554
├── assistants/ # Contains AI agents
3655
│ └── YourAssistant.py # An agent implementation
3756
├── workflows/ # Contains workflow implementations
@@ -51,21 +70,30 @@ src/modules/your_module_name/
5170

5271
To create a new module, follow these steps:
5372

54-
1. Create a new directory under `src/modules/` with your module name (use a descriptive name in snake_case)
55-
2. Set up the standard directory structure (assistants, workflows, pipelines, tests)
56-
3. Implement your components following the appropriate patterns
73+
1. Decide whether your module should be a core module or a custom module:
74+
- Core modules (`src/core/modules/`) are for essential system functionality
75+
- Custom modules (`src/custom/modules/`) are for extensions and user-specific functionality
76+
2. Create a new directory under the appropriate path with your module name (use a descriptive name in snake_case)
77+
3. Set up the standard directory structure (assistants, workflows, pipelines, tests)
78+
4. Implement your components following the appropriate patterns
5779

5880
### Step-by-Step Guide
5981

6082
#### 1. Create the Module Directory
6183

84+
For a core module:
6285
```bash
63-
mkdir -p src/modules/your_module_name/{assistants,workflows,pipelines,tests}
86+
mkdir -p src/core/modules/your_module_name/{assistants,workflows,pipelines,tests}
87+
```
88+
89+
For a custom module:
90+
```bash
91+
mkdir -p src/custom/modules/your_module_name/{assistants,workflows,pipelines,tests}
6492
```
6593

6694
#### 2. Create an Assistant
6795

68-
Create a file `src/modules/your_module_name/assistants/YourAssistant.py`:
96+
Create a file `src/[core|custom]/modules/your_module_name/assistants/YourAssistant.py`:
6997

7098
```python
7199
from langchain_openai import ChatOpenAI
@@ -102,7 +130,7 @@ def create_agent(
102130

103131
#### 3. Create a Workflow
104132

105-
Create a file `src/modules/your_module_name/workflows/YourWorkflow.py`:
133+
Create a file `src/[core|custom]/modules/your_module_name/workflows/YourWorkflow.py`:
106134

107135
```python
108136
from abi.workflow import Workflow, WorkflowConfiguration
@@ -152,7 +180,7 @@ class YourWorkflow(Workflow):
152180

153181
#### 4. Create a Pipeline
154182

155-
Create a file `src/modules/your_module_name/pipelines/YourPipeline.py`:
183+
Create a file `src/[core|custom]/modules/your_module_name/pipelines/YourPipeline.py`:
156184

157185
```python
158186
from abi.pipeline import Pipeline, PipelineConfiguration, PipelineParameters
@@ -204,7 +232,7 @@ class YourPipeline(Pipeline):
204232

205233
#### 5. Add Tests
206234

207-
Create a file `src/modules/your_module_name/tests/test_module.py`:
235+
Create a file `src/[core|custom]/modules/your_module_name/tests/test_module.py`:
208236

209237
```python
210238
import unittest
@@ -235,24 +263,29 @@ if __name__ == "__main__":
235263
To disable a module without removing it from the codebase, simply add "disabled" to the module directory name:
236264

237265
```bash
238-
mv src/modules/your_module_name src/modules/your_module_name_disabled
266+
# For core modules
267+
mv src/core/modules/your_module_name src/core/modules/your_module_name_disabled
268+
269+
# For custom modules
270+
mv src/custom/modules/your_module_name src/custom/modules/your_module_name_disabled
239271
```
240272

241273
The system will automatically skip loading modules with "disabled" in their name.
242274

243275
## Best Practices
244276

245-
1. **Keep modules focused**: Each module should have a clear, specific purpose
246-
2. **Maintain independence**: Minimize dependencies between modules
247-
3. **Document your components**: Provide clear documentation for your assistants, workflows, and pipelines
248-
4. **Write tests**: Include comprehensive tests for your module's components
249-
5. **Follow naming conventions**: Use descriptive names for your module and its components
277+
1. **Choose the right location**: Place essential system functionality in core modules and extensions in custom modules
278+
2. **Keep modules focused**: Each module should have a clear, specific purpose
279+
3. **Maintain independence**: Minimize dependencies between modules
280+
4. **Document your components**: Provide clear documentation for your assistants, workflows, and pipelines
281+
5. **Write tests**: Include comprehensive tests for your module's components
282+
6. **Follow naming conventions**: Use descriptive names for your module and its components
250283

251284
## Troubleshooting
252285

253286
If your module is not being loaded correctly, check the following:
254287

255-
1. Ensure your module directory is directly under `src/modules/`
288+
1. Ensure your module directory is directly under either `src/core/modules/` or `src/custom/modules/`
256289
2. Verify that your module name doesn't contain "disabled"
257290
3. Check that your assistants have a `create_agent()` function
258291
4. Ensure your workflows and pipelines follow the correct class structure

docs/modules.triggers.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Triggers in ABI Modules
2+
3+
## What are Triggers?
4+
5+
Triggers are a powerful feature in the ABI system that enable reactive programming based on changes in the ontology store. They allow modules to register callbacks that are automatically executed when specific RDF triples are added to or removed from the ontology.
6+
7+
A trigger consists of three main components:
8+
1. A **triple pattern** to match against (subject, predicate, object)
9+
2. An **event type** (INSERT or DELETE)
10+
3. A **callback function** to execute when the event occurs
11+
12+
Triggers transform the ontology from a passive data store into a reactive system that can automatically initiate actions when data changes. This creates an "executable ontology" or "reactive ontology" where the knowledge graph itself becomes a mechanism for coordinating system behavior.
13+
14+
## Why Use Triggers?
15+
16+
Triggers provide several key benefits:
17+
18+
1. **Decoupling of Components**: Modules can react to data changes without direct dependencies on each other.
19+
2. **Event-Driven Architecture**: The system becomes more responsive and can automatically initiate processes when relevant data appears.
20+
3. **Simplified Workflows**: Complex multi-step processes can be broken down into smaller, more manageable components that activate in sequence.
21+
4. **Reduced Boilerplate**: No need to manually check for conditions or poll for changes.
22+
23+
## How Triggers Work
24+
25+
When a triple is added to or removed from the ontology store, the system checks if any registered triggers match the affected triple. If a match is found, the associated callback function is executed.
26+
27+
The triple pattern in a trigger can include specific values or `None` wildcards:
28+
- If a specific value is provided, the trigger only matches triples with exactly that value in the corresponding position.
29+
- If `None` is provided, the trigger matches any value in that position.
30+
31+
For example:
32+
- `(ex:Person123, ex:hasProfile, None)` matches any triple with subject `ex:Person123` and predicate `ex:hasProfile`, regardless of the object.
33+
- `(None, rdf:type, ex:LinkedInProfile)` matches any triple with predicate `rdf:type` and object `ex:LinkedInProfile`, regardless of the subject.
34+
35+
## Example Use Case
36+
37+
Consider a LinkedIn profile ingestion workflow:
38+
39+
1. A workflow discovers a person and their LinkedIn profile URL, adding a triple like:
40+
```
41+
ex:Person123 ex:hasLinkedInProfile "https://linkedin.com/in/username"
42+
```
43+
44+
2. Instead of having this workflow also handle the profile ingestion, a separate LinkedIn ingestion pipeline registers a trigger:
45+
```python
46+
(None, ex:hasLinkedInProfile, None) # Match any triple with this predicate
47+
```
48+
49+
3. When the triple is added, the trigger automatically activates the LinkedIn ingestion pipeline, which:
50+
- Retrieves the profile URL
51+
- Scrapes the LinkedIn profile
52+
- Processes the data
53+
- Adds more detailed information to the ontology
54+
55+
This approach keeps each component focused on a single responsibility and allows the system to automatically coordinate complex workflows.
56+
57+
## How to Register Triggers in a Module
58+
59+
Triggers are registered by creating a `triggers.py` file in your module directory. This file should define a `triggers` list variable containing tuples of (triple_pattern, event_type, callback_function).
60+
61+
Here's an example of a `triggers.py` file:
62+
63+
```python
64+
from abi.services.ontology_store.OntologyStorePorts import OntologyEvent
65+
from rdflib import URIRef
66+
from .pipelines.LinkedInProfileIngestionPipeline import ingest_linkedin_profile
67+
68+
# Define the namespace
69+
ex = URIRef("http://example.org/")
70+
71+
# Define the triple pattern to watch for
72+
# (None, ex:hasLinkedInProfile, None) means "match any subject and object, but the predicate must be ex:hasLinkedInProfile"
73+
triple_pattern = (None, URIRef(ex + "hasLinkedInProfile"), None)
74+
75+
# Define the callback function
76+
def handle_new_linkedin_profile(event_type, ontology_name, triple):
77+
subject, predicate, profile_url = triple
78+
ingest_linkedin_profile(subject, profile_url)
79+
80+
# List of triggers to be registered
81+
triggers = [
82+
(triple_pattern, OntologyEvent.INSERT, handle_new_linkedin_profile)
83+
]
84+
```
85+
86+
## How Triggers are Loaded
87+
88+
The ABI system automatically loads triggers from modules during the module loading process:
89+
90+
1. The system checks if a `triggers.py` file exists in the module directory.
91+
2. If found, it imports the module and looks for a `triggers` variable.
92+
3. If the `triggers` variable exists and is a list, the system registers each trigger with the ontology store.
93+
94+
The relevant code in `Module.py` that handles this process is:
95+
96+
```python
97+
def __load_triggers(self):
98+
if os.path.exists(os.path.join(self.module_path, 'triggers.py')):
99+
module = importlib.import_module(self.module_import_path + '.triggers')
100+
if hasattr(module, 'triggers'):
101+
self.triggers = module.triggers
102+
```
103+
104+
## Best Practices for Using Triggers
105+
106+
1. **Be Specific**: Make your triple patterns as specific as possible to avoid unnecessary callback executions.
107+
2. **Keep Callbacks Lightweight**: Trigger callbacks should be quick and focused. For complex operations, consider having the callback initiate a separate process.
108+
3. **Handle Errors Gracefully**: Ensure your callback functions include proper error handling to prevent failures from affecting the ontology store.
109+
4. **Document Your Triggers**: Clearly document what triggers your module registers and what they do.
110+
5. **Avoid Circular Triggers**: Be careful not to create circular dependencies where triggers create conditions that activate other triggers indefinitely.
111+
112+
## Advanced Usage: Trigger Patterns
113+
114+
Here are some common patterns for using triggers effectively:
115+
116+
### Wildcard Matching
117+
118+
Use `None` as a wildcard to match any value in a triple position:
119+
120+
```python
121+
# Match any triple with rdf:type as predicate and ex:Person as object
122+
(None, RDF.type, ex.Person)
123+
```
124+
125+
### Chain Reactions
126+
127+
Create a chain of triggers where each step in a process triggers the next:
128+
129+
```python
130+
# Step 1: Detect new LinkedIn profile
131+
(None, ex.hasLinkedInProfile, None)
132+
133+
# Step 2: After profile data is ingested, analyze connections
134+
(None, ex.hasConnection, None)
135+
136+
# Step 3: After connections are analyzed, generate recommendations
137+
(None, ex.hasAnalyzedNetwork, None)
138+
```
139+
140+
### Conditional Processing
141+
142+
Use triggers to implement conditional logic based on the ontology state:
143+
144+
```python
145+
# Only process profiles that have both LinkedIn and Twitter
146+
def process_if_complete(event_type, ontology_name, triple):
147+
subject, _, _ = triple
148+
# Query to check if this subject also has Twitter
149+
if has_twitter_profile(subject):
150+
process_complete_profile(subject)
151+
152+
# Register the trigger
153+
(None, ex.hasLinkedInProfile, None, OntologyEvent.INSERT, process_if_complete)
154+
```
155+
156+
## Conclusion
157+
158+
Triggers are a powerful mechanism in the ABI system that enable reactive, event-driven programming based on changes in the ontology. By registering callbacks to respond to specific triple patterns, modules can create sophisticated workflows that automatically react to new information as it becomes available.
159+
160+
This approach helps keep the codebase modular, maintainable, and focused on specific responsibilities while allowing complex behaviors to emerge from the interaction of simple components.

0 commit comments

Comments
 (0)