Refactor the code directory structure to better suit the current complexity

This commit is contained in:
Liu Zhicong
2023-11-30 21:04:19 -08:00
parent 4955225ecd
commit adeca91e0a
99 changed files with 391 additions and 342 deletions
File diff suppressed because it is too large Load Diff
+539
View File
@@ -0,0 +1,539 @@
import abc
import copy
from abc import abstractmethod
from datetime import datetime, timedelta
import logging
from enum import Enum
import uuid
import time
import re
import shlex
import json
from typing import List
from .ai_function import FunctionItem, AIFunction
from ..proto.agent_msg import AgentMsg, AgentMsgType
from ..proto.compute_task import ComputeTaskResult,ComputeTaskResultCode
from ..environment.environment import Environment
logger = logging.getLogger(__name__)
class AgentPrompt:
def __init__(self,prompt_str = None) -> None:
self.messages = []
if prompt_str:
self.messages.append({"role":"user","content":prompt_str})
self.system_message = None
def as_str(self)->str:
result_str = ""
if self.system_message:
result_str += self.system_message.get("role") + ":" + self.system_message.get("content") + "\n"
if self.messages:
for msg in self.messages:
result_str += msg.get("role") + ":" + msg.get("content") + "\n"
return result_str
def to_message_list(self):
result = []
if self.system_message:
result.append(self.system_message)
result.extend(self.messages)
return result
def append(self,prompt):
if prompt is None:
return
if prompt.system_message is not None:
if self.system_message is None:
self.system_message = copy.deepcopy(prompt.system_message)
else:
self.system_message["content"] += prompt.system_message.get("content")
self.messages.extend(prompt.messages)
def get_prompt_token_len(self):
result = 0
if self.system_message:
result += len(self.system_message.get("content"))
for msg in self.messages:
result += len(msg.get("content"))
return result
def load_from_config(self,config:list) -> bool:
if isinstance(config,list) is not True:
logger.error("prompt is not list!")
return False
self.messages = []
for msg in config:
if msg.get("content"):
if msg.get("role") == "system":
self.system_message = msg
else:
self.messages.append(msg)
else:
logger.error("prompt message has no content!")
return True
class LLMResult:
def __init__(self) -> None:
self.state : str = "ignore"
self.resp : str = ""
self.raw_resp = None
self.paragraphs : dict[str,FunctionItem] = []
self.post_msgs : List[AgentMsg] = []
self.send_msgs : List[AgentMsg] = []
self.calls : List[FunctionItem] = []
self.post_calls : List[FunctionItem] = []
self.op_list : List[FunctionItem] = [] # op_list is a optimize design for saving token
@classmethod
def from_json_str(self,llm_json_str:str) -> 'LLMResult':
r = LLMResult()
if llm_json_str is None:
r.state = "ignore"
return r
if llm_json_str == "ignore":
r.state = "ignore"
return r
llm_json = json.loads(llm_json_str)
r.state = llm_json.get("state")
r.resp = llm_json.get("resp")
r.raw_resp = llm_json
post_msgs = llm_json.get("post_msg")
r.post_msgs = []
if post_msgs:
for msg in post_msgs:
new_msg = AgentMsg()
target_id = msg.get("target")
msg_content = msg.get("content")
new_msg.set("",target_id,msg_content)
r.post_msgs.append(new_msg)
#new_msg.msg_type = AgentMsgType.TYPE_MSG
r.calls = llm_json.get("calls")
r.post_calls = llm_json.get("post_calls")
r.op_list = llm_json.get("op_list")
return r
@classmethod
def from_str(self,llm_result_str:str,valid_func:List[str]=None) -> 'LLMResult':
r = LLMResult()
if llm_result_str is None:
r.state = "ignore"
return r
if llm_result_str == "ignore":
r.state = "ignore"
return r
if llm_result_str[0] == "{":
return LLMResult.from_json_str(llm_result_str)
lines = llm_result_str.splitlines()
is_need_wait = False
def check_args(func_item:FunctionItem):
match func_name:
case "send_msg":# /send_msg $target_id
if len(func_args) != 1:
return False
new_msg = AgentMsg()
target_id = func_item.args[0]
msg_content = func_item.body
new_msg.set("",target_id,msg_content)
r.send_msgs.append(new_msg)
is_need_wait = True
return True
case "post_msg":# /post_msg $target_id
if len(func_args) != 1:
return False
new_msg = AgentMsg()
target_id = func_item.args[0]
msg_content = func_item.body
new_msg.set("",target_id,msg_content)
r.post_msgs.append(new_msg)
return True
case "call":# /call $func_name $args_str
r.calls.append(func_item)
is_need_wait = True
return True
case "post_call": # /post_call $func_name,$args_str
r.post_calls.append(func_item)
return True
case _:
if valid_func is not None:
if func_name in valid_func:
r.paragraphs[func_name] = func_item
return True
return False
current_func : FunctionItem = None
for line in lines:
if line.startswith("##/"):
if current_func:
if check_args(current_func) is False:
r.resp += current_func.dumps()
func_name,func_args = AgentMsg.parse_function_call(line[3:])
current_func = FunctionItem(func_name,func_args)
else:
if current_func:
current_func.append_body(line + "\n")
else:
r.resp += line + "\n"
if current_func:
if check_args(current_func) is False:
r.resp += current_func.dumps()
if len(r.send_msgs) > 0 or len(r.calls) > 0:
r.state = "waiting"
else:
r.state = "reponsed"
return r
class AgentReport:
def __init__(self):
pass
class AgentTodoResult:
TODO_RESULT_CODE_OK = 0,
TODO_RESULT_CODE_LLM_ERROR = 1,
TODO_RESULT_CODE_EXEC_OP_ERROR = 2
def __init__(self) -> None:
self.result_code = AgentTodoResult.TODO_RESULT_CODE_OK
self.result_str = None
self.error_str = None
self.op_list = None
def to_dict(self) -> dict:
result = {}
result["result_code"] = self.result_code
result["result_str"] = self.result_str
result["error_str"] = self.error_str
result["op_list"] = self.op_list
return result
class AgentTodo:
TODO_STATE_WAIT_ASSIGN = "wait_assign"
TODO_STATE_INIT = "init"
TODO_STATE_PENDING = "pending"
TODO_STATE_WAITING_CHECK = "wait_check"
TODO_STATE_EXEC_FAILED = "exec_failed"
TDDO_STATE_CHECKFAILED = "check_failed"
TODO_STATE_CASNCEL = "cancel"
TODO_STATE_DONE = "done"
TODO_STATE_EXPIRED = "expired"
def __init__(self):
self.todo_id = "todo#" + uuid.uuid4().hex
self.title = None
self.detail = None
self.todo_path = None # get parent todo,sub todo by path
#self.parent = None
self.create_time = time.time()
self.state = "wait_assign"
self.worker = None
self.checker = None
self.createor = None
self.need_check = True
self.due_date = time.time() + 3600 * 24 * 2
self.last_do_time = None
self.last_check_time = None
self.last_review_time = None
self.depend_todo_ids = []
self.sub_todos = {}
self.result : AgentTodoResult = None
self.last_check_result = None
self.retry_count = 0
self.raw_obj = None
@classmethod
def from_dict(cls,json_obj:dict) -> 'AgentTodo':
todo = AgentTodo()
if json_obj.get("id") is not None:
todo.todo_id = json_obj.get("id")
todo.title = json_obj.get("title")
todo.state = json_obj.get("state")
create_time = json_obj.get("create_time")
if create_time:
todo.create_time = datetime.fromisoformat(create_time).timestamp()
todo.detail = json_obj.get("detail")
due_date = json_obj.get("due_date")
if due_date:
todo.due_date = datetime.fromisoformat(due_date).timestamp()
last_do_time = json_obj.get("last_do_time")
if last_do_time:
todo.last_do_time = datetime.fromisoformat(last_do_time).timestamp()
last_check_time = json_obj.get("last_check_time")
if last_check_time:
todo.last_check_time = datetime.fromisoformat(last_check_time).timestamp()
last_review_time = json_obj.get("last_review_time")
if last_review_time:
todo.last_review_time = datetime.fromisoformat(last_review_time).timestamp()
todo.depend_todo_ids = json_obj.get("depend_todo_ids")
todo.need_check = json_obj.get("need_check")
#todo.result = json_obj.get("result")
#todo.last_check_result = json_obj.get("last_check_result")
todo.worker = json_obj.get("worker")
todo.checker = json_obj.get("checker")
todo.createor = json_obj.get("createor")
if json_obj.get("retry_count"):
todo.retry_count = json_obj.get("retry_count")
todo.raw_obj = json_obj
return todo
def to_dict(self) -> dict:
if self.raw_obj:
result = self.raw_obj
else:
result = {}
result["id"] = self.todo_id
#result["parent_id"] = self.parent_id
result["title"] = self.title
result["state"] = self.state
result["create_time"] = datetime.fromtimestamp(self.create_time).isoformat()
result["detail"] = self.detail
result["due_date"] = datetime.fromtimestamp(self.due_date).isoformat()
result["last_do_time"] = datetime.fromtimestamp(self.last_do_time).isoformat() if self.last_do_time else None
result["last_check_time"] = datetime.fromtimestamp(self.last_check_time).isoformat() if self.last_check_time else None
result["last_review_time"] = datetime.fromtimestamp(self.last_review_time).isoformat() if self.last_review_time else None
result["depend_todo_ids"] = self.depend_todo_ids
result["need_check"] = self.need_check
result["worker"] = self.worker
result["checker"] = self.checker
result["createor"] = self.createor
result["retry_count"] = self.retry_count
return result
def can_check(self)->bool:
if self.state != AgentTodo.TODO_STATE_WAITING_CHECK:
return False
now = datetime.now().timestamp()
if self.last_check_time:
time_diff = now - self.last_check_time
if time_diff < 60*15:
logger.info(f"todo {self.title} is already checked, ignore")
return False
return True
def can_do(self) -> bool:
match self.state:
case AgentTodo.TODO_STATE_DONE:
logger.info(f"todo {self.title} is done, ignore")
return False
case AgentTodo.TODO_STATE_CASNCEL:
logger.info(f"todo {self.title} is cancel, ignore")
return False
case AgentTodo.TODO_STATE_EXPIRED:
logger.info(f"todo {self.title} is expired, ignore")
return False
case AgentTodo.TODO_STATE_EXEC_FAILED:
if self.retry_count > 3:
logger.info(f"todo {self.title} retry count ({self.retry_count}) is too many, ignore")
return False
now = datetime.now().timestamp()
time_diff = self.due_date - now
if time_diff < 0:
logger.info(f"todo {self.title} is expired, ignore")
self.state = AgentTodo.TODO_STATE_EXPIRED
return False
if time_diff > 7*24*3600:
logger.info(f"todo {self.title} is far before due date, ignore")
return False
if self.last_do_time:
time_diff = now - self.last_do_time
if time_diff < 60*15:
logger.info(f"todo {self.title} is already do ignore")
return False
logger.info(f"todo {self.title} can do.")
return True
class AgentWorkLog:
def __init__(self) -> None:
pass
class BaseAIAgent(abc.ABC):
@abstractmethod
def get_id(self) -> str:
pass
@abstractmethod
def get_llm_model_name(self) -> str:
pass
@abstractmethod
def get_max_token_size(self) -> int:
pass
@classmethod
def get_inner_functions(cls, env:Environment) -> (dict,int):
if env is None:
return None,0
all_inner_function = env.get_all_ai_functions()
if all_inner_function is None:
return None,0
result_func = []
result_len = 0
for inner_func in all_inner_function:
func_name = inner_func.get_name()
this_func = {}
this_func["name"] = func_name
this_func["description"] = inner_func.get_description()
this_func["parameters"] = inner_func.get_parameters()
result_len += len(json.dumps(this_func)) / 4
result_func.append(this_func)
return result_func,result_len
async def do_llm_complection(
self,
prompt:AgentPrompt,
org_msg:AgentMsg=None,
env:Environment=None,
inner_functions=None,
is_json_resp=False,
) -> ComputeTaskResult:
from ..frame.compute_kernel import ComputeKernel
#logger.debug(f"Agent {self.agent_id} do llm token static system:{system_prompt_len},function:{function_token_len},history:{history_token_len},input:{input_len}, totoal prompt:{system_prompt_len + function_token_len + history_token_len} ")
if inner_functions is None and env is not None:
inner_functions,_ = BaseAIAgent.get_inner_functions(env)
if is_json_resp:
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,resp_mode="json",mode_name=self.get_llm_model_name(),max_token=self.get_max_token_size(),inner_functions=inner_functions,timeout=None)
else:
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,resp_mode="text",mode_name=self.get_llm_model_name(),max_token=self.get_max_token_size(),inner_functions=inner_functions,timeout=None)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"_do_llm_complection llm compute error:{task_result.error_str}")
#error_resp = msg.create_error_resp(task_result.error_str)
return task_result
result_message = task_result.result.get("message")
inner_func_call_node = None
if result_message:
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
call_prompt : AgentPrompt = copy.deepcopy(prompt)
func_msg = copy.deepcopy(result_message)
del func_msg["tool_calls"]
call_prompt.messages.append(func_msg)
task_result = await self._execute_func(env,inner_func_call_node,call_prompt,inner_functions,org_msg)
return task_result
async def _execute_func(
self,
env: Environment,
inner_func_call_node: dict,
prompt: AgentPrompt,
inner_functions: dict,
org_msg:AgentMsg,
stack_limit = 5
) -> ComputeTaskResult:
arguments = None
try:
func_name = inner_func_call_node.get("name")
arguments = json.loads(inner_func_call_node.get("arguments"))
logger.info(f"llm execute inner func:{func_name} ({json.dumps(arguments)})")
func_node : AIFunction = env.get_ai_function(func_name)
if func_node is None:
result_str = f"execute {func_name} error,function not found"
else:
result_str:str = await func_node.execute(**arguments)
except Exception as e:
result_str = f"execute {func_name} error:{str(e)}"
logger.error(f"llm execute inner func:{func_name} error:{e}")
logger.info("llm execute inner func result:" + result_str)
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,mode_name=self.get_llm_model_name(),max_token=self.get_max_token_size(),inner_functions=inner_functions)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"llm compute error:{task_result.error_str}")
return task_result
if org_msg:
internal_call_record = AgentMsg.create_internal_call_msg(func_name,arguments,org_msg.get_msg_id(),org_msg.target)
internal_call_record.result_str = task_result.result_str
internal_call_record.done_time = time.time()
org_msg.inner_call_chain.append(internal_call_record)
inner_func_call_node = None
if stack_limit > 0:
result_message : dict = task_result.result.get("message")
if result_message:
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
func_msg = copy.deepcopy(result_message)
del func_msg["tool_calls"]
prompt.messages.append(func_msg)
if inner_func_call_node:
return await self._execute_func(env,inner_func_call_node,prompt,inner_functions,org_msg,stack_limit-1)
else:
return task_result
class CustomAIAgent(BaseAIAgent):
def __init__(self, agent_id: str, llm_model_name: str, max_token_size: int) -> None:
self.agent_id = agent_id
self.llm_model_name = llm_model_name
self.max_token_size = max_token_size
def get_id(self) -> str:
return self.agent_id
def get_llm_model_name(self) -> str:
return self.llm_model_name
def get_max_token_size(self) -> int:
return self.max_token_size
+144
View File
@@ -0,0 +1,144 @@
from abc import ABC, abstractmethod
from typing import Dict,Coroutine,Callable
class ParameterDefine:
def __init__(self) -> None:
self.name = None
self.type = None
self.description = None
class AIFunction:
def __init__(self) -> None:
self.description : str = None
@abstractmethod
def get_name(self) -> str:
"""
return the name of the function (should be snake case)
"""
pass
@abstractmethod
def get_description(self) -> str:
"""
return a detailed description of what the function does
"""
return self.description
@abstractmethod
def get_parameters(self) -> Dict:
"""
Return the list of parameters to execute this function in the form of
JSON schema as specified in the OpenAI documentation:
https://platform.openai.com/docs/api-reference/chat/create#chat/create-parameters
str = run_code(code:str)
parameters = {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code which needs to be executed"
}
}
}
"""
pass
@abstractmethod
async def execute(self, **kwargs) -> str:
"""
Execute the function and return a JSON serializable dict.
The parameters are passed in the form of kwargs
"""
pass
@abstractmethod
def is_local(self) -> bool:
"""
is this function call need network?
"""
pass
@abstractmethod
def is_in_zone(self) -> bool:
"""
is this function call in Lan?
"""
pass
@abstractmethod
def is_ready_only(self) -> bool:
pass
#def load_from_config(self,config:dict) -> bool:
# pass
class FunctionItem:
def __init__(self,name,args) -> None:
self.name = name
self.args = args
self.body = None
def append_body(self,body:str) -> None:
if self.body is None:
self.body = body
else:
self.body += body
def dumps(self) -> str:
pass
# call chain is a combination of ai_function,group of ai_function.
class CallChain:
def __init__(self) -> None:
pass
def load_from_config(self,config:dict) -> bool:
pass
async def execute(self):
pass
class SimpleAIFunction(AIFunction):
def __init__(self,func_id:str,description:str,func_handler:Coroutine,parameters:Dict = None) -> None:
self.func_id = func_id
self.description = description
self.func_handler = func_handler
self.parameters = parameters
def get_name(self) -> str:
return self.func_id
def get_parameters(self) -> Dict:
if self.parameters is not None:
result = {}
result["type"] = "object"
parm_defines = {}
for parm,desc in self.parameters.items():
parm_item = {}
parm_item["type"] = "string"
parm_item["description"] = desc
parm_defines[parm] = parm_item
result["properties"] = parm_defines
return result
return {"type": "object", "properties": {}}
async def execute(self,**kwargs) -> str:
if self.func_handler is None:
return "error: function not implemented"
return await self.func_handler(**kwargs)
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
+4
View File
@@ -0,0 +1,4 @@
# TODO: let agent develolp custmized behavior easily
class AgentBehavior:
def __init__(self) -> None:
pass
+406
View File
@@ -0,0 +1,406 @@
import sqlite3 # Because sqlite3 IO operation is small, so we can use sqlite3 directly.(so we don't need to use async sqlite3 now)
from sqlite3 import Error
import logging
import threading
import datetime
import uuid
import json
from ..proto.agent_msg import AgentMsgType, AgentMsg, AgentMsgStatus
class ChatSessionDB:
def __init__(self, db_file):
""" initialize db connection """
self.db_file = db_file
self._get_conn()
def _get_conn(self):
""" get db connection """
local = threading.local()
if not hasattr(local, 'conn'):
local.conn = self._create_connection(self.db_file)
return local.conn
def _create_connection(self, db_file):
""" create a database connection to a SQLite database """
conn = None
try:
conn = sqlite3.connect(db_file)
except Error as e:
logging.error("Error occurred while connecting to database: %s", e)
return None
if conn:
self._create_table(conn)
return conn
def close(self):
if not hasattr(self.local, 'conn'):
return
self.local.conn.close()
def _create_table(self, conn):
""" create table """
try:
# create sessions table
conn.execute("""
CREATE TABLE IF NOT EXISTS ChatSessions (
SessionID TEXT PRIMARY KEY,
SessionOwner TEXT,
SessionTopic TEXT,
StartTime TEXT,
SummarizePos INTEGER,
Summary TEXT,
ThreadID TEXT
);
""")
# create messages table
# reciver_id could be None
conn.execute("""
CREATE TABLE IF NOT EXISTS Messages (
MessageID TEXT PRIMARY KEY,
SessionID TEXT,
MsgType INTEGER,
PrevMsgID TEXT,
QuoteMsgID TEXT,
RelyMsgID TEXT,
SenderID TEXT,
ReceiverID TEXT,
Timestamp TEXT,
Topic TEXT,
Mentions TEXT,
ContentMIME TEXT,
Content TEXT,
ActionName TEXT,
ActionParams TEXT,
ActionResult TEXT,
DoneTime TEXT,
Status INTEGER
);
""")
conn.commit()
except Error as e:
logging.error("Error occurred while creating tables: %s", e)
def insert_chatsession(self, session_id, session_owner,session_topic, start_time,thread_id = ""):
""" insert a new session into the ChatSessions table """
try:
conn = self._get_conn()
conn.execute("""
INSERT INTO ChatSessions (SessionID, SessionOwner,SessionTopic, StartTime,SummarizePos,Summary,ThreadID)
VALUES (?,?, ?, ?,0,"",?)
""", (session_id, session_owner,session_topic, start_time,thread_id))
conn.commit()
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while inserting session: %s", e)
return -1 # return -1 if an error occurs
def insert_message(self, msg:AgentMsg):
""" insert a new message into the Messages table """
try:
action_name = None
action_params = None
action_result = None
mentions = None
if msg.mentions:
mentions = json.dumps(msg.mentions)
match msg.msg_type:
case AgentMsgType.TYPE_MSG:
pass
case AgentMsgType.TYPE_ACTION:
action_name = msg.func_name
action_params = json.dumps(msg.args)
action_result = msg.result_str
case AgentMsgType.TYPE_INTERNAL_CALL:
action_name = msg.func_name
action_params = json.dumps(msg.args)
action_result = msg.result_str
case AgentMsgType.TYPE_EVENT:
action_name = msg.event_name
action_params = json.dumps(msg.event_args)
conn = self._get_conn()
conn.execute("""
INSERT INTO Messages (MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status)
VALUES (?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (msg.msg_id, msg.session_id, msg.msg_type.value, msg.prev_msg_id, msg.sender, msg.target, msg.create_time, msg.topic,mentions,msg.body_mime,msg.body,action_name,action_params,action_result,msg.done_time,msg.status.value))
conn.commit()
if msg.inner_call_chain:
for inner_call in msg.inner_call_chain:
self.insert_message(inner_call)
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while inserting message: %s", e)
return -1 # return -1 if an error occurs
def get_chatsession_by_id(self, session_id):
"""Get a message by its ID"""
conn = self._get_conn()
c = conn.cursor()
c.execute("SELECT * FROM ChatSessions WHERE SessionID = ?", (session_id,))
chatsession = c.fetchone()
return chatsession
def get_chatsession_by_owner_topic(self, owner_id, topic):
"""Get a chatsession by its owner and topic"""
conn = self._get_conn()
c = conn.cursor()
c.execute("SELECT * FROM ChatSessions WHERE SessionOwner = ? AND SessionTopic = ?", (owner_id,topic))
chatsession = c.fetchone()
return chatsession
def list_chatsessions(self, owner_id, limit, offset):
""" retrieve sessions with pagination """
try:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT SessionID FROM ChatSessions
WHERE SessionOwner = ?
ORDER BY StartTime DESC
LIMIT ? OFFSET ?
""", (owner_id,limit, offset))
results = cursor.fetchall()
#self.close()
return results # return 0 and the result if successful
except Error as e:
logging.error("Error occurred while getting sessions: %s", e)
return -1, None # return -1 and None if an error occurs
def get_message_by_id(self, message_id):
"""Get a message by its ID"""
conn =self._get_conn()
c = conn.cursor()
c.execute("SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages WHERE MessageID = ?", (message_id,))
message = c.fetchone()
return message
# read message from begin->now
def read_message(self,session_id,limit,offset):
try:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages
WHERE SessionID = ?
ORDER BY Timestamp
LIMIT ? OFFSET ?
""", (session_id, limit, offset))
results = cursor.fetchall()
#self.close()
return results # return 0 and the result if successful
except Error as e:
logging.error("Error occurred while getting messages: %s", e)
return -1, None # return -1 and None if an error occurs
# read message from now->beign
def get_messages(self, session_id, limit, offset):
""" retrieve messages of a session with pagination """
try:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages
WHERE SessionID = ?
ORDER BY Timestamp DESC
LIMIT ? OFFSET ?
""", (session_id, limit, offset))
results = cursor.fetchall()
#self.close()
return results # return 0 and the result if successful
except Error as e:
logging.error("Error occurred while getting messages: %s", e)
return -1, None # return -1 and None if an error occurs
def update_message_status(self, message_id, status):
""" update the status of a message """
try:
conn = self._get_conn()
conn.execute("""
UPDATE Messages
SET Status = ?
WHERE MessageID = ?
""", (status, message_id))
conn.commit()
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while updating message status: %s", e)
return -1 # return -1 if an error occurs
def update_session_summary(self, session_id, summarize_pos, summary):
""" update the summary of a session """
try:
conn = self._get_conn()
conn.execute("""
UPDATE ChatSessions
SET SummarizePos = ?, Summary = ?
WHERE SessionID = ?
""", (summarize_pos, summary, session_id))
conn.commit()
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while updating session summary: %s", e)
return -1
def update_session_thread_id(self, session_id, thread_id):
""" update the threadid of a session """
try:
conn = self._get_conn()
conn.execute("""
UPDATE ChatSessions
SET ThreadID = ?
WHERE SessionID = ?
""", (thread_id, session_id))
conn.commit()
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while updating session threadid: %s", e)
return -1
# chat session store the chat history between owner and agent
# chat session might be large, so can read / write at stream mode.
class AIChatSession:
_dbs = {}
#@classmethod
#async def get_session_by_id(cls,session_id:str,db_path:str):
# db = cls._dbs.get(db_path)
# if db is None:
# db = ChatSessionDB(db_path)
# cls._dbs[db_path] = db
# db.get_chatsession_by_id(session_id)
# #result = AIChatSession()
@classmethod
def get_session(cls,owner_id:str,session_topic:str,db_path:str,auto_create = True) -> 'AIChatSession':
db = cls._dbs.get(db_path)
if db is None:
db = ChatSessionDB(db_path)
cls._dbs[db_path] = db
result = None
session = db.get_chatsession_by_owner_topic(owner_id,session_topic)
if session is None:
if auto_create:
session_id = "CS#" + uuid.uuid4().hex
db.insert_chatsession(session_id,owner_id,session_topic,datetime.datetime.now())
result = AIChatSession(owner_id,session_id,db)
else:
result = AIChatSession(owner_id,session[0],db)
result.topic = session_topic
result.summarize_pos = session[4]
result.summary = session[5]
result.openai_thread_id = session[6]
return result
@classmethod
def get_session_by_id(cls,session_id:str,db_path:str)->'AIChatSession':
db = cls._dbs.get(db_path)
if db is None:
db = ChatSessionDB(db_path)
cls._dbs[db_path] = db
result = None
session = db.get_chatsession_by_id(session_id)
if session is None:
return None
else:
result = AIChatSession(session[1],session[0],db)
result.topic = session[2]
result.summarize_pos = session[4]
result.summary = session[5]
result.openai_thread_id = session[6]
return result
@classmethod
def list_session(cls,owner_id:str,db_path:str) -> list[str]:
db = cls._dbs.get(db_path)
if db is None:
db = ChatSessionDB(db_path)
cls._dbs[db_path] = db
result = db.list_chatsessions(owner_id,16,0)
result_ids = []
for r in result:
result_ids.append(r[0])
return result_ids
def __init__(self,owner_id:str, session_id:str, db:ChatSessionDB) -> None:
self.owner_id :str = owner_id
self.session_id : str = session_id
self.db : ChatSessionDB = db
self.topic : str = None
self.start_time : str = None
self.summarize_pos : int = 0
self.summary = None
self.openai_thread_id = None
def get_owner_id(self) -> str:
return self.owner_id
def read_history(self, number:int=10,offset=0,order="revers") -> [AgentMsg]:
if order == "revers":
msgs = self.db.get_messages(self.session_id, number, offset)
else:
msgs = self.db.read_message(self.session_id, number, offset)
result = []
for msg in msgs:
agent_msg = AgentMsg()
agent_msg.msg_id = msg[0]
agent_msg.session_id = msg[1]
agent_msg.msg_type = AgentMsgType(msg[2])
agent_msg.prev_msg_id = msg[3]
agent_msg.sender = msg[4]
agent_msg.target = msg[5]
agent_msg.create_time = msg[6]
agent_msg.topic = msg[7]
if msg[8] is not None:
agent_msg.mentions = json.loads(msg[8])
agent_msg.body_mime = msg[9]
agent_msg.body = msg[10]
agent_msg.func_name = msg[11]
if msg[12] is not None:
agent_msg.args = json.loads(msg[12])
agent_msg.result_str = msg[13]
agent_msg.done_time = msg[14]
agent_msg.status = AgentMsgStatus(msg[15])
result.append(agent_msg)
return result
def append(self,msg:AgentMsg) -> None:
msg.session_id = self.session_id
self.db.insert_message(msg)
def update_think_progress(self,progress:int,new_summary:str) -> None:
self.db.update_session_summary(self.session_id,progress,new_summary)
self.summarize_pos = progress
self.summary = new_summary
def update_openai_thread_id(self,thread_id:str) -> None:
self.db.update_session_thread_id(self.session_id,thread_id)
self.openai_thread_id = thread_id
#def attach_event_handler(self,handler) -> None:
# """chat session changed event handler"""
# pass
#TODO : add iterator interface for read chat history
+80
View File
@@ -0,0 +1,80 @@
import logging
from .agent_base import AgentPrompt
class AIRole:
def __init__(self) -> None:
self.agent_instance_id : str = None
self.role_name : str = None
self.role_id :str = None # $workflow_id.$sub_workflow_id.$role_name
self.fullname : str = None
self.agent_name : str = None
self.prompt : AgentPrompt = None
self.introduce : str = None
self.agent = None
self.enable_function_list : list[str] = None
self.history_len = 10
def load_from_config(self,config:dict) -> bool:
name_node = config.get("name")
if name_node is None:
logging.error("role name is not found!")
return False
self.role_name = name_node
agent_id_node = config.get("agent")
if agent_id_node is None:
logging.error("agent id is not found!")
return False
self.agent_name = agent_id_node
prompt_node = config.get("prompt")
if prompt_node:
self.prompt = AgentPrompt()
if self.prompt.load_from_config(prompt_node) is False:
logging.error("load prompt failed!")
return False
intro_node = config.get("intro")
if intro_node is not None:
self.introduce = intro_node
history_node = config.get("history_len")
if history_node is not None:
self.history_len = int(history_node)
if config.get("enable_function") is not None:
self.enable_function_list = config["enable_function"]
def get_role_id(self) -> str:
return self.role_id
def get_intro(self) -> str:
return self.introduce
def get_name(self) -> str:
return self.role_name
def get_prompt(self) -> AgentPrompt:
return self.prompt
class AIRoleGroup:
def __init__(self) -> None:
self.roles : dict[str,AIRole] = {}
self.owner_name : str = None
def load_from_config(self,config:dict) -> bool:
for k,v in config.items():
role = AIRole()
if role.load_from_config(v) is False:
logging.error(f"load role {k} failed!")
return False
role.role_id = self.owner_name + "." + k
self.roles[k] = role
return True
def get(self,role_name:str) -> AIRole:
return self.roles.get(role_name)
+519
View File
@@ -0,0 +1,519 @@
import logging
import asyncio
import json
import os
import time
from asyncio import Queue
from typing import Optional,Tuple,List
from abc import ABC, abstractmethod
from ..proto.compute_task import *
from ..proto.agent_msg import *
from .agent_base import *
from .chatsession import AIChatSession
from .role import AIRole,AIRoleGroup
from .ai_function import AIFunction,FunctionItem
from ..frame.compute_kernel import ComputeKernel
from ..frame.bus import AIBus
from ..environment.environment import Environment,EnvironmentEvent
from ..environment.workflow_env import WorkflowEnvironment
logger = logging.getLogger(__name__)
class MessageFilter:
def __init__(self) -> None:
self.filters = {}
def select(self,msg:AgentMsg) -> str:
star_target = self.filters.get("*")
if star_target is not None:
return star_target
# TODO: add more filter
return None
def load_from_config(self,config:dict) -> bool:
self.filters = config
return True
class Workflow:
def __init__(self) -> None:
self.workflow_name : str = None
self.workflow_id : str = None
self.rule_prompt : AgentPrompt = None
self.workflow_config = None
self.role_group : dict = None
self.input_filter : MessageFilter= None
self.connected_environment = {}
self.sub_workflows = {}
self.owner_workflow = None
self.db_file = None
self.env_db_file = None
self.workflow_env:WorkflowEnvironment = None
self.is_start = False
self.msg_queue = Queue()
def get_bus(self) -> AIBus:
return AIBus.get_default_bus()
def set_owner(self,owner):
self.owner_workflow = owner
def load_from_config(self,config:dict) -> bool:
if config is None:
return False
if config.get("name") is None:
logger.error("workflow config must have name")
return False
self.workflow_name = config.get("name")
if self.owner_workflow is None:
self.workflow_id = self.workflow_name
else:
self.workflow_id = self.owner_workflow.workflow_id + "." + self.workflow_name
self.db_file = self.owner_workflow.db_file
if config.get("prompt") is not None:
self.rule_prompt = AgentPrompt()
if self.rule_prompt.load_from_config(config.get("prompt")) is False:
logger.error("Workflow load prompt failed")
return False
if config.get("roles") is None:
logger.error("workflow config must have roles")
return False
self.role_group = AIRoleGroup()
self.role_group.owner_name = self.workflow_id
if self.role_group.load_from_config(config.get("roles")) is False:
logger.error("Workflow load role_group failed")
return False
if config.get("filter") is not None:
self.input_filter = MessageFilter()
if self.input_filter.load_from_config(config.get("filter")) is False:
logger.error("Workflow load input_filter failed")
return False
if self.owner_workflow is None:
self.env_db_file = os.path.dirname(self.db_file) + "/" + self.workflow_id + "_env.db"
else:
self.env_db_file = self.owner_workflow.env_db_file
self.workflow_env = WorkflowEnvironment(self.workflow_id,self.env_db_file)
env_ndoe = config.get("enviroment")
if env_ndoe is not None:
if self._load_env_from_config(env_ndoe) is False:
logger.error("Workflow load env failed")
return False
connected_env_ndoe = config.get("connected_env")
if connected_env_ndoe is not None:
for _node in connected_env_ndoe:
env_id = _node.get("env_id")
if env_id is None:
continue
remote_env = Environment.get_env_by_id(env_id)
if remote_env is None:
logger.error(f"Workflow load connected_env failed, env {env_id} not found!")
return False
self.connect_to_environment(remote_env,_node.get("event2msg"))
sub_workflows = config.get("sub_workflows")
if sub_workflows is not None:
if self._load_sub_workflows(sub_workflows) is False:
logger.error("Workflow load sub workflows failed")
return False
return True
def _load_env_from_config(self,config:dict) -> bool:
for k,v in config.items():
self.workflow_env.set_value(k,v,False)
def _load_sub_workflows(self,config:dict) -> bool:
for k,v in config.items():
sub_workflow = Workflow()
sub_workflow.set_owner(self)
if sub_workflow.load_from_config(v) is False:
logger.error(f"load sub workflow {k} failed!")
return False
self.sub_workflows[k] = sub_workflow
return True
def _parse_msg_target(self,s:str)->list[str]:
return s.split(".")
async def _forword_msg(self,inner_obj_id,msg):
i : int = 1
current_workflow = self
while i < len(inner_obj_id):
if i == len(inner_obj_id) - 1:
the_role : AIRole = current_workflow.role_group.get(inner_obj_id[i])
current_workflow_chatsession = AIChatSession.get_session(current_workflow.workflow_id,msg.sender + "#" + msg.topic,current_workflow.db_file)
if the_role is not None:
return await current_workflow.role_process_msg(msg,the_role,current_workflow_chatsession)
sub_workflow = current_workflow.sub_workflows.get(inner_obj_id[i])
if sub_workflow is not None:
return await sub_workflow._process_msg(msg)
logger.error(f"{msg.target} not found! forword message failed!")
return None
else:
current_workflow = current_workflow.sub_workflows.get(inner_obj_id[i])
if current_workflow is None:
logger.error(f"sub workflow {inner_obj_id[i]} not found!")
return None
i += 1
logger.error(f"{msg.target} not found! forword message failed!")
return None
def get_workflow_id_from_target(self,target:str) -> str:
target_list = target.split(".")
if len(target_list) == 0:
return target
else:
result_str = ""
p = 0
for s in target_list:
p = p + 1
result_str += s
if p < len(target_list)-1:
result_str += "."
else:
return result_str
async def _process_msg(self,msg:AgentMsg) -> AgentMsg:
real_target = msg.target.split(".")[0]
targets = self._parse_msg_target(msg.target)
if len(targets) > 1:
return await self._forword_msg(targets,msg)
#0 we don't support workflow join a group right now, this cloud be a feture in future
if msg.mentions is not None:
logger.warn(f"workflow {self.workflow_id} recv a group chat message,not support ignore!")
error_resp = msg.create_error_resp(f"workflow {self.workflow_id} recv a group chat message,not support ignore!")
return error_resp
#1. workflow start process message
# this is workflow's group_chat session
session_topic = msg.sender + "#" + msg.topic
chatsesssion = AIChatSession.get_session(self.workflow_id,session_topic,self.db_file)
#2. find role by msg.mentions or workflow's selector logic
if msg.mentions is not None:
if not self.workflow_id in msg.mentions:
chatsesssion.append(msg)
logger.info(f"workflow {self.workflow_id} recv a group chat message from {msg.sender},but is not mentioned,ignore!")
return None
for mention in msg.mentions:
this_role = self.role_group.get(mention)
if this_role is not None:
return await self.role_process_msg(msg,this_role,chatsesssion)
if self.input_filter is not None:
select_role_id = self.input_filter.select(msg)
if select_role_id is not None:
select_role = self.role_group.get(select_role_id)
if select_role is None:
logger.error(f"input_filter return invalid role id:{select_role_id}, role not found in role_group")
return None
return await self.role_process_msg(msg,select_role,chatsesssion)
else:
logger.error(f"input_filter return None for :{msg.body}")
return None
err_str = f"{self.workflow_id}:no role can process this msg:{msg.body}"
logger.error(err_str)
error_resp = msg.create_error_resp(err_str)
return error_resp
async def role_post_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession):
msg.sender = the_role.get_role_id()
target_role = self.role_group.get(msg.target)
if target_role:
msg.target = target_role.get_role_id()
logger.info(f"{msg.sender} post message {msg.msg_id} to inner role: {msg.target}")
asyncio.create_task(self.role_process_msg(msg,target_role,workflow_chat_session))
return
target_workflow = self.sub_workflows.get(msg.target)
if target_workflow:
msg.target = target_workflow.workflow_id
logger.info(f"{msg.sender} post message {msg.msg_id} to sub workflow: {msg.target}")
asyncio.create_task(target_workflow._process_msg(msg))
logger.info(f"{msg.sender} post message {msg.msg_id} to AIBus: {msg.target}")
await self.get_bus().post_message(msg,msg.target)
return
async def role_send_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession):
msg.sender = the_role.get_role_id()
target_role = self.role_group.get(msg.target)
if target_role:
# msg.target = target_role.get_role_id()
logger.info(f"{msg.sender} send message {msg.msg_id} to inner role: {msg.target}")
return await self.role_process_msg(msg,target_role,workflow_chat_session)
target_workflow = self.sub_workflows.get(msg.target)
if target_workflow:
# msg.target = target_workflow.workflow_id
logger.info(f"{msg.sender} send message {msg.msg_id} to sub workflow: {msg.target}")
return await target_workflow._process_msg(msg)
logger.info(f"{msg.sender} post message {msg.msg_id} to AIBus: {msg.target}")
return await self.get_bus().send_message(msg)
async def role_call(self,func_item:FunctionItem,the_role:AIRole):
logger.info(f"{the_role.role_id} call {func_item.name} ")
arguments = func_item.args
func_node : AIFunction = self.workflow_env.get_ai_function(func_item.name)
if func_node is None:
return "execute failed,function not found"
result_str:str = await func_node.execute(**arguments)
return result_str
async def role_post_call(self,func_item:FunctionItem,the_role:AIRole):
logger.info(f"{the_role.role_id} post call {func_item.name} ")
return await self.role_call(func_item,the_role)
def _format_msg_by_env_value(self,prompt:AgentPrompt):
if self.workflow_env is None:
return
for msg in prompt.messages:
old_content = msg.get("content")
msg["content"] = old_content.format_map(self.workflow_env)
def _get_inner_functions(self,the_role:AIRole) -> dict:
all_inner_function = self.workflow_env.get_all_ai_functions()
if all_inner_function is None:
return None
result_func = []
for inner_func in all_inner_function:
func_name = inner_func.get_name()
if the_role.enable_function_list is not None:
if len(the_role.enable_function_list) > 0:
if func_name not in the_role.enable_function_list:
logger.debug(f"agent {the_role.agent.agent_id} ignore inner func:{func_name}")
continue
else:
continue
this_func = {}
this_func["name"] = func_name
this_func["description"] = inner_func.get_description()
this_func["parameters"] = inner_func.get_parameters()
result_func.append(this_func)
if len(result_func) > 0:
return result_func
return None
async def _role_execute_func(self,the_role:AIRole,inenr_func_call_node:dict,prompt:AgentPrompt,org_msg:AgentMsg,stack_limit = 5) -> [str,int]:
func_name = inenr_func_call_node.get("name")
arguments = json.loads(inenr_func_call_node.get("arguments"))
ineternal_call_record = AgentMsg.create_internal_call_msg(func_name,arguments,org_msg.get_msg_id(),org_msg.target)
func_node : AIFunction = self.workflow_env.get_ai_function(func_name)
result_str : str = ""
if func_node is None:
result_str = f"execute {func_name} failed,function not found"
else:
try:
result_str = await func_node.execute(**arguments)
except Exception as e:
result_str = f"execute {func_name} error:{str(e)}"
logger.error(f"llm execute inner func:{func_name} error:{e}")
logger.exception(e)
inner_functions = self._get_inner_functions(the_role)
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
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)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"llm compute error:{task_result.error_str}")
return task_result.error_str,1
ineternal_call_record.result_str = task_result.result_str
ineternal_call_record.done_time = time.time()
org_msg.inner_call_chain.append(ineternal_call_record)
if stack_limit > 0:
result_message = task_result.result.get("message")
if result_message:
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
return await self._role_execute_func(the_role,inner_func_call_node,prompt,org_msg,stack_limit-1)
else:
return task_result.result_str,0
def _is_in_same_workflow(self,msg) -> bool:
pass
async def role_process_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession) -> AgentMsg:
msg.target = the_role.get_role_id()
prompt = AgentPrompt()
prompt.append(the_role.agent.agent_prompt)
prompt.append(self.get_workflow_rule_prompt())
prompt.append(the_role.get_prompt())
# prompt.append(self._get_function_prompt(the_role.get_name()))
# prompt.append(self._get_knowlege_prompt(the_role.get_name()))
#support group chat, user content include sender name!
prompt.append(await self._get_prompt_from_session(the_role,workflow_chat_session))
msg_prompt = AgentPrompt()
msg_prompt.messages = [{"role":"user","content":f"user name is {msg.sender}, his question is :{msg.body}"}]
prompt.append(msg_prompt)
self._format_msg_by_env_value(prompt)
inner_functions = self._get_inner_functions(the_role)
async def _do_process_msg():
#TODO: send msg to agent might be better?
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)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"llm compute error:{task_result.error_str}")
error_resp = msg.create_error_resp(task_result.error_str)
return error_resp
result_str = task_result.result_str
logger.info(f"{the_role.role_id} process {msg.sender}:{msg.body},llm str is :{result_str}")
result_message = task_result.result.get("message")
if result_message:
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
#TODO to save more token ,can i use msg_prompt?
result_str,r_code = await self._role_execute_func(the_role,inner_func_call_node,prompt,msg)
if r_code != 0:
error_resp = msg.create_error_resp(result_str)
return error_resp
result : LLMResult = LLMResult.from_str(result_str)
for postmsg in result.post_msgs:
postmsg.prev_msg_id = msg.get_msg_id()
# might be craete a new msg.topic for this postmsg
postmsg.topic = msg.topic
await self.role_post_msg(postmsg,the_role,workflow_chat_session)
if not self._is_in_same_workflow(postmsg):
role_sesion = AIChatSession.get_session(the_role.get_role_id(),f"{postmsg.target}#{msg.topic}",self.db_file)
role_sesion.append(postmsg)
else:
# message will be saved in role.process_message
pass
for post_call in result.post_calls:
action_msg = msg.create_action_msg(post_call[0],post_call[1],the_role.get_role_id())
workflow_chat_session.append(action_msg)
await self.role_post_call(post_call,the_role)
#save post_call
result_prompt_str = ""
match result.state:
case "ignore":
return None
case "reponsed":
resp_msg = msg.create_resp_msg(result.resp)
resp_msg.sender = the_role.get_role_id()
# It is always the person handling the messages who puts them into the session.
workflow_chat_session.append(msg)
workflow_chat_session.append(resp_msg)
#await self.get_bus().resp_message(resp_msg)
return resp_msg
case "waiting":
for sendmsg in result.send_msgs:
target = sendmsg.target
sendmsg.topic = msg.topic
sendmsg.prev_msg_id = msg.get_msg_id()
send_resp = await self.role_send_msg(sendmsg,the_role,workflow_chat_session)
if send_resp is not None:
result_prompt_str += f"\n# {target} response is : \n{send_resp.body}"
if not self._is_in_same_workflow(sendmsg):
role_sesion = AIChatSession.get_session(the_role.get_role_id(),f"{sendmsg.target}#{sendmsg.topic}",self.db_file)
role_sesion.append(sendmsg)
role_sesion.append(send_resp)
else:
# message will be saved in role.process_message
pass
this_llm_resp_prompt = AgentPrompt()
this_llm_resp_prompt.messages = [{"role":"assistant","content":result_str}]
prompt.append(this_llm_resp_prompt)
result_prompt = AgentPrompt()
result_prompt.messages = [{"role":"user","content":result_prompt_str}]
prompt.append(result_prompt)
return await _do_process_msg()
return await _do_process_msg()
async def _get_prompt_from_session(self,the_role:AIRole,chatsession:AIChatSession) -> AgentPrompt:
messages = chatsession.read_history(the_role.history_len) # read last 10 message
result_prompt = AgentPrompt()
for msg in reversed(messages):
if msg.sender == the_role.role_id:
result_prompt.messages.append({"role":"assistant","content":msg.body})
else:
result_prompt.messages.append({"role":"user","content":f"{msg.body}"})
return result_prompt
def _get_knowlege_prompt(self,role_name:str) -> AgentPrompt:
pass
def get_workflow_rule_prompt(self) -> AgentPrompt:
return self.rule_prompt
def _env_event_to_msg(self,env_event:EnvironmentEvent) -> AgentMsg:
pass
def get_inner_environment(self,env_id:str) -> Environment:
pass
def connect_to_environment(self,the_env:Environment,conn_info:dict) -> None:
if the_env is not None:
self.workflow_env.add_owner_env(the_env)
#for event2msg in conn_info:
# for k,v in event2msg:
# if k == "role":
# continue
# else:
#
# def _env_msg_handler(env_event:EnvironmentEvent) -> None:
# the_msg:AgentMsg= self._env_event_to_msg(env_event)
# self.role_post_msg
# the_env.attach_event_handler(k,_env_msg_handler)
# break