agentkit 0.0.1__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.
- agentkit-0.0.1/.gitignore +2 -0
- agentkit-0.0.1/LICENSE +21 -0
- agentkit-0.0.1/PKG-INFO +78 -0
- agentkit-0.0.1/README.md +39 -0
- agentkit-0.0.1/agentkit/__init__.py +0 -0
- agentkit-0.0.1/agentkit/agents/__init__.py +0 -0
- agentkit-0.0.1/agentkit/agents/simple_agent.py +101 -0
- agentkit-0.0.1/agentkit/brains/__init__.py +0 -0
- agentkit-0.0.1/agentkit/brains/simple_brain.py +36 -0
- agentkit-0.0.1/agentkit/databus.py +57 -0
- agentkit-0.0.1/agentkit/examples/config/simple_chat.json +9 -0
- agentkit-0.0.1/agentkit/examples/human_agent.py +55 -0
- agentkit-0.0.1/agentkit/examples/simple_chat_agent.py +86 -0
- agentkit-0.0.1/agentkit/handlers.py +10 -0
- agentkit-0.0.1/agentkit/io/__init__.py +0 -0
- agentkit-0.0.1/agentkit/io/console.py +18 -0
- agentkit-0.0.1/agentkit/memory/__init__.py +0 -0
- agentkit-0.0.1/agentkit/memory/memory_protocol.py +11 -0
- agentkit-0.0.1/agentkit/memory/simple_memory.py +23 -0
- agentkit-0.0.1/agentkit/messages.py +45 -0
- agentkit-0.0.1/agentkit/network.py +67 -0
- agentkit-0.0.1/agentkit/processor.py +74 -0
- agentkit-0.0.1/pyproject.toml +28 -0
agentkit-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Vik Kumar
|
|
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.
|
agentkit-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: agentkit
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A simple framework for creating distributed llm agent swarms
|
|
5
|
+
Project-URL: Repository, https://github.com/japanvik/agentkit
|
|
6
|
+
Author-email: Vikram Kumar <vik@japanvik.net>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2024 Vik Kumar
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Keywords: artificial intelligencs,llm,pubsub
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Programming Language :: Python
|
|
32
|
+
Requires-Python: >=3.8
|
|
33
|
+
Requires-Dist: fastapi
|
|
34
|
+
Requires-Dist: litellm
|
|
35
|
+
Requires-Dist: pydantic
|
|
36
|
+
Requires-Dist: uvicorn
|
|
37
|
+
Requires-Dist: zmq
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# AgentKit
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
`AgentKit` is a simple framework designed for creating distributed LLM (Large Language Model) agent swarms. It provides an easy-to-use interface for managing communication and data processing among distributed agents, leveraging the power of modern libraries such as `zmq` for messaging, `fastapi` and `uvicorn` for web APIs, `pydantic` for data validation, and `litellm` for language model interactions.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
Install `AgentKit` using pip:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install agentkit
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Ensure you have Python 3.8 or higher installed to meet the compatibility requirements.
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
Look at the examples directory for a simple chat agent. More documentation will come!
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- Easy integration with ZeroMQ for efficient messaging.
|
|
63
|
+
- Web API support through FastAPI and Uvicorn.
|
|
64
|
+
- Robust data validation with Pydantic.
|
|
65
|
+
- Seamless interaction with large language models via Litellm.
|
|
66
|
+
- Designed for scalability and flexibility in distributed environments.
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
TBD!
|
|
71
|
+
|
|
72
|
+
## Contributing
|
|
73
|
+
|
|
74
|
+
We welcome contributions! If you're interested in improving `AgentKit`, please check out our issues section on GitHub.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
`AgentKit` is licensed under the MIT License. See the LICENSE file in the source code for more information.
|
agentkit-0.0.1/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AgentKit
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`AgentKit` is a simple framework designed for creating distributed LLM (Large Language Model) agent swarms. It provides an easy-to-use interface for managing communication and data processing among distributed agents, leveraging the power of modern libraries such as `zmq` for messaging, `fastapi` and `uvicorn` for web APIs, `pydantic` for data validation, and `litellm` for language model interactions.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install `AgentKit` using pip:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install agentkit
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Ensure you have Python 3.8 or higher installed to meet the compatibility requirements.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
Look at the examples directory for a simple chat agent. More documentation will come!
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- Easy integration with ZeroMQ for efficient messaging.
|
|
24
|
+
- Web API support through FastAPI and Uvicorn.
|
|
25
|
+
- Robust data validation with Pydantic.
|
|
26
|
+
- Seamless interaction with large language models via Litellm.
|
|
27
|
+
- Designed for scalability and flexibility in distributed environments.
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
TBD!
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
We welcome contributions! If you're interested in improving `AgentKit`, please check out our issues section on GitHub.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
`AgentKit` is licensed under the MIT License. See the LICENSE file in the source code for more information.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import requests
|
|
4
|
+
from typing import Any
|
|
5
|
+
from agentkit.messages import Message, MessageType
|
|
6
|
+
from agentkit.network import MessageSender
|
|
7
|
+
from agentkit.handlers import default_handle_helo_message
|
|
8
|
+
|
|
9
|
+
class SimpleAgent:
|
|
10
|
+
def __init__(self, config:dict, message_sender: MessageSender, name:str="", description:str=""):
|
|
11
|
+
self.config:dict = config
|
|
12
|
+
self.message_sender: MessageSender = message_sender
|
|
13
|
+
# Get these from instatiation or config
|
|
14
|
+
self.name:str = self.get_config_value("name", name)
|
|
15
|
+
self.description:str = self.get_config_value("description", description)
|
|
16
|
+
|
|
17
|
+
self.message_queue = asyncio.Queue()
|
|
18
|
+
self.running = True
|
|
19
|
+
self.tasks = {}
|
|
20
|
+
self.message_handlers = {
|
|
21
|
+
MessageType.HELO: default_handle_helo_message
|
|
22
|
+
}
|
|
23
|
+
self.attention: str = "ALL"
|
|
24
|
+
# Add default tasks
|
|
25
|
+
self.add_task("message_dispatcher", self.message_dispatcher())
|
|
26
|
+
self.send_helo()
|
|
27
|
+
|
|
28
|
+
def get_config_value(self, parameter_name, override):
|
|
29
|
+
#
|
|
30
|
+
if override:
|
|
31
|
+
return override
|
|
32
|
+
else:
|
|
33
|
+
if parameter_name in self.config['agent'].keys():
|
|
34
|
+
return self.config['agent'][parameter_name]
|
|
35
|
+
else:
|
|
36
|
+
raise ValueError(f'Required Parameter "{parameter_name}" is not defined in the config file or instance creation')
|
|
37
|
+
|
|
38
|
+
def add_task(self, task_name, coro):
|
|
39
|
+
"""Add a new task and start it."""
|
|
40
|
+
if task_name in self.tasks:
|
|
41
|
+
self.remove_task(task_name) # Cancel and remove existing task with the same name
|
|
42
|
+
task = asyncio.create_task(coro)
|
|
43
|
+
logging.info(f"Starting task:{task_name}")
|
|
44
|
+
self.tasks[task_name] = task
|
|
45
|
+
|
|
46
|
+
async def remove_task(self, task_name):
|
|
47
|
+
"""Cancel and remove a task."""
|
|
48
|
+
task = self.tasks.pop(task_name, None)
|
|
49
|
+
if task:
|
|
50
|
+
task.cancel()
|
|
51
|
+
try:
|
|
52
|
+
await task
|
|
53
|
+
except asyncio.CancelledError:
|
|
54
|
+
pass # Task cancellation is expected
|
|
55
|
+
|
|
56
|
+
def add_message_handler(self, message_type:MessageType, handler) -> None:
|
|
57
|
+
self.message_handlers[message_type] = handler
|
|
58
|
+
logging.info(f"Added handler for message type: {message_type}")
|
|
59
|
+
return handler
|
|
60
|
+
|
|
61
|
+
async def stop_all_tasks(self):
|
|
62
|
+
"""Stop all running tasks."""
|
|
63
|
+
for task_name in list(self.tasks.keys()):
|
|
64
|
+
await self.remove_task(task_name)
|
|
65
|
+
|
|
66
|
+
async def handle_message(self, message: Message) -> Any:
|
|
67
|
+
logging.info(f"Received message: {message}")
|
|
68
|
+
await self.message_queue.put(message)
|
|
69
|
+
|
|
70
|
+
async def message_dispatcher(self):
|
|
71
|
+
while self.running:
|
|
72
|
+
message = await self.message_queue.get()
|
|
73
|
+
handler = self.message_handlers.get(message.message_type)
|
|
74
|
+
if handler:
|
|
75
|
+
await handler(self, message)
|
|
76
|
+
await asyncio.sleep(0.1)
|
|
77
|
+
self.message_queue.task_done()
|
|
78
|
+
|
|
79
|
+
def is_intended_for_me(self, message: Message) -> bool:
|
|
80
|
+
# Subscriber Implementation
|
|
81
|
+
for_me = message.to == self.name or message.to == "ALL"
|
|
82
|
+
chat_by_me = message.source == self.name and message.message_type == "CHAT" # for storing as history
|
|
83
|
+
not_my_helo = message.source != self.name and message.message_type == "HELO"
|
|
84
|
+
return for_me or not_my_helo or chat_by_me
|
|
85
|
+
|
|
86
|
+
def send_helo(self)->requests.Response:
|
|
87
|
+
"""Send the HELO message"""
|
|
88
|
+
msg = Message(source=self.name, to="ALL", content=self.description, message_type=MessageType.HELO)
|
|
89
|
+
return self.send_message(msg)
|
|
90
|
+
|
|
91
|
+
def send_message(self, message: Message):
|
|
92
|
+
return self.message_sender.send_message(message)
|
|
93
|
+
|
|
94
|
+
async def start(self):
|
|
95
|
+
while self.running:
|
|
96
|
+
await asyncio.sleep(1)
|
|
97
|
+
await self.stop()
|
|
98
|
+
|
|
99
|
+
async def stop(self):
|
|
100
|
+
await self.stop_all_tasks()
|
|
101
|
+
self.running = False
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
from agentkit.memory.memory_protocol import Memory
|
|
3
|
+
from agentkit.messages import Message, MessageType
|
|
4
|
+
from agentkit.processor import llm_processor, remove_emojis
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SimpleBrain:
|
|
8
|
+
def __init__(self, name:str, description:str, model:str, memory_manger:Memory, system_prompt:str="", user_prompt:str="") -> None:
|
|
9
|
+
self.name = name
|
|
10
|
+
self.description = description
|
|
11
|
+
self.model = model
|
|
12
|
+
self.memory_manager = memory_manger
|
|
13
|
+
self.system_prompt = system_prompt
|
|
14
|
+
self.user_prompt = user_prompt
|
|
15
|
+
|
|
16
|
+
async def handle_chat_message(self, agent, message:Message):
|
|
17
|
+
self.memory_manager.remember(message)
|
|
18
|
+
if message.source != agent.name:
|
|
19
|
+
# Reply to a chat message from someone
|
|
20
|
+
agent.attention = message.source
|
|
21
|
+
response = await self.create_chat_message(agent)
|
|
22
|
+
agent.send_message(response)
|
|
23
|
+
|
|
24
|
+
async def create_chat_message(self, agent)->Message:
|
|
25
|
+
"""prepare the CHAT message"""
|
|
26
|
+
context = self.memory_manager.get_chat_context(target=agent.attention)
|
|
27
|
+
system_prompt = self.system_prompt.format(name=self.name, description=self.description, context=context, target=agent.attention)
|
|
28
|
+
user_prompt = self.user_prompt.format(name=self.name, description=self.description, context=context, target=agent.attention)
|
|
29
|
+
reply = await llm_processor(llm_model=self.model, system_prompt=system_prompt, user_prompt=user_prompt, stop=["\n", "<|im_end|>"])
|
|
30
|
+
# Do some formatting
|
|
31
|
+
reply = reply.replace(f"{self.name}:", "").strip()
|
|
32
|
+
reply = reply.replace(f"{agent.attention}:", "").strip()
|
|
33
|
+
reply = remove_emojis(reply)
|
|
34
|
+
msg = Message(source=self.name, to=agent.attention, content=reply, message_type=MessageType.CHAT)
|
|
35
|
+
return msg
|
|
36
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
import zmq
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
|
|
7
|
+
from agentkit.messages import Message, MessageType
|
|
8
|
+
|
|
9
|
+
app = FastAPI()
|
|
10
|
+
|
|
11
|
+
# ZeroMQ Publisher setup
|
|
12
|
+
context = zmq.Context()
|
|
13
|
+
publisher = context.socket(zmq.PUB)
|
|
14
|
+
publisher.bind("tcp://*:5555") # Allow connections from any IP address
|
|
15
|
+
|
|
16
|
+
async def send_message(message: Message):
|
|
17
|
+
try:
|
|
18
|
+
# Make sure we have a created_at
|
|
19
|
+
if not message.created_at:
|
|
20
|
+
message.created_at = datetime.datetime.now().strftime("%a %Y-%m-%d %H:%M:%S")
|
|
21
|
+
# Send the message to the ZeroMQ publisher channel in JSON format
|
|
22
|
+
publisher.send_json(message.dict())
|
|
23
|
+
|
|
24
|
+
# Return success status
|
|
25
|
+
return {"status": "success"}
|
|
26
|
+
|
|
27
|
+
except Exception as e:
|
|
28
|
+
# Log the exception
|
|
29
|
+
print(f"Error sending message: {e}")
|
|
30
|
+
# Return error status
|
|
31
|
+
return {"status": "error"}
|
|
32
|
+
|
|
33
|
+
async def time_publisher_task():
|
|
34
|
+
while True:
|
|
35
|
+
current_time = datetime.datetime.now().strftime("%a %Y-%m-%d %H:%M:%S")
|
|
36
|
+
message = Message(
|
|
37
|
+
source="TimePublisher",
|
|
38
|
+
to="ALL",
|
|
39
|
+
content=f"Current time: {current_time}",
|
|
40
|
+
created_at=current_time,
|
|
41
|
+
message_type=MessageType.SYSTEM
|
|
42
|
+
)
|
|
43
|
+
await send_message(message)
|
|
44
|
+
await asyncio.sleep(300)
|
|
45
|
+
|
|
46
|
+
@app.on_event("startup")
|
|
47
|
+
async def startup_event():
|
|
48
|
+
asyncio.create_task(time_publisher_task())
|
|
49
|
+
|
|
50
|
+
@app.post("/data")
|
|
51
|
+
async def post_message(message: Message):
|
|
52
|
+
# This endpoint calls the same send_message method
|
|
53
|
+
return await send_message(message)
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
import uvicorn
|
|
57
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agent": {
|
|
3
|
+
"name": "Julia",
|
|
4
|
+
"description": "A helpful AI assistant.",
|
|
5
|
+
"model": "ollama/dolphin-mistral",
|
|
6
|
+
"system_prompt": "You are an AI agent called {name} who excels at conversations. Your description is as follows: {description}\n You will be given the current history so continue the conversation and be engaged.\nMake your words short and specific.\n",
|
|
7
|
+
"user_prompt": "Continue the conversation:\n{context}\n{name}:"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from agentkit.agents.simple_agent import SimpleAgent
|
|
6
|
+
from agentkit.memory.simple_memory import SimpleMemory
|
|
7
|
+
from agentkit.messages import MessageType
|
|
8
|
+
from agentkit.network import HTTPMessageSender, ZMQMessageReceiver
|
|
9
|
+
from agentkit.io import console
|
|
10
|
+
from agentkit.handlers import print_chat_message
|
|
11
|
+
|
|
12
|
+
# Task definitions
|
|
13
|
+
async def user_input_task(agent):
|
|
14
|
+
while agent.running:
|
|
15
|
+
await console.ainput(agent)
|
|
16
|
+
await asyncio.sleep(0.1)
|
|
17
|
+
|
|
18
|
+
async def main(name:str, description:str, bus_ip:str="127.0.0.1"):
|
|
19
|
+
config = {"agent": {"name": name, "description":description, "model": "dummy/dummy"}}
|
|
20
|
+
agent = SimpleAgent(config=config, message_sender=HTTPMessageSender(publish_address=f"http://{bus_ip}:8000"))
|
|
21
|
+
# Register the tasks to the agent
|
|
22
|
+
agent.add_task("user_input", user_input_task(agent))
|
|
23
|
+
agent.add_message_handler(MessageType.CHAT, print_chat_message)
|
|
24
|
+
|
|
25
|
+
# Initialize the Message Reciever
|
|
26
|
+
message_receiver = ZMQMessageReceiver(subscribe_address=f"tcp://{bus_ip}:5555")
|
|
27
|
+
message_receiver.register_subscriber(agent)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
receiver_task = asyncio.create_task(message_receiver.start())
|
|
31
|
+
agent_task = asyncio.create_task(agent.start())
|
|
32
|
+
await asyncio.gather(receiver_task, agent_task)
|
|
33
|
+
except KeyboardInterrupt:
|
|
34
|
+
logging.info("Ctrl+C pressed. Stopping agent.")
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logging.error(f"An error occurred: {e}")
|
|
37
|
+
finally:
|
|
38
|
+
# Ensure that stop is called for all the tasks
|
|
39
|
+
if hasattr(message_receiver, 'stop') and callable(getattr(message_receiver, 'stop')):
|
|
40
|
+
message_receiver.stop()
|
|
41
|
+
if hasattr(agent, 'stop') and callable(getattr(agent, 'stop')):
|
|
42
|
+
await agent.stop() # If stop is an async method
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
parser = argparse.ArgumentParser(description="Start a console conversational UI for humans")
|
|
46
|
+
parser.add_argument("--name", help="Name of the Human agent. Default 'Human'", default="Human")
|
|
47
|
+
parser.add_argument("--description", help="Description of the agent. Introduce yourself to the world. Default 'I am a human agent. Ask me anything!'", default="I am a human agent. Ask me anything!")
|
|
48
|
+
parser.add_argument("--bus_ip", default="127.0.0.1", help="The IP Address of the bus to subscribe to. Default 127.0.0.1")
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
asyncio.run(main(name=args.name,
|
|
52
|
+
description=args.description,
|
|
53
|
+
bus_ip=args.bus_ip))
|
|
54
|
+
|
|
55
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from agentkit.agents.simple_agent import SimpleAgent
|
|
7
|
+
from agentkit.brains.simple_brain import SimpleBrain
|
|
8
|
+
from agentkit.messages import MessageType
|
|
9
|
+
from agentkit.network import HTTPMessageSender, ZMQMessageReceiver
|
|
10
|
+
from agentkit.memory.simple_memory import SimpleMemory
|
|
11
|
+
|
|
12
|
+
# Set up logging
|
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
|
14
|
+
|
|
15
|
+
def load_config(file_path):
|
|
16
|
+
with open(file_path, 'r') as file:
|
|
17
|
+
return json.load(file)
|
|
18
|
+
|
|
19
|
+
def get_config_value(config, parameter_name, override):
|
|
20
|
+
#
|
|
21
|
+
if override:
|
|
22
|
+
return override
|
|
23
|
+
else:
|
|
24
|
+
if parameter_name in config['agent'].keys():
|
|
25
|
+
return config['agent'][parameter_name]
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(f'Required Parameter "{parameter_name}" is not defined in the config file or instance creation')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def main(name:str, description:str, config_file:str, model:str, bus_ip:str="127.0.0.1"):
|
|
31
|
+
# Load the configuration
|
|
32
|
+
config = load_config(config_file)
|
|
33
|
+
name=get_config_value(config, "name", name)
|
|
34
|
+
description=get_config_value(config, "description", description)
|
|
35
|
+
model = get_config_value(config, "model", model)
|
|
36
|
+
system_prompt = get_config_value(config, "system_prompt", "")
|
|
37
|
+
user_prompt = get_config_value(config, "user_prompt", "")
|
|
38
|
+
|
|
39
|
+
agent = SimpleAgent(
|
|
40
|
+
config=config,
|
|
41
|
+
name=name,
|
|
42
|
+
description=description,
|
|
43
|
+
message_sender=HTTPMessageSender(publish_address=f"http://{bus_ip}:8000"),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
brain = SimpleBrain(name=name,
|
|
47
|
+
description=description,
|
|
48
|
+
model=model,
|
|
49
|
+
memory_manger=SimpleMemory(max_history_length=6),
|
|
50
|
+
system_prompt=system_prompt,
|
|
51
|
+
user_prompt=user_prompt
|
|
52
|
+
)
|
|
53
|
+
agent.add_message_handler(MessageType.CHAT, brain.handle_chat_message)
|
|
54
|
+
|
|
55
|
+
message_receiver = ZMQMessageReceiver(subscribe_address=f"tcp://{bus_ip}:5555")
|
|
56
|
+
message_receiver.register_subscriber(agent)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
receiver_task = asyncio.create_task(message_receiver.start())
|
|
60
|
+
agent_task = asyncio.create_task(agent.start())
|
|
61
|
+
await asyncio.gather(receiver_task, agent_task)
|
|
62
|
+
except KeyboardInterrupt:
|
|
63
|
+
logging.info("Ctrl+C pressed. Stopping agent.")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logging.error(f"An error occurred: {e}")
|
|
66
|
+
finally:
|
|
67
|
+
# Ensure that stop is called for all the tasks
|
|
68
|
+
if hasattr(message_receiver, 'stop') and callable(getattr(message_receiver, 'stop')):
|
|
69
|
+
message_receiver.stop()
|
|
70
|
+
if hasattr(agent, 'stop') and callable(getattr(agent, 'stop')):
|
|
71
|
+
await agent.stop() # If stop is an async method
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
parser = argparse.ArgumentParser(description="Run an agent with specified name and description")
|
|
75
|
+
parser.add_argument("--config", help="Path to the configuration file", required=True)
|
|
76
|
+
parser.add_argument("--name", help="Name of the agent. Optional, overriding the config file")
|
|
77
|
+
parser.add_argument("--description", help="Description of the agent. Optional overriding the config file")
|
|
78
|
+
parser.add_argument("--model", help="The litellm model you want to use. Optional overriding the config file")
|
|
79
|
+
parser.add_argument("--bus_ip", default="127.0.0.1", help="The IP Address of the bus to subscribe to. Default 127.0.0.1")
|
|
80
|
+
args = parser.parse_args()
|
|
81
|
+
|
|
82
|
+
asyncio.run(main(name=args.name,
|
|
83
|
+
description=args.description,
|
|
84
|
+
config_file=args.config,
|
|
85
|
+
model=args.model,
|
|
86
|
+
bus_ip=args.bus_ip))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from agentkit.messages import Message, MessageType
|
|
2
|
+
|
|
3
|
+
async def default_handle_helo_message(agent, message:Message):
|
|
4
|
+
agent.attention = message.source
|
|
5
|
+
response = Message(source=agent.name, to=message.source, content=agent.description, message_type=MessageType.ACK)
|
|
6
|
+
agent.send_message(response)
|
|
7
|
+
|
|
8
|
+
async def print_chat_message(agent, message:Message):
|
|
9
|
+
agent.attention = message.source
|
|
10
|
+
print(f"\n## {message.source} to {message.to}[{message.message_type}]: {message.content}")
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import aioconsole
|
|
2
|
+
from agentkit.messages import Message, MessageType
|
|
3
|
+
|
|
4
|
+
async def ainput(agent) -> None:
|
|
5
|
+
prompt = f"##{agent.name} ({agent.attention}):"
|
|
6
|
+
message = await aioconsole.ainput(prompt)
|
|
7
|
+
if message != "exit":
|
|
8
|
+
if not message.startswith("TO:"):
|
|
9
|
+
destination = agent.attention
|
|
10
|
+
content = message.strip()
|
|
11
|
+
else:
|
|
12
|
+
first_part = message.split(",")[0]
|
|
13
|
+
content = message.replace(first_part, "").strip()[1:]
|
|
14
|
+
destination = first_part.replace("TO:", "").strip()
|
|
15
|
+
msg = Message(source=agent.name, to=destination, content=content, message_type=MessageType.CHAT)
|
|
16
|
+
agent.message_sender.send_message(msg)
|
|
17
|
+
else:
|
|
18
|
+
raise ValueError("Exit was called")
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
from agentkit.messages import Message
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SimpleMemory:
|
|
6
|
+
def __init__(self, max_history_length:int=10) -> None:
|
|
7
|
+
self.history = []
|
|
8
|
+
self.max_history_length = max_history_length
|
|
9
|
+
|
|
10
|
+
def remember(self, message: Message) -> None:
|
|
11
|
+
# Store the conversation in the history
|
|
12
|
+
if len(self.history) >= self.max_history_length:
|
|
13
|
+
self.history.pop(0) # remove oldest message
|
|
14
|
+
self.history.append(message)
|
|
15
|
+
|
|
16
|
+
def get_history(self) -> list[Message]:
|
|
17
|
+
return self.history
|
|
18
|
+
|
|
19
|
+
def get_chat_context(self ,target:str, prefix:str="") -> str:
|
|
20
|
+
chat_log = [x for x in self.get_history() if (target in [x.to, x.source]) and x.message_type=="CHAT"]
|
|
21
|
+
context = "\n".join(f"{prefix}{x.source}: {x.content.strip()}" for x in chat_log)
|
|
22
|
+
return context
|
|
23
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Pydantic model for request validation
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
class MessageType(str, Enum):
|
|
7
|
+
HELO = "HELO"# indicating a login or checking if the agent "to" is available
|
|
8
|
+
ACK = "ACK" # response to a HELO request. Indicates that the agent is available
|
|
9
|
+
CHAT = "CHAT" # text message intended for conversation
|
|
10
|
+
SYSTEM = "SYSTEM" # system message coming from the datahub
|
|
11
|
+
SENSOR = "SENSOR" # messages for data coming from sensors
|
|
12
|
+
ERROR = "ERROR" # error messages
|
|
13
|
+
MEMORY = "MEMORY" # A memory cluster
|
|
14
|
+
INFO = "INFO" # A response from a previous information request
|
|
15
|
+
|
|
16
|
+
class Message(BaseModel):
|
|
17
|
+
"""A Message Object that is sent out on the PUB channel of the PUBHUB mechanism.
|
|
18
|
+
The description of the fields is as follows:
|
|
19
|
+
source: the source of the message (e.g., the name of the sender, usually another agent or a sensor)
|
|
20
|
+
to: the intended recipient of the message (e.g., the name of the receiver, usually another agent, or 'ALL' for broadcast)
|
|
21
|
+
content: the actual message content in string
|
|
22
|
+
topic: Enum of the topic.
|
|
23
|
+
created_at: Optional timestamp for when the message was created.
|
|
24
|
+
"""
|
|
25
|
+
source: str
|
|
26
|
+
to: str
|
|
27
|
+
content: str
|
|
28
|
+
created_at: str
|
|
29
|
+
message_type: MessageType
|
|
30
|
+
|
|
31
|
+
class Config:
|
|
32
|
+
use_enum_values = True
|
|
33
|
+
|
|
34
|
+
def __init__(self, **data):
|
|
35
|
+
if 'created_at' not in data or data['created_at'] is None:
|
|
36
|
+
data['created_at'] = datetime.now().strftime("%a %Y-%m-%d %H:%M:%S")
|
|
37
|
+
super().__init__(**data)
|
|
38
|
+
|
|
39
|
+
def prompt(self):
|
|
40
|
+
"""Print the message in a human-readable format"""
|
|
41
|
+
return f"source: {self.source}: to: {self.to} ({self.message_type}): {self.content} on {self.created_at})"
|
|
42
|
+
|
|
43
|
+
# Pydantic model for response
|
|
44
|
+
class MessageResponse(BaseModel):
|
|
45
|
+
status: str
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Any, Protocol
|
|
2
|
+
import requests
|
|
3
|
+
import zmq
|
|
4
|
+
import asyncio
|
|
5
|
+
from agentkit.messages import Message
|
|
6
|
+
|
|
7
|
+
class Subscriber(Protocol):
|
|
8
|
+
name: str
|
|
9
|
+
|
|
10
|
+
async def handle_message(self, message: Message) -> Any:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
def is_intended_for_me(self, message: Message) -> bool:
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
class MessageSender(Protocol):
|
|
17
|
+
def send_message(self, message: Message) -> Any:
|
|
18
|
+
"""Send message out via the network"""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ZMQMessageReceiver:
|
|
23
|
+
def __init__(self, subscribe_address: str="tcp://127.0.0.1:5555"):
|
|
24
|
+
self.subscribe_address = subscribe_address
|
|
25
|
+
self.subscribers: list[Subscriber] = []
|
|
26
|
+
# ZMQ stuff
|
|
27
|
+
self.context = zmq.Context()
|
|
28
|
+
self.pubsub_subscriber = self.context.socket(zmq.SUB)
|
|
29
|
+
self.pubsub_subscriber.setsockopt_string(zmq.SUBSCRIBE, "")
|
|
30
|
+
self.pubsub_subscriber.connect(subscribe_address)
|
|
31
|
+
self.running = False
|
|
32
|
+
|
|
33
|
+
async def start(self):
|
|
34
|
+
self.running = True
|
|
35
|
+
while self.running:
|
|
36
|
+
try:
|
|
37
|
+
raw_message = await asyncio.to_thread(self.pubsub_subscriber.recv_json)
|
|
38
|
+
message = Message.model_validate(raw_message)
|
|
39
|
+
await self.handle_message(message)
|
|
40
|
+
except zmq.ZMQError as e:
|
|
41
|
+
if e.errno != zmq.EAGAIN:
|
|
42
|
+
raise
|
|
43
|
+
except asyncio.CancelledError:
|
|
44
|
+
# Handle task cancellation gracefully if needed
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
def stop(self):
|
|
48
|
+
self.running = False
|
|
49
|
+
self.pubsub_subscriber.close()
|
|
50
|
+
self.context.term()
|
|
51
|
+
|
|
52
|
+
def register_subscriber(self, subscriber: Subscriber):
|
|
53
|
+
self.subscribers.append(subscriber)
|
|
54
|
+
|
|
55
|
+
async def handle_message(self, message: Message):
|
|
56
|
+
for subscriber in self.subscribers:
|
|
57
|
+
if subscriber.is_intended_for_me(message):
|
|
58
|
+
await subscriber.handle_message(message)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class HTTPMessageSender:
|
|
62
|
+
def __init__(self, publish_address: str="http://127.0.0.1:8000") -> None:
|
|
63
|
+
self.publish_address = publish_address
|
|
64
|
+
|
|
65
|
+
def send_message(self, message: Message) -> requests.Response:
|
|
66
|
+
response = requests.post(f"{self.publish_address}/data", json=message.dict())
|
|
67
|
+
return response
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from litellm import acompletion, set_verbose
|
|
5
|
+
|
|
6
|
+
class JSONParseError(ValueError):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def llm_processor(llm_model: str,
|
|
11
|
+
system_prompt: str = "",
|
|
12
|
+
user_prompt: str = "",
|
|
13
|
+
api_base: str = "http://localhost:11434",
|
|
14
|
+
stop: list[str] = ["\n"],
|
|
15
|
+
) -> str:
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
A text processor that uses a LLM to generate a response.
|
|
19
|
+
"""
|
|
20
|
+
set_verbose=True
|
|
21
|
+
# Use the LLM to generate a response
|
|
22
|
+
messages=[ {"content": system_prompt, "role": "system"} ]
|
|
23
|
+
if user_prompt:
|
|
24
|
+
messages.append({"content": user_prompt, "role": "user"})
|
|
25
|
+
|
|
26
|
+
response = await acompletion(
|
|
27
|
+
model = llm_model,
|
|
28
|
+
messages = messages,
|
|
29
|
+
api_base= api_base,
|
|
30
|
+
stop=stop
|
|
31
|
+
)
|
|
32
|
+
return response.choices[0].message.content.strip()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def extract_json(text: str) -> dict:
|
|
36
|
+
""" Extract the JSON object from the text.
|
|
37
|
+
:param text: The text to extract the JSON from.
|
|
38
|
+
:return: The JSON object as a dictionary.
|
|
39
|
+
"""
|
|
40
|
+
# Extract JSON part from the message
|
|
41
|
+
json_match = re.search(r"\{.*\}", text, re.DOTALL)
|
|
42
|
+
if not json_match:
|
|
43
|
+
raise JSONParseError("No JSON structure was found in your input while parsing. Make sure the JSON formatting is correct.")
|
|
44
|
+
try:
|
|
45
|
+
match = json_match.group(0)
|
|
46
|
+
# convert it to dict
|
|
47
|
+
#data = ast.literal_eval(match)
|
|
48
|
+
data = json.loads(match)
|
|
49
|
+
return data
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
raise JSONParseError(f"Invalid JSON structure: {match}. Make sure the JSON formatting is correct. Always use double quotes and don't use single quotes in your formatting.")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def remove_emojis(data):
|
|
55
|
+
emoj = re.compile("["
|
|
56
|
+
u"\U0001F600-\U0001F64F" # emoticons
|
|
57
|
+
u"\U0001F300-\U0001F5FF" # symbols & pictographs
|
|
58
|
+
u"\U0001F680-\U0001F6FF" # transport & map symbols
|
|
59
|
+
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
|
|
60
|
+
u"\U00002500-\U00002BEF" # chinese char
|
|
61
|
+
u"\U00002702-\U000027B0"
|
|
62
|
+
u"\U000024C2-\U0001F251"
|
|
63
|
+
u"\U0001f926-\U0001f937"
|
|
64
|
+
u"\U00010000-\U0010ffff"
|
|
65
|
+
u"\u2640-\u2642"
|
|
66
|
+
u"\u2600-\u2B55"
|
|
67
|
+
u"\u200d"
|
|
68
|
+
u"\u23cf"
|
|
69
|
+
u"\u23e9"
|
|
70
|
+
u"\u231a"
|
|
71
|
+
u"\ufe0f" # dingbats
|
|
72
|
+
u"\u3030"
|
|
73
|
+
"]+", re.UNICODE)
|
|
74
|
+
return re.sub(emoj, '', data)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agentkit"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"zmq",
|
|
10
|
+
"fastapi",
|
|
11
|
+
"uvicorn",
|
|
12
|
+
"pydantic",
|
|
13
|
+
"litellm"
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
authors = [
|
|
17
|
+
{name = "Vikram Kumar", email = "vik@japanvik.net"},
|
|
18
|
+
]
|
|
19
|
+
description = "A simple framework for creating distributed llm agent swarms"
|
|
20
|
+
readme = "README.md"
|
|
21
|
+
license = {file = "LICENSE"}
|
|
22
|
+
keywords = ["artificial intelligencs", "pubsub", "llm"]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Programming Language :: Python"
|
|
26
|
+
]
|
|
27
|
+
[project.urls]
|
|
28
|
+
Repository = "https://github.com/japanvik/agentkit"
|