Features
Fabricatio provides a powerful set of features designed to streamline the development of LLM applications through its event-driven architecture, seamless LLM integration, and extensible async framework.
Event-Driven Architecture
Fabricatio’s core architecture is built around an event-driven model that promotes loose coupling and high cohesion between components. This pattern allows for scalable, maintainable, and flexible application development.
EventEmitter Pattern
At the heart of Fabricatio’s event-driven architecture is the EventEmitter class, which provides a robust mechanism for managing event handling with both exact and wildcard event matching capabilities.
The EventEmitter supports:
Exact Event Matching: Direct registration and emission of specific events
Wildcard Event Matching: Pattern-based event handling using wildcards (
*) for flexible event routingConcurrent Event Processing: Multiple event handlers can be executed concurrently for improved performance
Hierarchical Event Structure: Events can be organized in hierarchical namespaces using configurable separators
Key features of the EventEmitter pattern include:
from fabricatio import EventEmitter
# Create an event emitter with custom separator
emitter = EventEmitter(sep="::")
# Register handlers for exact events
emitter.on("user::login", login_handler)
# Register handlers with wildcards for pattern matching
emitter.on("user::*", user_activity_handler)
# Emit events with data
await emitter.emit("user::login", user_data)
Task Management Workflows
Fabricatio’s event-driven architecture excels at managing complex task workflows through its sophisticated task lifecycle management system.
Task Lifecycle Management
Tasks in Fabricatio progress through well-defined states:
Pending: Task has been created but not yet started
Running: Task is currently being processed
Finished: Task completed successfully
Failed: Task encountered an error during processing
Cancelled: Task was cancelled before completion
Each state transition triggers corresponding events that can be handled by registered workflows:
# Task state transitions emit events
task = Task(name="process_data")
# Emits "work::process_data::Pending"
task.publish("work")
# Emits "work::process_data::Running"
await task.start()
# Emits "work::process_data::Finished"
await task.finish(result)
Event-Based Workflow Registration
Roles in Fabricatio register workflows to handle specific events, creating a flexible routing system:
role = Role.with_bio().subscribe(
Event.quick_instantiate("process"), # Matches "process::*::pending"
WorkFlow(name="data_processor", steps=(Validate, Process, Store))
).dispatch()
Benefits of Event-Driven Design
Fabricatio’s event-driven architecture provides several key advantages:
- Scalability
Decoupled components can be scaled independently
Event-based communication supports distributed processing
Concurrent event handling maximizes resource utilization
- Flexibility
New functionality can be added through event handlers without modifying existing code
Wildcard event matching enables generic handlers for related events
Dynamic workflow registration allows runtime configuration
- Maintainability
Loose coupling reduces dependencies between system components
Clear event boundaries make code easier to understand and debug
Standardized event patterns promote consistency across applications
- Extensibility
Event handlers can be added or removed without system downtime
Plugin architecture supports modular feature development
Event inheritance enables specialized handling of generic events
LLM Integration & Templating
Fabricatio provides seamless integration with Large Language Models through its sophisticated templating system and unified LLM interface.
Handlebars Templating
Fabricatio leverages Handlebars as its primary templating engine, providing a powerful and familiar way to generate dynamic content for LLM interactions. The TemplateManager wraps the high-performance handlebars-rust engine for efficient template rendering.
Template Features
The templating system supports:
Logic-less Templates: Clean separation between presentation and logic
Helper Functions: Extensible template functionality through custom helpers
Partials: Reusable template components for consistent formatting
Context-aware Rendering: Templates can access complex data structures
Template Management
Fabricatio’s template system provides flexible template discovery and management:
from fabricatio_core.rust import TEMPLATE_MANAGER, CONFIG
# Templates are automatically discovered from configured directories
templates_dir = CONFIG.template_manager.template_stores[0]
# Render templates with context data
result = TEMPLATE_MANAGER.render_template(
"task_briefing",
task.model_dump()
)
Dynamic Content Generation
Fabricatio’s LLM integration enables sophisticated dynamic content generation through its action-based workflow system.
LLM Configuration
The system provides comprehensive LLM configuration through multiple sources with clear priority ordering:
# Configuration priority: Call Arguments > .env > Environment Variables >
# fabricatio.toml > pyproject.toml > <ROAMING>/fabricatio/fabricatio.toml > Defaults
from fabricatio_core.rust import CONFIG
llm_config = CONFIG.llm
print(f"Using model: {llm_config.model}")
print(f"Temperature: {llm_config.temperature}")
Action-based LLM Interaction
Actions in Fabricatio can seamlessly interact with LLMs through built-in methods:
class ContentGenerator(Action):
"""Action that generates content using LLM."""
output_key: str = "generated_content"
async def _execute(self, task_input: Task, **context) -> str:
# Generate content using LLM with templated prompt
prompt = TEMPLATE_MANAGER.render_template(
"content_generation_prompt",
{"task": task_input.model_dump(), "context": context}
)
# Interact with LLM through unified interface
response = await self.aask(prompt)
return response
Template-driven Prompt Engineering
Fabricatio’s templating system enables sophisticated prompt engineering:
Async & Extensible
Fabricatio is built from the ground up with asynchronous execution and extensibility as core principles, enabling high-performance LLM applications that can be easily customized and extended.
Asynchronous Execution Model
Fabricatio leverages Python’s async/await syntax throughout its architecture to provide non-blocking, concurrent execution of tasks and workflows.
Concurrent Task Processing
The framework supports concurrent execution of multiple tasks:
import asyncio
from fabricatio import Task
async def process_multiple_tasks():
# Create multiple tasks
tasks = [
Task(name=f"task_{i}", description=f"Process item {i}")
for i in range(10)
]
# Delegate all tasks concurrently
results = await asyncio.gather(*[
task.delegate("processing") for task in tasks
])
return results
Non-blocking Action Execution
All actions in Fabricatio are designed to be non-blocking:
class AsyncAction(Action):
"""Example of an async action."""
async def _execute(self, **context):
# Non-blocking I/O operations
data = await fetch_external_data()
# Concurrent processing
processed_data = await process_data_concurrently(data)
return processed_data
Event Loop Integration
Fabricatio integrates seamlessly with Python’s asyncio event loop:
# Blocking execution for simple cases
result = Task(name="simple").delegate_blocking("workflow")
# Async execution for complex scenarios
async def main():
result = await Task(name="async").delegate("workflow")
return result
asyncio.run(main())
Extension Mechanisms
Fabricatio provides multiple extension points that allow developers to customize and extend the framework’s functionality.
Custom Actions
Developers can create custom actions by subclassing the Action base class:
from fabricatio import Action
class CustomProcessing(Action):
"""Custom action for specialized processing."""
# Override context variables with values from context
ctx_override: bool = True
# Store output in context under this key
output_key: str = "processed_data"
# Custom attributes that can be overridden from context
threshold: float = 0.5
max_retries: int = 3
async def _execute(self, task_input: Task, **context) -> Any:
# Custom implementation
result = await self.process_with_custom_logic(
task_input,
threshold=self.threshold,
max_retries=self.max_retries
)
return result
Custom Workflows
Workflows can be customized to implement specific processing patterns:
from fabricatio import WorkFlow
class CustomWorkflow(WorkFlow):
"""Custom workflow with specialized behavior."""
# Custom initialization context
extra_init_context = {
"custom_helper": my_helper_function,
"default_config": {"timeout": 30}
}
async def serve(self, task: Task) -> None:
# Custom workflow logic before calling super().serve(task)
await self.pre_process(task)
await super().serve(task)
await self.post_process(task)
Plugin Architecture
Fabricatio supports a plugin architecture through its modular design:
# Create specialized roles with custom capabilities
class SpecializedRole(Role):
"""Role with domain-specific capabilities."""
def __init__(self):
super().__init__(
name="specialized_processor",
description="Handles domain-specific processing tasks"
)
# Register domain-specific workflows
self.subscribe(
Event.quick_instantiate("domain_process"),
DomainWorkflow(steps=(Validate, Process, DomainSpecificAction))
)
Workflow Customization
Fabricatio provides extensive customization options for workflow behavior and task processing.
Context Management
Workflows provide sophisticated context management for sharing data between actions:
# Initialize workflow with custom context
workflow = WorkFlow(
name="custom_processor",
steps=(Action1, Action2, Action3),
extra_init_context={
"api_client": get_api_client(),
"database_connection": get_db_connection(),
"custom_config": load_config()
}
)
# Update context dynamically
workflow.update_init_context(new_setting="value")
Error Handling and Recovery
Custom workflows can implement sophisticated error handling:
class ResilientWorkflow(WorkFlow):
"""Workflow with enhanced error handling."""
async def serve(self, task: Task) -> None:
try:
await super().serve(task)
except SpecificException as e:
await self.handle_specific_error(task, e)
except Exception as e:
await self.handle_generic_error(task, e)
raise
Dynamic Workflow Composition
Workflows can be composed dynamically at runtime:
def create_workflow_for_task(task_type: str) -> WorkFlow:
"""Factory function for creating type-specific workflows."""
if task_type == "analysis":
steps = (ValidateInput, AnalyzeData, GenerateReport)
elif task_type == "generation":
steps = (PrepareContext, GenerateContent, ReviewOutput)
else:
steps = (DefaultValidate, DefaultProcess)
return WorkFlow(
name=f"{task_type}_processor",
steps=steps
)