1) Add e-mail tunnel support 2) Modify all __new__ to get_instance() 3) init docker build scripts

This commit is contained in:
Liu Zhicong
2023-09-16 11:41:59 -07:00
parent ef9ab83e63
commit 43c70489e7
15 changed files with 272 additions and 70 deletions
+27
View File
@@ -0,0 +1,27 @@
**/__pycache__
**/*venv
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/.db
**/.python-version
+10
View File
@@ -0,0 +1,10 @@
FROM python:3.11
WORKDIR /opt/aios
COPY ./src /opt/aios
COPY ./rootfs /var/aios
RUN pip install --no-cache-dir -r /opt/opendan/requirements.txt
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
CMD ["python3","./service/aios_shell/aios_shell.py"]
+18 -5
View File
@@ -1,6 +1,19 @@
[[tunnels]]
tunnel_id = "MyRoobot"
type="TelegramTunnel"
target="agent_1"
token=""
#[[tunnels]]
#tunnel_id = "MyRoobot"
#type="TelegramTunnel"
#target="agent_1"
#token=""
#[[tunnels]]
#tunnel_id="MyEmailRobot"
#type="EmailTunnel"
#target="agent_1"
#email="your_email@msn.com"
#imap="outlook.office365.com:993"
#smtp="outlook.office365.com:587"
#user=""
#password=""
#folder="inbox"
#interval=10
+2 -1
View File
@@ -10,4 +10,5 @@ from .workflow import Workflow
from .bus import AIBus
from .workflow_env import WorkflowEnvironment,CalenderEnvironment,CalenderEvent
from .tunnel import AgentTunnel
from .tg_tunnel import TelegramTunnel
from .tg_tunnel import TelegramTunnel
from .email_tunnel import EmailTunnel
+3 -3
View File
@@ -82,7 +82,7 @@ class AIAgent:
self.chat_db = None
self.unread_msg = Queue() # msg from other agent
self.owner_env : Environment = None
self.owenr_bus : AIBus = None
self.owenr_bus = None
@classmethod
def create_from_templete(cls,templete:AIAgentTemplete, fullname:str):
@@ -164,7 +164,7 @@ class AIAgent:
inner_functions = self._get_inner_functions()
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
ineternal_call_record.result_str = task_result.result_str
ineternal_call_record.done_time = time.time()
@@ -198,7 +198,7 @@ class AIAgent:
inner_functions = self._get_inner_functions()
task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
final_result = task_result.result_str
inner_func_call_node = task_result.result_message.get("function_call")
+5 -10
View File
@@ -17,19 +17,14 @@ logger = logging.getLogger(__name__)
class ComputeKernel:
_instance = None
def __new__(cls):
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.is_start = False
cls._instance = ComputeKernel()
return cls._instance
def __init__(self) -> None:
if self.is_start is True:
return
self.is_start = True
self.is_start = False
self.task_queue = Queue()
self.is_start = False
self.compute_nodes = {}
+143
View File
@@ -0,0 +1,143 @@
import asyncio
import aiosmtplib
import aioimaplib
import email
from email.header import decode_header
import mailparser
import logging
import time
import datetime
from .tunnel import AgentTunnel
from .agent_message import AgentMsg
from email.message import EmailMessage
logger = logging.getLogger(__name__)
class EmailTunnel(AgentTunnel):
@classmethod
def register_to_loader(cls):
async def load_email_tunnel(config:dict) -> AgentTunnel:
result_tunnel = EmailTunnel()
if await result_tunnel.load_from_config(config):
return result_tunnel
else:
return None
AgentTunnel.register_loader("EmailTunnel",load_email_tunnel)
async def load_from_config(self,config:dict)->bool:
self.target_id = config["target"]
self.tunnel_id = config["tunnel_id"]
self.type = "TelegramTunnel"
self.email = config["email"]
self.imap_server = config["imap"]
s = self.imap_server.split(":")
if len(s) == 2:
self.imap_server = s[0]
self.imap_port = int(s[1])
self.smtp_server = config["smtp"]
s = self.smtp_server.split(":")
if len(s) == 2:
self.smtp_server = s[0]
self.smtp_port = int(s[1])
self.login_user = config["user"]
self.login_password = config["password"]
self.folder = config["folder"]
self.check_interval = config["interval"]
return True
def __init__(self) -> None:
super().__init__()
self.is_start = False
self.read_email = {}
async def on_new_email(self,mail:mailparser.MailParser) -> None:
remote_email_addr = mail.from_[0][1]
remote_user_name = remote_email_addr.split("@")[0]
agent_msg = self.conver_mail_to_agent_msg(mail)
agent_msg.sender = remote_user_name
agent_msg.target = self.target_id
self.ai_bus.register_message_handler(remote_user_name, self._process_message)
resp_msg = await self.ai_bus.send_message(agent_msg)
if resp_msg is None:
await self.reply_email(remote_email_addr,"Sorry, I can't understand your message","")
else:
if resp_msg.body_mime is None:
await self.reply_email(remote_email_addr,"result",resp_msg.body)
async def reply_email(self,target_email:str,title:str,msg:str) -> None:
email_msg = EmailMessage()
email_msg['Subject'] = f"Reply: {title}"
email_msg['From'] = self.email
email_msg['To'] = target_email
email_msg.set_content(msg)
await aiosmtplib.send(
email_msg,
hostname = self.smtp_server,
port=self.smtp_port,
username=self.login_user,
password=self.login_password,
)
def conver_mail_to_agent_msg(self,mail:mailparser.MailParser) -> AgentMsg:
msg = AgentMsg()
msg.set("",self.target_id,mail.text_plain[0])
msg.topic = "email"
return msg
async def check_email(self) -> None:
self.last_check_num = 0
self.last_check_time = datetime.datetime.now()
while True:
if self.is_start == False:
return
await asyncio.sleep(self.check_interval)
imap_client = aioimaplib.IMAP4_SSL(host=self.imap_server,port=self.imap_port)
await imap_client.wait_hello_from_server()
await imap_client.login(self.login_user, self.login_password)
date_since = self.last_check_time.strftime("%d-%b-%Y")
await imap_client.select(self.folder)
status, messages = await imap_client.search('UNSEEN',charset='US-ASCII')
self.last_check_time = datetime.datetime.now()
if status == "OK":
message_numbers = messages[0].split()
for num in message_numbers:
num = int(num)
if self.read_email.get(num) is not None:
continue
status, email_data = await imap_client.fetch(str(num), "(RFC822)")
if status == "OK":
#r = email.message_from_bytes(email_data[1])
mail = mailparser.parse_from_bytes(email_data[1])
self.read_email[num] = mail
await self.on_new_email(mail)
await imap_client.logout()
async def start(self) -> bool:
if self.is_start:
logger.warning(f"tunnel {self.tunnel_id} is already started")
return False
self.is_start = True
asyncio.create_task(self.check_email())
return True
async def close(self) -> None:
self.is_start = False
async def _process_message(self, msg: AgentMsg) -> None:
logger.warn(f"process message {msg.msg_id} from {msg.sender} to {msg.target}")
+9 -8
View File
@@ -13,20 +13,17 @@ logger = logging.getLogger(__name__)
class OpenAI_ComputeNode(ComputeNode):
_instance = None
def __new__(cls):
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = super(OpenAI_ComputeNode, cls).__new__(cls)
cls._instance.is_start = False
cls._instance = OpenAI_ComputeNode()
return cls._instance
def __init__(self) -> None:
super().__init__()
if self.is_start is True:
logger.warn("OpenAI_ComputeNode is already start")
return
self.is_start = True
self.is_start = False
# openai.organization = "org-AoKrOtF2myemvfiFfnsSU8rF" #buckycloud
self.openai_api_key = ""
self.node_id = "openai_node"
@@ -89,6 +86,10 @@ class OpenAI_ComputeNode(ComputeNode):
return result
def start(self):
if self.is_start is True:
return
self.is_start = True
async def _run_task_loop():
while True:
task = await self.task_queue.get()
+11 -11
View File
@@ -16,20 +16,17 @@ logger = logging.getLogger(__name__)
class Stability_ComputeNode(ComputeNode):
_instanace = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = Stability_ComputeNode()
return cls._instance
def __new__(cls):
if cls._instanace is None:
cls._instanace = super(Stability_ComputeNode, cls).__new__(cls)
cls._instanace.is_start = False
return cls._instanace
def __init__(self) -> None:
super().__init__()
if self.is_start is True:
logger.warn("Stability_ComputeNode is already start")
return
self.is_start = True
self.is_start = False
self.node_id = "stability_node"
self.api_key = ""
self.engine = "stable-diffusion-512-v2-1"
@@ -88,8 +85,8 @@ class Stability_ComputeNode(ComputeNode):
for resp in answers:
for artifact in resp.artifacts:
logger.info("artifact:", artifact.id,
artifact.type, artifact.finish_reason)
logger.info(f"artifact:{artifact.id},{artifact.type},{artifact.finish_reason}")
if artifact.finish_reason == generation.FILTER:
logging.warn("request activated the API's safety filters")
if artifact.type == generation.ARTIFACT_IMAGE:
@@ -108,6 +105,9 @@ class Stability_ComputeNode(ComputeNode):
return None
def start(self):
if self.is_start:
return
self.is_start = True
async def _run_task_loop():
while True:
logger.info("stability_node is waiting for task...")
+7 -3
View File
@@ -122,7 +122,11 @@ class Workflow:
connected_env_ndoe = config.get("connected_env")
if connected_env_ndoe is not None:
for _node in connected_env_ndoe:
remote_env = Environment.get_env_by_id(_node.get("env_id"))
env_id = _node.get("env_id")
if env_id is None:
continue
remote_env = Environment.get_env_by_id(_node.get(env_id))
if remote_env is None:
logger.error(f"Workflow load connected_env failed, env {env_id} not found!")
return False
@@ -391,7 +395,7 @@ class Workflow:
inner_functions = self._get_inner_functions()
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(prompt,
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,
the_role.agent.llm_model_name,the_role.agent.max_token_size,
inner_functions)
@@ -431,7 +435,7 @@ class Workflow:
async def _do_process_msg():
#TODO: send msg to agent might be better?
task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(prompt,the_role.agent.get_llm_model_name(),the_role.agent.get_max_token_size(),inner_functions)
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,the_role.agent.get_llm_model_name(),the_role.agent.get_max_token_size(),inner_functions)
result_str = task_result.result_str
logger.info(f"{the_role.role_id} process {msg.sender}:{msg.body},llm str is :{result_str}")
+3 -3
View File
@@ -11,12 +11,12 @@ logger = logging.getLogger(__name__)
class AgentManager:
_instance = None
def __new__(cls):
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = super(AgentManager, cls).__new__(cls)
cls._instance = AgentManager()
return cls._instance
def initial(self,root_dir:str) -> None:
self.agent_templete_env : PackageEnv = PackageEnvManager().get_env(f"{root_dir}/templetes/templetes.cfg")
self.agent_env : PackageEnv = PackageEnvManager().get_env(f"{root_dir}/agents/agents.cfg")
+4 -5
View File
@@ -134,16 +134,15 @@ class PackageEnv:
class PackageEnvManager:
_instance = None
def __new__(cls):
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = super(PackageEnvManager, cls).__new__(cls)
cls._instance = PackageEnvManager()
return cls._instance
def __init__(self) -> None:
self._pkg_envs = {}
pass
def get_env(self,cfg_path:str) -> PackageEnv:
if cfg_path in self._pkg_envs:
return self._pkg_envs[cfg_path]
@@ -9,9 +9,10 @@ import os
class WorkflowManager:
_instance = None
def __new__(cls):
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = super(WorkflowManager, cls).__new__(cls)
cls._instance = WorkflowManager()
return cls._instance
+9 -4
View File
@@ -1,8 +1,13 @@
aiofiles==23.2.1
aiohttp==3.8.5
openai==0.28.0
Pillow==10.0.0
Pillow==10.0.0
aiohttp==3.7.0
aioimaplib==1.0.1
aiosmtplib==2.0.2
beautifulsoup4==4.12.2
mail_parser==3.15.0
openai==0.27.10
Pillow==10.0.1
prompt_toolkit==3.0.39
python-telegram-bot==20.5
Requests==2.31.0
stability_sdk==0.8.4
toml==0.10.2
+18 -15
View File
@@ -18,22 +18,24 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.styles import Style
shell_style = Style.from_dict({
'title': '#87d7ff bold', #RGB
'content': '#007f00 bold',
'prompt': '#00FF00',
})
directory = os.path.dirname(__file__)
sys.path.append(directory + '/../../')
from aios_kernel import Workflow,AIAgent,AgentMsg,AgentMsgStatus,ComputeKernel,OpenAI_ComputeNode,AIBus,AIChatSession,AgentTunnel,TelegramTunnel,CalenderEnvironment,Environment
from aios_kernel import Workflow,AIAgent,AgentMsg,AgentMsgStatus,ComputeKernel,OpenAI_ComputeNode,AIBus,AIChatSession,AgentTunnel,TelegramTunnel,CalenderEnvironment,Environment,EmailTunnel
sys.path.append(directory + '/../../component/')
from agent_manager import AgentManager
from workflow_manager import WorkflowManager
shell_style = Style.from_dict({
'title': '#87d7ff bold', #RGB
'content': '#007f00 bold',
'prompt': '#00FF00',
})
class AIOS_Shell:
def __init__(self,username:str) -> None:
@@ -43,13 +45,13 @@ class AIOS_Shell:
async def _handle_no_target_msg(self,bus:AIBus,msg:AgentMsg) -> bool:
target_id = msg.target.split(".")[0]
agent : AIAgent = await AgentManager().get(target_id)
agent : AIAgent = await AgentManager.get_instance().get(target_id)
if agent is not None:
agent.owner_env = Environment.get_env_by_id("calender")
bus.register_message_handler(target_id,agent._process_msg)
return True
a_workflow = await WorkflowManager().get_workflow(target_id)
a_workflow = await WorkflowManager.get_instance().get_workflow(target_id)
if a_workflow is not None:
bus.register_message_handler(target_id,a_workflow._process_msg)
return True
@@ -57,7 +59,7 @@ class AIOS_Shell:
return False
async def is_agent(self,target_id:str) -> bool:
agent : AIAgent = await AgentManager().get(target_id)
agent : AIAgent = await AgentManager.get_instance().get(target_id)
if agent is not None:
return True
else:
@@ -68,15 +70,16 @@ class AIOS_Shell:
cal_env.start()
Environment.set_env_by_id("calender",cal_env)
AgentManager().initial(os.path.abspath(directory + "/../../../rootfs/"))
WorkflowManager().initial(os.path.abspath(directory + "/../../../rootfs/workflows/"))
open_ai_node = OpenAI_ComputeNode()
AgentManager.get_instance().initial(os.path.abspath(directory + "/../../../rootfs/"))
WorkflowManager.get_instance().initial(os.path.abspath(directory + "/../../../rootfs/workflows/"))
open_ai_node = OpenAI_ComputeNode.get_instance()
open_ai_node.start()
ComputeKernel().add_compute_node(open_ai_node)
ComputeKernel.get_instance().add_compute_node(open_ai_node)
AIBus().get_default_bus().register_unhandle_message_handler(self._handle_no_target_msg)
AIBus().get_default_bus().register_message_handler(self.username,self._user_process_msg)
TelegramTunnel.register_to_loader()
EmailTunnel.register_to_loader()
tunnels_config_path = os.path.abspath(directory + "/../../../rootfs/tunnels.cfg.toml")
tunnel_config = toml.load(tunnels_config_path)
@@ -140,9 +143,9 @@ class AIOS_Shell:
db_path = ""
if await self.is_agent(self.current_target):
db_path = AgentManager().db_path
db_path = AgentManager.get_instance().db_path
else:
db_path = WorkflowManager().db_file
db_path = WorkflowManager.get_instance().db_file
chatsession:AIChatSession = AIChatSession.get_session(self.current_target,f"{self.username}#{self.current_topic}",db_path,False)
if chatsession is not None:
msgs = chatsession.read_history(num,offset)