|
| 1 | +# Event Interfaces |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Graphite uses a consistent event-driven interface pattern where components communicate exclusively through two primary event types: |
| 6 | +- **PublishToTopicEvent**: Used to publish data to topics |
| 7 | +- **ConsumeFromTopicEvent**: Used to consume data from topics |
| 8 | + |
| 9 | +This design creates a clean separation of concerns and enables loose coupling between components. |
| 10 | + |
| 11 | +## Interface Patterns |
| 12 | + |
| 13 | +### Component Communication Flow |
| 14 | + |
| 15 | +The event interface creates a bidirectional flow pattern: |
| 16 | + |
| 17 | +```mermaid |
| 18 | +graph LR |
| 19 | + A[Assistant/Workflow] -->|PublishToTopicEvent| B[Topics] |
| 20 | + B -->|ConsumeFromTopicEvent| C[Nodes] |
| 21 | + C -->|PublishToTopicEvent| D[Topics] |
| 22 | + D -->|ConsumeFromTopicEvent| A |
| 23 | +``` |
| 24 | + |
| 25 | +### Component Interfaces |
| 26 | + |
| 27 | +| Component | Input Type | Output Type | Description | |
| 28 | +|-----------|------------|-------------|-------------| |
| 29 | +| **Assistant** | `PublishToTopicEvent` | `List[ConsumeFromTopicEvent]` | Receives published events, returns consumed events | |
| 30 | +| **Workflow** | `PublishToTopicEvent` | `List[ConsumeFromTopicEvent]` | Orchestrates nodes through event flow | |
| 31 | +| **Node** | `List[ConsumeFromTopicEvent]` | `PublishToTopicEvent` | Consumes events, processes, publishes results | |
| 32 | +| **Tool** | `Messages` | `Messages` | Transforms message data (used within nodes) | |
| 33 | + |
| 34 | +## Event Structure |
| 35 | + |
| 36 | +### PublishToTopicEvent |
| 37 | + |
| 38 | +Published when a component sends data to a topic: |
| 39 | + |
| 40 | +```python |
| 41 | +from grafi.common.events.topic_events.publish_to_topic_event import PublishToTopicEvent |
| 42 | +from grafi.common.models.invoke_context import InvokeContext |
| 43 | +from grafi.common.models.message import Message |
| 44 | + |
| 45 | +event = PublishToTopicEvent( |
| 46 | + publisher_name="ProcessorNode", |
| 47 | + publisher_type="node", |
| 48 | + invoke_context=InvokeContext(session_id="session-123"), |
| 49 | + consumed_event_ids=["evt_1", "evt_2"], # Events that led to this publication |
| 50 | + data=[Message(role="assistant", content="Processed result")] |
| 51 | +) |
| 52 | +``` |
| 53 | + |
| 54 | +### ConsumeFromTopicEvent |
| 55 | + |
| 56 | +Created when data is consumed from a topic: |
| 57 | + |
| 58 | +```python |
| 59 | +from grafi.common.events.topic_events.consume_from_topic_event import ConsumeFromTopicEvent |
| 60 | + |
| 61 | +event = ConsumeFromTopicEvent( |
| 62 | + topic_name="output_topic", |
| 63 | + offset=42, |
| 64 | + publisher_name="ProcessorNode", # Original publisher |
| 65 | + publisher_type="node", |
| 66 | + invoke_context=InvokeContext(session_id="session-123"), |
| 67 | + consumed_event_ids=["evt_1", "evt_2"], |
| 68 | + data=[Message(role="assistant", content="Processed result")] |
| 69 | +) |
| 70 | +``` |
| 71 | + |
| 72 | +## Implementation Examples |
| 73 | + |
| 74 | +### Assistant Implementation |
| 75 | + |
| 76 | +```python |
| 77 | +from grafi.assistants.assistant import Assistant |
| 78 | +from grafi.common.events.topic_events.publish_to_topic_event import PublishToTopicEvent |
| 79 | +from grafi.common.events.topic_events.consume_from_topic_event import ConsumeFromTopicEvent |
| 80 | +from typing import List, AsyncGenerator |
| 81 | + |
| 82 | +class MyAssistant(Assistant): |
| 83 | + def invoke(self, input_event: PublishToTopicEvent) -> List[ConsumeFromTopicEvent]: |
| 84 | + """Synchronous processing of events.""" |
| 85 | + # Delegate to workflow |
| 86 | + events = self.workflow.invoke(input_event) |
| 87 | + return events |
| 88 | + |
| 89 | + async def a_invoke( |
| 90 | + self, |
| 91 | + input_event: PublishToTopicEvent |
| 92 | + ) -> AsyncGenerator[ConsumeFromTopicEvent, None]: |
| 93 | + """Asynchronous streaming of events.""" |
| 94 | + async for output in self.workflow.a_invoke(input_event): |
| 95 | + yield output |
| 96 | +``` |
| 97 | + |
| 98 | +### Node Implementation |
| 99 | + |
| 100 | +```python |
| 101 | +from grafi.nodes.node import Node |
| 102 | +from grafi.common.models.invoke_context import InvokeContext |
| 103 | +from grafi.common.events.topic_events.consume_from_topic_event import ConsumeFromTopicEvent |
| 104 | +from grafi.common.events.topic_events.publish_to_topic_event import PublishToTopicEvent |
| 105 | +from typing import List |
| 106 | + |
| 107 | +class ProcessorNode(Node): |
| 108 | + def invoke( |
| 109 | + self, |
| 110 | + invoke_context: InvokeContext, |
| 111 | + node_input: List[ConsumeFromTopicEvent] |
| 112 | + ) -> PublishToTopicEvent: |
| 113 | + """Process consumed events and publish result.""" |
| 114 | + # Execute command on input data |
| 115 | + response = self.command.invoke(invoke_context, node_input) |
| 116 | + |
| 117 | + # Wrap response in PublishToTopicEvent |
| 118 | + return PublishToTopicEvent( |
| 119 | + publisher_name=self.name, |
| 120 | + publisher_type=self.type, |
| 121 | + invoke_context=invoke_context, |
| 122 | + consumed_event_ids=[event.event_id for event in node_input], |
| 123 | + data=response |
| 124 | + ) |
| 125 | +``` |
| 126 | + |
| 127 | +### Workflow Integration |
| 128 | + |
| 129 | +```python |
| 130 | +from grafi.workflows.workflow import Workflow |
| 131 | +from grafi.common.events.topic_events.publish_to_topic_event import PublishToTopicEvent |
| 132 | +from grafi.common.events.topic_events.consume_from_topic_event import ConsumeFromTopicEvent |
| 133 | + |
| 134 | +class MyWorkflow(Workflow): |
| 135 | + def invoke(self, input_event: PublishToTopicEvent) -> List[ConsumeFromTopicEvent]: |
| 136 | + """Execute workflow synchronously.""" |
| 137 | + # Initialize workflow with input event |
| 138 | + self.initial_workflow(input_event) |
| 139 | + |
| 140 | + # Process nodes until completion |
| 141 | + while not self._invoke_queue.empty(): |
| 142 | + node = self._invoke_queue.get() |
| 143 | + output = node.invoke(...) |
| 144 | + # Publish output to topics |
| 145 | + |
| 146 | + # Return consumed events from output topics |
| 147 | + return self._get_output_events() |
| 148 | +``` |
| 149 | + |
| 150 | +## Benefits of Event Interfaces |
| 151 | + |
| 152 | +### 1. Loose Coupling |
| 153 | +Components don't need direct references to each other - they communicate through events and topics. |
| 154 | + |
| 155 | +### 2. Traceability |
| 156 | +Every event carries: |
| 157 | +- `invoke_context`: Request correlation information |
| 158 | +- `consumed_event_ids`: Chain of events that led to this event |
| 159 | +- Publisher information: Source component details |
| 160 | + |
| 161 | +### 3. Flexibility |
| 162 | +- Easy to add new components without modifying existing ones |
| 163 | +- Components can be tested in isolation |
| 164 | +- Workflows can be composed dynamically |
| 165 | + |
| 166 | +### 4. Observability |
| 167 | +- Complete audit trail through event chain |
| 168 | +- Easy to trace data flow through the system |
| 169 | +- Built-in support for distributed tracing |
| 170 | + |
| 171 | +### 5. Recovery |
| 172 | +- Events can be replayed from any point |
| 173 | +- Workflows can resume from interruption |
| 174 | +- State can be reconstructed from event history |
| 175 | + |
| 176 | +## Migration from Direct Invocation |
| 177 | + |
| 178 | +If migrating from older patterns that used direct method calls: |
| 179 | + |
| 180 | +**Old Pattern:** |
| 181 | +```python |
| 182 | +def invoke(self, invoke_context: InvokeContext, input_data: Messages) -> Messages: |
| 183 | + return self.workflow.invoke(invoke_context, input_data) |
| 184 | +``` |
| 185 | + |
| 186 | +**New Pattern:** |
| 187 | +```python |
| 188 | +def invoke(self, input_event: PublishToTopicEvent) -> List[ConsumeFromTopicEvent]: |
| 189 | + return self.workflow.invoke(input_event) |
| 190 | +``` |
| 191 | + |
| 192 | +Key changes: |
| 193 | +1. Input is now a single `PublishToTopicEvent` instead of separate context and data |
| 194 | +2. Output is a list of `ConsumeFromTopicEvent` objects |
| 195 | +3. Context and data are embedded within the event objects |
| 196 | +4. Event IDs enable tracing the full processing chain |
| 197 | + |
| 198 | +## Best Practices |
| 199 | + |
| 200 | +1. **Always preserve event chains**: Include `consumed_event_ids` when creating new events |
| 201 | +2. **Use descriptive names**: Set meaningful `publisher_name` values for debugging |
| 202 | +3. **Type your components**: Use proper type hints for event interfaces |
| 203 | +4. **Handle streaming properly**: Use async generators for streaming responses |
| 204 | +5. **Validate event data**: Ensure data types match expected formats |
| 205 | + |
| 206 | +## See Also |
| 207 | + |
| 208 | +- [Events System](./events/events.md) - Detailed event documentation |
| 209 | +- [Topics](./topics/topic.md) - Topic-based messaging |
| 210 | +- [Event-Driven Workflow](./event-driven-workflow.md) - Workflow orchestration |
| 211 | +- [Node](./node.md) - Node component documentation |
0 commit comments