eggai 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eggai-0.1.5/LICENSE.md +21 -0
- eggai-0.1.5/PKG-INFO +172 -0
- eggai-0.1.5/README.md +157 -0
- eggai-0.1.5/eggai/__init__.py +0 -0
- eggai-0.1.5/eggai/agent.py +129 -0
- eggai-0.1.5/eggai/channel.py +58 -0
- eggai-0.1.5/eggai/constants.py +1 -0
- eggai-0.1.5/eggai/settings/kafka.py +9 -0
- eggai-0.1.5/eggai/utils/ssl_context.py +10 -0
- eggai-0.1.5/pyproject.toml +18 -0
eggai-0.1.5/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 EggAI Technologies GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
eggai-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: eggai
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Stefano Tucci
|
|
6
|
+
Author-email: stefanotucci89@gmail.com
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: aiokafka (>=0.12.0,<0.13.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# EggAI Multi-Agent Framework 🤖
|
|
16
|
+
|
|
17
|
+
[](https://www.python.org/downloads/)
|
|
18
|
+
[](https://opensource.org/licenses/MIT)
|
|
19
|
+
[](https://github.com/eggai-tech/eggai/pulls)
|
|
20
|
+
[](https://github.com/eggai-tech/eggai/issues)
|
|
21
|
+
[](https://github.com/eggai-tech/eggai/stargazers)
|
|
22
|
+
|
|
23
|
+
`EggAI Multi-Agent Framework` is an async-first framework for building, deploying, and scaling multi-agent systems for modern enterprise environments.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Table of Contents
|
|
28
|
+
|
|
29
|
+
[Overview](#overview) •
|
|
30
|
+
[Features](#features) •
|
|
31
|
+
[Installation](#installation) •
|
|
32
|
+
[Quick Start](#quick-start) •
|
|
33
|
+
[Core Concepts](#core-concepts) •
|
|
34
|
+
[Examples](#examples) •
|
|
35
|
+
[Development](#development) •
|
|
36
|
+
[License](#license)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🌟 Overview
|
|
41
|
+
|
|
42
|
+
`EggAI` is a Python library that simplifies the development of multi-agent systems by providing a high-level abstraction over Kafka. It allows developers to focus on business logic by handling the complexities of Kafka producers and consumers.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✨ Features
|
|
47
|
+
|
|
48
|
+
- **Event-Driven Architecture**: Build systems that react to events in real-time.
|
|
49
|
+
- **Agent Orchestration**: Manage multiple agents with ease.
|
|
50
|
+
- **Kafka Integration**: Seamlessly integrate with Kafka topics.
|
|
51
|
+
- **Async-First Design**: Utilize Python's async features for high performance.
|
|
52
|
+
- **Minimal Boilerplate**: Focus on business logic without worrying about Kafka details.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
Install `EggAI` via pip:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install eggai
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
Here's how you can quickly set up an agent to handle events in an event-driven system:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
|
|
73
|
+
from eggai.agent import Agent
|
|
74
|
+
from eggai.channel import Channel
|
|
75
|
+
|
|
76
|
+
CHANNEL_NAME = "events"
|
|
77
|
+
|
|
78
|
+
agent = Agent("OrderAgent")
|
|
79
|
+
|
|
80
|
+
@agent.subscribe(channel_name=CHANNEL_NAME, event_name="order_requested")
|
|
81
|
+
async def handle_order_requested(event):
|
|
82
|
+
print(f"[ORDER AGENT]: Received order request. Event: {event}")
|
|
83
|
+
await Channel(name=CHANNEL_NAME).publish({"event_name": "order_created", "payload": event})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@agent.subscribe(channel_name=CHANNEL_NAME, event_name="order_created")
|
|
87
|
+
async def handle_order_created(event):
|
|
88
|
+
print(f"[ORDER AGENT]: Order created. Event: {event}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def main():
|
|
92
|
+
task = agent.start()
|
|
93
|
+
await asyncio.sleep(2)
|
|
94
|
+
|
|
95
|
+
await Channel(CHANNEL_NAME).publish({
|
|
96
|
+
"event_name": "order_requested",
|
|
97
|
+
"payload": {
|
|
98
|
+
"product": "Laptop",
|
|
99
|
+
"quantity": 1
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
await asyncio.sleep(2)
|
|
104
|
+
task.cancel()
|
|
105
|
+
await Channel.stop()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
try:
|
|
110
|
+
asyncio.run(main())
|
|
111
|
+
except KeyboardInterrupt:
|
|
112
|
+
print("Shutting down...")
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This code demonstrates how to define an `Agent` and use it to process events from Kafka topics.
|
|
117
|
+
|
|
118
|
+
This repository contains a few applications you can use as a reference:
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Core Concepts
|
|
123
|
+
|
|
124
|
+
### Agent
|
|
125
|
+
|
|
126
|
+
An `Agent` is responsible for subscribing to Kafka topics, processing events, and orchestrating tasks using user-defined handlers. The key features of the `Agent` class include:
|
|
127
|
+
|
|
128
|
+
- **Event Subscription**: Bind event handlers to specific events using the `subscribe` decorator.
|
|
129
|
+
- **Lifecycle Management**: Manage producer and consumer lifecycles seamlessly.
|
|
130
|
+
- **Minimal Boilerplate**: Focus on business logic without worrying about Kafka details.
|
|
131
|
+
|
|
132
|
+
### Channel
|
|
133
|
+
|
|
134
|
+
A `Channel` abstracts the Kafka producer interface to publish events to specific Kafka topics. Key features include:
|
|
135
|
+
|
|
136
|
+
- **Event Publishing**: Send events to Kafka topics easily.
|
|
137
|
+
- **Singleton Producers**: Manage Kafka producers efficiently across multiple channels.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Examples
|
|
142
|
+
|
|
143
|
+
For detailed examples, please refer to [examples](examples).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
### Setting Up
|
|
150
|
+
|
|
151
|
+
Clone the repository and install the development dependencies:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git clone https://github.com/eggai-tech/eggai.git
|
|
155
|
+
cd eggai
|
|
156
|
+
pip install -r requirements.txt
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Running Tests
|
|
160
|
+
|
|
161
|
+
Run the tests using:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
pytest
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
This project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for details.
|
|
172
|
+
|
eggai-0.1.5/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# EggAI Multi-Agent Framework 🤖
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/eggai-tech/eggai/pulls)
|
|
6
|
+
[](https://github.com/eggai-tech/eggai/issues)
|
|
7
|
+
[](https://github.com/eggai-tech/eggai/stargazers)
|
|
8
|
+
|
|
9
|
+
`EggAI Multi-Agent Framework` is an async-first framework for building, deploying, and scaling multi-agent systems for modern enterprise environments.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
[Overview](#overview) •
|
|
16
|
+
[Features](#features) •
|
|
17
|
+
[Installation](#installation) •
|
|
18
|
+
[Quick Start](#quick-start) •
|
|
19
|
+
[Core Concepts](#core-concepts) •
|
|
20
|
+
[Examples](#examples) •
|
|
21
|
+
[Development](#development) •
|
|
22
|
+
[License](#license)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🌟 Overview
|
|
27
|
+
|
|
28
|
+
`EggAI` is a Python library that simplifies the development of multi-agent systems by providing a high-level abstraction over Kafka. It allows developers to focus on business logic by handling the complexities of Kafka producers and consumers.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✨ Features
|
|
33
|
+
|
|
34
|
+
- **Event-Driven Architecture**: Build systems that react to events in real-time.
|
|
35
|
+
- **Agent Orchestration**: Manage multiple agents with ease.
|
|
36
|
+
- **Kafka Integration**: Seamlessly integrate with Kafka topics.
|
|
37
|
+
- **Async-First Design**: Utilize Python's async features for high performance.
|
|
38
|
+
- **Minimal Boilerplate**: Focus on business logic without worrying about Kafka details.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Install `EggAI` via pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install eggai
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
Here's how you can quickly set up an agent to handle events in an event-driven system:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import asyncio
|
|
58
|
+
|
|
59
|
+
from eggai.agent import Agent
|
|
60
|
+
from eggai.channel import Channel
|
|
61
|
+
|
|
62
|
+
CHANNEL_NAME = "events"
|
|
63
|
+
|
|
64
|
+
agent = Agent("OrderAgent")
|
|
65
|
+
|
|
66
|
+
@agent.subscribe(channel_name=CHANNEL_NAME, event_name="order_requested")
|
|
67
|
+
async def handle_order_requested(event):
|
|
68
|
+
print(f"[ORDER AGENT]: Received order request. Event: {event}")
|
|
69
|
+
await Channel(name=CHANNEL_NAME).publish({"event_name": "order_created", "payload": event})
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@agent.subscribe(channel_name=CHANNEL_NAME, event_name="order_created")
|
|
73
|
+
async def handle_order_created(event):
|
|
74
|
+
print(f"[ORDER AGENT]: Order created. Event: {event}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def main():
|
|
78
|
+
task = agent.start()
|
|
79
|
+
await asyncio.sleep(2)
|
|
80
|
+
|
|
81
|
+
await Channel(CHANNEL_NAME).publish({
|
|
82
|
+
"event_name": "order_requested",
|
|
83
|
+
"payload": {
|
|
84
|
+
"product": "Laptop",
|
|
85
|
+
"quantity": 1
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await asyncio.sleep(2)
|
|
90
|
+
task.cancel()
|
|
91
|
+
await Channel.stop()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
try:
|
|
96
|
+
asyncio.run(main())
|
|
97
|
+
except KeyboardInterrupt:
|
|
98
|
+
print("Shutting down...")
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This code demonstrates how to define an `Agent` and use it to process events from Kafka topics.
|
|
103
|
+
|
|
104
|
+
This repository contains a few applications you can use as a reference:
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Core Concepts
|
|
109
|
+
|
|
110
|
+
### Agent
|
|
111
|
+
|
|
112
|
+
An `Agent` is responsible for subscribing to Kafka topics, processing events, and orchestrating tasks using user-defined handlers. The key features of the `Agent` class include:
|
|
113
|
+
|
|
114
|
+
- **Event Subscription**: Bind event handlers to specific events using the `subscribe` decorator.
|
|
115
|
+
- **Lifecycle Management**: Manage producer and consumer lifecycles seamlessly.
|
|
116
|
+
- **Minimal Boilerplate**: Focus on business logic without worrying about Kafka details.
|
|
117
|
+
|
|
118
|
+
### Channel
|
|
119
|
+
|
|
120
|
+
A `Channel` abstracts the Kafka producer interface to publish events to specific Kafka topics. Key features include:
|
|
121
|
+
|
|
122
|
+
- **Event Publishing**: Send events to Kafka topics easily.
|
|
123
|
+
- **Singleton Producers**: Manage Kafka producers efficiently across multiple channels.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Examples
|
|
128
|
+
|
|
129
|
+
For detailed examples, please refer to [examples](examples).
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
### Setting Up
|
|
136
|
+
|
|
137
|
+
Clone the repository and install the development dependencies:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/eggai-tech/eggai.git
|
|
141
|
+
cd eggai
|
|
142
|
+
pip install -r requirements.txt
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Running Tests
|
|
146
|
+
|
|
147
|
+
Run the tests using:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pytest
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
This project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for details.
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from typing import Dict, List, Callable
|
|
4
|
+
|
|
5
|
+
from aiokafka import AIOKafkaProducer, AIOKafkaConsumer
|
|
6
|
+
|
|
7
|
+
from eggai.constants import DEFAULT_CHANNEL_NAME
|
|
8
|
+
from eggai.settings.kafka import KafkaSettings
|
|
9
|
+
|
|
10
|
+
class Agent:
|
|
11
|
+
"""
|
|
12
|
+
A message-based agent for subscribing to events and handling messages with user-defined functions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, name: str):
|
|
16
|
+
"""
|
|
17
|
+
Initialize the Agent.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name (str): The name of the agent.
|
|
21
|
+
"""
|
|
22
|
+
self.name = name
|
|
23
|
+
self.kafka_settings = KafkaSettings()
|
|
24
|
+
self.producer = None
|
|
25
|
+
self.consumer = None
|
|
26
|
+
self.handlers: Dict[str, List[Callable]] = {} # Maps channel:event_name to list of handlers
|
|
27
|
+
self.channels = set() # Keep track of all subscribed channels
|
|
28
|
+
self.running_task = None # Task to manage the lifecycle of the agent
|
|
29
|
+
|
|
30
|
+
def subscribe(self, event_name: str, channel_name: str = DEFAULT_CHANNEL_NAME):
|
|
31
|
+
"""
|
|
32
|
+
Decorator for subscribing to a channel and binding a handler to an event name.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
event_name (str): Name of the event within the channel.
|
|
36
|
+
channel_name (str): The name of the channel.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Callable: The decorator for wrapping the handler function.
|
|
40
|
+
"""
|
|
41
|
+
def decorator(func: Callable):
|
|
42
|
+
topic_event = f"{channel_name}:{event_name}"
|
|
43
|
+
if topic_event not in self.handlers:
|
|
44
|
+
self.handlers[topic_event] = []
|
|
45
|
+
self.handlers[topic_event].append(func) # Allow multiple handlers for the same event
|
|
46
|
+
self.channels.add(channel_name) # Register the channel name
|
|
47
|
+
return func
|
|
48
|
+
return decorator
|
|
49
|
+
|
|
50
|
+
async def _start_producer(self):
|
|
51
|
+
"""
|
|
52
|
+
Start the message producer.
|
|
53
|
+
"""
|
|
54
|
+
self.producer = AIOKafkaProducer(
|
|
55
|
+
bootstrap_servers=self.kafka_settings.BOOTSTRAP_SERVERS
|
|
56
|
+
)
|
|
57
|
+
await self.producer.start()
|
|
58
|
+
|
|
59
|
+
async def _start_consumer(self):
|
|
60
|
+
"""
|
|
61
|
+
Start the message consumer and process incoming messages.
|
|
62
|
+
"""
|
|
63
|
+
self.consumer = AIOKafkaConsumer(
|
|
64
|
+
*self.channels, # Use the registered channels
|
|
65
|
+
bootstrap_servers=self.kafka_settings.BOOTSTRAP_SERVERS,
|
|
66
|
+
#group_id=f"{self.name}_group",
|
|
67
|
+
auto_offset_reset="latest",
|
|
68
|
+
)
|
|
69
|
+
await self.consumer.start()
|
|
70
|
+
|
|
71
|
+
print(f"Agent '{self.name}' is ready to receive messages. Channels: {self.channels}")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
async for msg in self.consumer:
|
|
75
|
+
channel_name = msg.topic
|
|
76
|
+
try:
|
|
77
|
+
message = json.loads(msg.value.decode("utf-8"))
|
|
78
|
+
event_name = message.get("event_name")
|
|
79
|
+
payload = message.get("payload")
|
|
80
|
+
|
|
81
|
+
topic_event = f"{channel_name}:{event_name}"
|
|
82
|
+
if topic_event in self.handlers:
|
|
83
|
+
# Call all handlers for this event
|
|
84
|
+
for handler in self.handlers[topic_event]:
|
|
85
|
+
await handler(payload)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"Failed to process message from {channel_name}: {e}")
|
|
88
|
+
except asyncio.CancelledError:
|
|
89
|
+
pass
|
|
90
|
+
finally:
|
|
91
|
+
print(f"Stopping agent '{self.name}'...")
|
|
92
|
+
await self.consumer.stop()
|
|
93
|
+
print(f"Agent '{self.name}' stopped.")
|
|
94
|
+
|
|
95
|
+
async def _lifecycle(self):
|
|
96
|
+
"""
|
|
97
|
+
Internal lifecycle management for the agent.
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
await self._start_producer()
|
|
101
|
+
await self._start_consumer()
|
|
102
|
+
finally:
|
|
103
|
+
await self.producer.stop()
|
|
104
|
+
|
|
105
|
+
async def run(self):
|
|
106
|
+
"""
|
|
107
|
+
Run the agent lifecycle, blocking until it is stopped.
|
|
108
|
+
"""
|
|
109
|
+
if self.running_task:
|
|
110
|
+
raise RuntimeError("Agent is already running.")
|
|
111
|
+
try:
|
|
112
|
+
self.running_task = asyncio.create_task(self._lifecycle())
|
|
113
|
+
await self.running_task # Block until the lifecycle completes
|
|
114
|
+
except asyncio.CancelledError:
|
|
115
|
+
print("Agent lifecycle was cancelled.")
|
|
116
|
+
finally:
|
|
117
|
+
self.running_task = None
|
|
118
|
+
|
|
119
|
+
async def stop(self):
|
|
120
|
+
"""
|
|
121
|
+
Stop the agent gracefully by cancelling the running task.
|
|
122
|
+
"""
|
|
123
|
+
if not self.running_task:
|
|
124
|
+
raise RuntimeError("Agent is not running.")
|
|
125
|
+
self.running_task.cancel()
|
|
126
|
+
try:
|
|
127
|
+
await self.running_task # Ensure the task finishes
|
|
128
|
+
except asyncio.CancelledError:
|
|
129
|
+
pass # Task was cancelled
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from aiokafka import AIOKafkaProducer
|
|
4
|
+
|
|
5
|
+
from eggai.constants import DEFAULT_CHANNEL_NAME
|
|
6
|
+
from eggai.settings.kafka import KafkaSettings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Channel:
|
|
10
|
+
"""
|
|
11
|
+
A standalone class to send events to Kafka channels.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
_producers = {} # Singleton dictionary to hold producers for each channel
|
|
15
|
+
|
|
16
|
+
def __init__(self, name: str = DEFAULT_CHANNEL_NAME):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the Channel.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
name (str): The name of the Kafka channel (topic).
|
|
22
|
+
"""
|
|
23
|
+
self.name = name
|
|
24
|
+
self.kafka_settings = KafkaSettings()
|
|
25
|
+
|
|
26
|
+
async def _get_producer(self):
|
|
27
|
+
"""
|
|
28
|
+
Get or create a Kafka producer for the channel.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
AIOKafkaProducer: Kafka producer for the channel.
|
|
32
|
+
"""
|
|
33
|
+
if self.name not in Channel._producers:
|
|
34
|
+
producer = AIOKafkaProducer(
|
|
35
|
+
bootstrap_servers=self.kafka_settings.BOOTSTRAP_SERVERS,
|
|
36
|
+
)
|
|
37
|
+
await producer.start()
|
|
38
|
+
Channel._producers[self.name] = producer
|
|
39
|
+
return Channel._producers[self.name]
|
|
40
|
+
|
|
41
|
+
async def publish(self, event: dict):
|
|
42
|
+
"""
|
|
43
|
+
Publish an event to the Kafka channel.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
event (dict): The event payload.
|
|
47
|
+
"""
|
|
48
|
+
producer = await self._get_producer()
|
|
49
|
+
await producer.send_and_wait(self.name, json.dumps(event).encode("utf-8"))
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
async def stop():
|
|
53
|
+
"""
|
|
54
|
+
Close all Kafka producers in the singleton list.
|
|
55
|
+
"""
|
|
56
|
+
for producer in Channel._producers.values():
|
|
57
|
+
await producer.stop()
|
|
58
|
+
Channel._producers.clear()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT_CHANNEL_NAME = "eggai.events"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class KafkaSettings:
|
|
7
|
+
BOOTSTRAP_SERVERS: str = os.getenv("KAFKA_BOOTSTRAP_SERVERS", "localhost:19092")
|
|
8
|
+
USE_SSL: bool = os.getenv("KAFKA_USE_SSL", False)
|
|
9
|
+
CA_CONTENT: str | None = os.getenv("KAFKA_CA_CONTENT", None)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
from aiokafka.helpers import create_ssl_context
|
|
3
|
+
|
|
4
|
+
def create_ssl_context_from_settings(ca_content: str) -> ssl.SSLContext:
|
|
5
|
+
context = create_ssl_context()
|
|
6
|
+
context.check_hostname = False
|
|
7
|
+
context.verify_mode = ssl.CERT_NONE
|
|
8
|
+
|
|
9
|
+
context.load_verify_locations(cadata=ca_content)
|
|
10
|
+
return context
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "eggai"
|
|
3
|
+
version = "0.1.5"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["Stefano Tucci <stefanotucci89@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.10"
|
|
10
|
+
aiokafka = "^0.12.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[tool.poetry.group.dev.dependencies]
|
|
14
|
+
twine = "^6.0.1"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["poetry-core"]
|
|
18
|
+
build-backend = "poetry.core.masonry.api"
|