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.
@@ -0,0 +1,2 @@
1
+ __pycache__
2
+ .DS_Store
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.
@@ -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.
@@ -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,11 @@
1
+ from typing import List, Protocol
2
+ from agentkit.messages import Message
3
+
4
+
5
+ class Memory(Protocol):
6
+
7
+ def remember(self, message: Message) -> None:
8
+ ...
9
+
10
+ def get_history(self) -> List[Message]:
11
+ ...
@@ -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"