1) Do some rename refactor ,prepare for LLMProcess refactor

2) Fix merge bugs.
This commit is contained in:
Liu Zhicong
2023-12-06 13:31:05 -08:00
parent 35d204ac05
commit 8739bf6a76
44 changed files with 693 additions and 2043 deletions
+47 -45
View File
@@ -13,10 +13,12 @@ import copy
import sys
from ..proto.agent_msg import AgentMsg
from ..proto.ai_function import *
from ..proto.agent_task import *
from ..proto.compute_task import *
from .agent_base import *
from .chatsession import *
from .ai_function import *
from ..environment.workspace_env import WorkspaceEnvironment, TodoListType
from ..frame.contact_manager import ContactManager,Contact,FamilyMember
@@ -69,7 +71,7 @@ class AIAgentTemplete:
self.template_id:str = None
self.introduce:str = None
self.author:str = None
self.prompt:AgentPrompt = None
self.prompt:LLMPrompt = None
def load_from_config(self,config:dict) -> bool:
if config.get("llm_model_name") is not None:
@@ -79,7 +81,7 @@ class AIAgentTemplete:
if config.get("template_id") is not None:
self.template_id = config["template_id"]
if config.get("prompt") is not None:
self.prompt = AgentPrompt()
self.prompt = LLMPrompt()
if self.prompt.load_from_config(config["prompt"]) is False:
logger.error("load prompt from config failed!")
return False
@@ -90,9 +92,9 @@ class AIAgentTemplete:
class AIAgent(BaseAIAgent):
def __init__(self) -> None:
self.role_prompt:AgentPrompt = None
self.agent_prompt:AgentPrompt = None
self.agent_think_prompt:AgentPrompt = None
self.role_prompt:LLMPrompt = None
self.agent_prompt:LLMPrompt = None
self.agent_think_prompt:LLMPrompt = None
self.llm_model_name:str = None
self.max_token_size:int = 128000
self.agent_energy = 15
@@ -149,26 +151,26 @@ class AIAgent(BaseAIAgent):
self.enable_thread = bool(config["enable_thread"])
if config.get("prompt") is not None:
self.agent_prompt = AgentPrompt()
self.agent_prompt = LLMPrompt()
self.agent_prompt.load_from_config(config["prompt"])
if config.get("think_prompt") is not None:
self.agent_think_prompt = AgentPrompt()
self.agent_think_prompt = LLMPrompt()
self.agent_think_prompt.load_from_config(config["think_prompt"])
def load_todo_config(todo_type:str) -> bool:
todo_config = config.get(todo_type)
if todo_config is not None:
if todo_config.get("do") is not None:
prompt = AgentPrompt()
prompt = LLMPrompt()
prompt.load_from_config(todo_config["do"])
self.todo_prompts[todo_type]["do"] = prompt
if todo_config.get("check") is not None:
prompt = AgentPrompt()
prompt = LLMPrompt()
prompt.load_from_config(todo_config["check"])
self.todo_prompts[todo_type]["check"] = prompt
if todo_config.get("review_prompt") is not None:
prompt = AgentPrompt()
prompt = LLMPrompt()
prompt.load_from_config(todo_config["review_prompt"])
self.todo_prompts[todo_type]["review"] = prompt
@@ -224,16 +226,16 @@ class AIAgent(BaseAIAgent):
def get_max_token_size(self) -> int:
return self.max_token_size
def get_agent_role_prompt(self) -> AgentPrompt:
def get_agent_role_prompt(self) -> LLMPrompt:
return self.role_prompt
def _get_remote_user_prompt(self,remote_user:str) -> AgentPrompt:
def _get_remote_user_prompt(self,remote_user:str) -> LLMPrompt:
cm = ContactManager.get_instance()
contact = cm.find_contact_by_name(remote_user)
if contact is None:
#create guest prompt
if self.guest_prompt_str is not None:
prompt = AgentPrompt()
prompt = LLMPrompt()
prompt.system_message = {"role":"system","content":self.guest_prompt_str}
return prompt
return None
@@ -241,25 +243,25 @@ class AIAgent(BaseAIAgent):
if contact.is_family_member:
if self.owner_promp_str is not None:
real_str = self.owner_promp_str.format_map(contact.to_dict())
prompt = AgentPrompt()
prompt = LLMPrompt()
prompt.system_message = {"role":"system","content":real_str}
return prompt
else:
if self.contact_prompt_str is not None:
real_str = self.contact_prompt_str.format_map(contact.to_dict())
prompt = AgentPrompt()
prompt = LLMPrompt()
prompt.system_message = {"role":"system","content":real_str}
return prompt
return None
def get_agent_prompt(self) -> AgentPrompt:
def get_agent_prompt(self) -> LLMPrompt:
return self.agent_prompt
async def _get_agent_think_prompt(self) -> AgentPrompt:
async def _get_agent_think_prompt(self) -> LLMPrompt:
return self.agent_think_prompt
def _format_msg_by_env_value(self,prompt:AgentPrompt):
def _format_msg_by_env_value(self,prompt:LLMPrompt):
for msg in prompt.messages:
old_content = msg.get("content")
msg["content"] = old_content.format_map(self.agent_workspace)
@@ -284,7 +286,7 @@ class AIAgent(BaseAIAgent):
return image_path
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
msg_prompt = AgentPrompt()
msg_prompt = LLMPrompt()
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
need_process = False
if msg.is_image_msg():
@@ -378,7 +380,7 @@ class AIAgent(BaseAIAgent):
workspace = self.get_workspace_by_msg(msg)
prompt = AgentPrompt()
prompt = LLMPrompt()
if workspace:
prompt.append(workspace.get_prompt())
prompt.append(workspace.get_role_prompt(self.agent_id))
@@ -390,7 +392,7 @@ class AIAgent(BaseAIAgent):
if self.need_session_summmary(msg,chatsession):
# get relate session(todos) summary
summary = self.llm_select_session_summary(msg,chatsession)
prompt.append(AgentPrompt(summary))
prompt.append(LLMPrompt(summary))
known_info_str = "# Known information\n"
have_known_info = False
@@ -399,7 +401,7 @@ class AIAgent(BaseAIAgent):
have_known_info = True
known_info_str += f"## todo\n{todos_str}\n"
inner_functions,function_token_len = BaseAIAgent.get_inner_functions(self.agent_workspace)
system_prompt_len = self.token_len(prompt=prompt)
system_prompt_len = ComputeKernel.llm_num_tokens(prompt)
input_len = len(msg.body)
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
history_str,history_token_len = await self._get_prompt_from_session_for_groupchat(chatsession,system_prompt_len + function_token_len,input_len)
@@ -410,7 +412,7 @@ class AIAgent(BaseAIAgent):
known_info_str += history_str
if have_known_info:
known_info_prompt = AgentPrompt(known_info_str)
known_info_prompt = LLMPrompt(known_info_str)
prompt.append(known_info_prompt) # chat context
prompt.append(msg_prompt)
@@ -436,7 +438,7 @@ class AIAgent(BaseAIAgent):
final_result = llm_result.resp
await workspace.exec_op_list(llm_result.op_list,self.agent_id)
await workspace.exec_op_list(llm_result.action_list,self.agent_id)
is_ignore = False
result_prompt_str = ""
@@ -471,12 +473,12 @@ class AIAgent(BaseAIAgent):
return None
async def _get_history_prompt_for_think(self,chatsession:AIChatSession,summary:str,system_token_len:int,pos:int)->(AgentPrompt,int):
async def _get_history_prompt_for_think(self,chatsession:AIChatSession,summary:str,system_token_len:int,pos:int)->(LLMPrompt,int):
history_len = (self.max_token_size * 0.7) - system_token_len
messages = chatsession.read_history(self.history_len,pos,"natural") # read
result_token_len = 0
result_prompt = AgentPrompt()
result_prompt = LLMPrompt()
have_summary = False
if summary is not None:
if len(summary) > 1:
@@ -511,7 +513,7 @@ class AIAgent(BaseAIAgent):
history_len = (self.max_token_size * 0.7) - system_token_len - input_token_len
messages = chatsession.read_history(self.history_len) # read
result_token_len = 0
result_prompt = AgentPrompt()
result_prompt = LLMPrompt()
read_history_msg = 0
for msg in reversed(messages):
read_history_msg += 1
@@ -569,13 +571,13 @@ class AIAgent(BaseAIAgent):
async def _llm_read_report(self,report:AgentReport,worksapce:WorkspaceEnvironment):
work_summary = worksapce.get_work_summary(self.agent_id)
prompt : AgentPrompt = AgentPrompt()
prompt : LLMPrompt = LLMPrompt()
prompt.append(self.agent_prompt)
prompt.append(worksapce.get_role_prompt(self.agent_id))
prompt.append(self.read_report_prompt)
# report is a message from other agent(human) about work
prompt.append(AgentPrompt(work_summary))
prompt.append(AgentPrompt(report.content))
prompt.append(LLMPrompt(work_summary))
prompt.append(LLMPrompt(report.content))
task_result:ComputeTaskResult = await self.do_llm_complection(prompt)
@@ -606,7 +608,7 @@ class AIAgent(BaseAIAgent):
do_prompts = self._can_do_todo(todo_list_type, todo)
if do_prompts:
prompt : AgentPrompt = AgentPrompt()
prompt : LLMPrompt = LLMPrompt()
prompt.append(self.agent_prompt)
prompt.append(workspace.get_role_prompt(self.agent_id))
prompt.append(do_prompts)
@@ -635,13 +637,13 @@ class AIAgent(BaseAIAgent):
check_prompts = self._can_check_todo(todo_list_type, todo)
if check_prompts:
prompt : AgentPrompt = AgentPrompt()
prompt : LLMPrompt = LLMPrompt()
prompt.append(self.agent_prompt)
prompt.append(workspace.get_role_prompt(self.agent_id))
prompt.append(check_prompts)
if todo.last_check_result:
prompt.append(AgentPrompt(todo.last_check_result))
prompt.append(LLMPrompt(todo.last_check_result))
prompt.append(todo.detail)
prompt.append(todo.result)
@@ -669,7 +671,7 @@ class AIAgent(BaseAIAgent):
prompt.append(review_prompts)
todo_tree = todo_list.get_todo_tree("/")
prompt.append(AgentPrompt(todo_tree))
prompt.append(LLMPrompt(todo_tree))
do_result : AgentTodoResult = await self._llm_review_todo(todo, prompt, workspace)
todo.last_review_time = datetime.datetime.now().timestamp()
@@ -690,7 +692,7 @@ class AIAgent(BaseAIAgent):
logger.info(f"agent {self.agent_id} ,check:{check_count} todo,do:{do_count} todo.")
def _can_review_todo(self, todo_list_type: TodoListType, todo:AgentTodo) -> AgentPrompt:
def _can_review_todo(self, todo_list_type: TodoListType, todo:AgentTodo) -> LLMPrompt:
do_prompts = self.todo_prompts[todo_list_type].get("review")
if not do_prompts:
return None
@@ -701,7 +703,7 @@ class AIAgent(BaseAIAgent):
return do_prompts
def _can_check_todo(self, todo_list_type: TodoListType, todo:AgentTodo) -> AgentPrompt:
def _can_check_todo(self, todo_list_type: TodoListType, todo:AgentTodo) -> LLMPrompt:
do_prompts = self.todo_prompts[todo_list_type].get("check")
if not do_prompts:
return None
@@ -720,7 +722,7 @@ class AIAgent(BaseAIAgent):
return do_prompts
def _can_do_todo(self, todo_list_type: TodoListType, todo:AgentTodo) -> AgentPrompt:
def _can_do_todo(self, todo_list_type: TodoListType, todo:AgentTodo) -> LLMPrompt:
do_prompts = self.todo_prompts[todo_list_type].get("do")
if not do_prompts:
return None
@@ -739,7 +741,7 @@ class AIAgent(BaseAIAgent):
return do_prompts
async def _llm_do_todo(self, todo: AgentTodo, prompt: AgentPrompt, workspace: WorkspaceEnvironment) -> AgentTodoResult:
async def _llm_do_todo(self, todo: AgentTodo, prompt: LLMPrompt, workspace: WorkspaceEnvironment) -> AgentTodoResult:
result = AgentTodoResult()
task_result:ComputeTaskResult = await self.do_llm_complection(prompt, is_json_resp=True)
@@ -763,7 +765,7 @@ class AIAgent(BaseAIAgent):
resp = await AIBus.get_default_bus().post_message(msg)
logging.info(f"agent {self.agent_id} send msg to {msg.target} result:{resp}")
result_str, have_error = await workspace.exec_op_list(llm_result.op_list, self.agent_id)
result_str, have_error = await workspace.exec_op_list(llm_result.action_list, self.agent_id)
if have_error:
result.result_code = AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR
#result.error_str = error_str
@@ -771,7 +773,7 @@ class AIAgent(BaseAIAgent):
result.result_str = result_str
return result
async def _llm_check_todo(self, todo: AgentTodo, prompt: AgentPrompt, workspace: WorkspaceEnvironment) -> AgentTodoResult:
async def _llm_check_todo(self, todo: AgentTodo, prompt: LLMPrompt, workspace: WorkspaceEnvironment) -> AgentTodoResult:
result = AgentTodoResult()
inner_functions,_ = BaseAIAgent.get_inner_functions(workspace)
@@ -786,7 +788,7 @@ class AIAgent(BaseAIAgent):
todo.last_check_result = task_result.result_str
return result
async def _llm_review_todo(self, todo:AgentTodo, prompt: AgentPrompt, workspace: WorkspaceEnvironment):
async def _llm_review_todo(self, todo:AgentTodo, prompt: LLMPrompt, workspace: WorkspaceEnvironment):
inner_functions,_ = BaseAIAgent.get_inner_functions(workspace)
task_result:ComputeTaskResult = await self.do_llm_complection(prompt,inner_functions=inner_functions)
@@ -842,10 +844,10 @@ class AIAgent(BaseAIAgent):
while True:
cur_pos = chatsession.summarize_pos
summary = chatsession.summary
prompt:AgentPrompt = AgentPrompt()
prompt:LLMPrompt = LLMPrompt()
#prompt.append(self._get_agent_prompt())
prompt.append(await self._get_agent_think_prompt())
system_prompt_len = self.token_len(prompt=prompt)
system_prompt_len = ComputeKernel.llm_num_tokens(prompt)
#think env?
history_prompt,next_pos = await self._get_history_prompt_for_think(chatsession,summary,system_prompt_len,cur_pos)
prompt.append(history_prompt)
@@ -864,7 +866,7 @@ class AIAgent(BaseAIAgent):
chatsession.update_think_progress(next_pos,new_summary)
return
async def get_prompt_from_session(self,chatsession:AIChatSession,system_token_len,input_token_len) -> AgentPrompt:
async def get_prompt_from_session(self,chatsession:AIChatSession,system_token_len,input_token_len) -> LLMPrompt:
# TODO: get prompt from group chat is different from single chat
if self.enable_thread:
return None
+10 -405
View File
@@ -11,400 +11,15 @@ import shlex
import json
from typing import List, Tuple
from .ai_function import FunctionItem, AIFunction
from ..proto.agent_msg import AgentMsg, AgentMsgType
from ..proto.compute_task import ComputeTaskResult,ComputeTaskResultCode
from ..environment.environment import BaseEnvironment
from ..proto.ai_function import *
from ..proto.agent_msg import *
from ..proto.compute_task import *
from ..environment.environment import *
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 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)
# if llm_result_str.startswith("json"):
# return LLMResult.from_json_str(llm_result_str[4:])
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_CANCEL = "cancel"
TODO_STATE_DONE = "done"
TODO_STATE_REVIEWED = "reviewed"
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 to_prompt(self) -> AgentPrompt:
json_str = json.dumps(self.raw_obj)
return AgentPrompt(json_str)
def can_review(self) -> bool:
if self.state != AgentTodo.TODO_STATE_DONE:
return False
now = datetime.now().timestamp()
if self.last_review_time:
time_diff = now - self.last_review_time
if time_diff < 60*15:
logger.info(f"todo {self.title} is already reviewed, ignore")
return False
return True
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_CANCEL:
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):
@@ -420,19 +35,9 @@ class BaseAIAgent(abc.ABC):
def get_max_token_size(self) -> int:
pass
def token_len(self, text:str=None, prompt:AgentPrompt=None) -> int:
from ..frame.compute_kernel import ComputeKernel
if text:
return ComputeKernel.llm_num_tokens_from_text(text,self.get_llm_model_name())
elif prompt:
result = 0
if prompt.system_message:
result += ComputeKernel.llm_num_tokens_from_text(prompt.system_message.get("content"),self.get_llm_model_name())
for msg in prompt.messages:
result += ComputeKernel.llm_num_tokens_from_text(msg.get("content"),self.get_llm_model_name())
return result
else:
return 0
@abstractmethod
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
pass
@classmethod
def get_inner_functions(cls, env:BaseEnvironment) -> (dict,int):
@@ -458,7 +63,7 @@ class BaseAIAgent(abc.ABC):
async def do_llm_complection(
self,
prompt:AgentPrompt,
prompt:LLMPrompt,
org_msg:AgentMsg=None,
env:BaseEnvironment=None,
inner_functions=None,
@@ -503,7 +108,7 @@ class BaseAIAgent(abc.ABC):
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
call_prompt : AgentPrompt = copy.deepcopy(prompt)
call_prompt : LLMPrompt = copy.deepcopy(prompt)
func_msg = copy.deepcopy(result_message)
del func_msg["tool_calls"]
call_prompt.messages.append(func_msg)
@@ -515,7 +120,7 @@ class BaseAIAgent(abc.ABC):
self,
env: BaseEnvironment,
inner_func_call_node: dict,
prompt: AgentPrompt,
prompt: LLMPrompt,
inner_functions: dict,
org_msg:AgentMsg,
stack_limit = 5
-203
View File
@@ -1,203 +0,0 @@
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:
@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
"""
pass
@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_description(self) -> str:
return self.description
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
class AIOperation:
@abstractmethod
def get_name(self) -> str:
"""
return the name of the operation (should be snake case)
"""
pass
@abstractmethod
def get_description(self) -> str:
"""
return a detailed description of what the operation does
"""
pass
@abstractmethod
async def execute(self, params: dict) -> str:
"""
Execute the function and return a JSON serializable dict.
The parameters are passed in the form of kwargs
"""
pass
class SimpleAIOperation(AIOperation):
def __init__(self,op:str,description:str,func_handler:Coroutine) -> None:
self.op = op
self.description = description
self.func_handler = func_handler
def get_name(self) -> str:
return self.op
def get_description(self) -> str:
return self.description
async def execute(self, params: Dict) -> str:
if self.func_handler is None:
return "error: function not implemented"
return await self.func_handler(params)
class AIFunctionOperation(AIOperation):
def __init__(self, func: AIFunction) -> None:
self.func = func
super().__init__()
@abstractmethod
def get_name(self) -> str:
return self.func.get_name()
@abstractmethod
def get_description(self) -> str:
return self.func.get_description()
@abstractmethod
async def execute(self, params: dict) -> str:
self.func.execute(**params)
-4
View File
@@ -1,4 +0,0 @@
# TODO: let agent develolp custmized behavior easily
class AgentBehavior:
def __init__(self) -> None:
pass
+149
View File
@@ -0,0 +1,149 @@
# Old name is behavior, I belive new name "llm_process" is better
from abc import ABC,abstractmethod
import copy
import json
import shlex
from typing import Any, Callable, Optional,Dict,Awaitable,List
from enum import Enum
from ..proto.compute_task import *
from ..proto.ai_function import *
from ..frame.compute_kernel import *
import logging
logger = logging.getLogger(__name__)
MIN_PREDICT_TOKEN_LEN = 32
class BaseLLMProcess:
def __init__(self) -> None:
self.enable_json_resp = False
self.model_name = "gpt-4"
self.max_token = 2000 # include input prompt
self.timeout = 1800 # 30 min
@abstractmethod
async def prepare_prompt(self) -> LLMPrompt:
pass
@abstractmethod
async def get_inner_function(self,func_name:str) -> AIFunction:
pass
async def _execute_inner_func(self,inner_func_call_node,prompt: LLMPrompt,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"LLMProcess execute inner func:{func_name} :\n\t {json.dumps(arguments)}")
func_node : AIFunction = await self.get_inner_function(func_name)
if func_node is None:
result_str: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"LLMProcess execute inner func:{func_name} error:\n\t{e}")
logger.info("LLMProcess execute inner func result:" + result_str)
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
if self.enable_json_resp:
resp_mode = "json"
else:
resp_mode = "text"
max_result_token = self.max_token - ComputeKernel.llm_num_tokens(prompt)
if max_result_token < MIN_PREDICT_TOKEN_LEN:
task_result = ComputeTaskResult()
task_result.result_code = ComputeTaskResultCode.ERROR
task_result.error_str = f"prompt too long,can not predict"
return task_result
task_result: ComputeTaskResult = await (ComputeKernel.get_instance().do_llm_completion(
prompt,
resp_mode=resp_mode,
mode_name=self.model_name,
max_token=max_result_token,
inner_functions=prompt.inner_functions,
timeout=self.timeout))
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"llm compute error:{task_result.error_str}")
return task_result
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"]#TODO: support tool_calls?
prompt.messages.append(func_msg)
else:
logger.error(f"inner function call stack limit reached")
task_result.result_code = ComputeTaskResultCode.ERROR
task_result.error_str = "inner function call stack limit reached"
return task_result
if inner_func_call_node:
return await self._execute_inner_func(inner_func_call_node,prompt,stack_limit-1)
else:
return task_result
async def process(self) -> LLMResult:
if self.enable_json_resp:
resp_mode = "json"
else:
resp_mode = "text"
prompt = await self.prepare_prompt()
max_result_token = self.max_token - ComputeKernel.llm_num_tokens(prompt)
if max_result_token < MIN_PREDICT_TOKEN_LEN:
return LLMResult.from_error_str(f"prompt too long,can not predict")
task_result: ComputeTaskResult = await (ComputeKernel.get_instance().do_llm_completion(
prompt,
resp_mode=resp_mode,
mode_name=self.model_name,
max_token=max_result_token,
inner_functions=prompt.inner_functions,
timeout=self.timeout))
if task_result.result_code != ComputeTaskResultCode.OK:
err_str = f"do_llm_completion error:{task_result.error_str}"
logger.error(err_str)
return LLMResult.from_error_str(err_str)
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 : LLMPrompt = 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_inner_func(inner_func_call_node,call_prompt)
# parse task_result to LLM Result
if self.enable_json_resp:
llm_result = LLMResult.from_json_str(task_result.result_str)
else:
llm_result = LLMResult.from_str(task_result.result_str)
# execute op_list in LLM Result?
return llm_result
#class LLMProcess
+4 -4
View File
@@ -1,6 +1,6 @@
import logging
from .agent_base import AgentPrompt
from .agent_base import LLMPrompt
class AIRole:
def __init__(self) -> None:
@@ -9,7 +9,7 @@ class AIRole:
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.prompt : LLMPrompt = None
self.introduce : str = None
self.agent = None
self.enable_function_list : list[str] = None
@@ -31,7 +31,7 @@ class AIRole:
prompt_node = config.get("prompt")
if prompt_node:
self.prompt = AgentPrompt()
self.prompt = LLMPrompt()
if self.prompt.load_from_config(prompt_node) is False:
logging.error("load prompt failed!")
return False
@@ -56,7 +56,7 @@ class AIRole:
def get_name(self) -> str:
return self.role_name
def get_prompt(self) -> AgentPrompt:
def get_prompt(self) -> LLMPrompt:
return self.prompt
class AIRoleGroup:
+15 -15
View File
@@ -9,11 +9,11 @@ from abc import ABC, abstractmethod
from ..proto.compute_task import *
from ..proto.agent_msg import *
from ..proto.ai_function 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
@@ -48,7 +48,7 @@ class Workflow:
def __init__(self) -> None:
self.workflow_name : str = None
self.workflow_id : str = None
self.rule_prompt : AgentPrompt = None
self.rule_prompt : LLMPrompt = None
self.workflow_config = None
self.role_group : dict = None
self.input_filter : MessageFilter= None
@@ -83,7 +83,7 @@ class Workflow:
self.db_file = self.owner_workflow.db_file
if config.get("prompt") is not None:
self.rule_prompt = AgentPrompt()
self.rule_prompt = LLMPrompt()
if self.rule_prompt.load_from_config(config.get("prompt")) is False:
logger.error("Workflow load prompt failed")
return False
@@ -279,7 +279,7 @@ class Workflow:
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):
async def role_call(self,func_item:ActionItem,the_role:AIRole):
logger.info(f"{the_role.role_id} call {func_item.name} ")
arguments = func_item.args
@@ -290,11 +290,11 @@ class Workflow:
result_str:str = await func_node.execute(**arguments)
return result_str
async def role_post_call(self,func_item:FunctionItem,the_role:AIRole):
async def role_post_call(self,func_item:ActionItem,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):
def _format_msg_by_env_value(self,prompt:LLMPrompt):
if self.workflow_env is None:
return
@@ -326,7 +326,7 @@ class Workflow:
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]:
async def _role_execute_func(self,the_role:AIRole,inenr_func_call_node:dict,prompt:LLMPrompt,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"))
@@ -372,7 +372,7 @@ class Workflow:
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 = LLMPrompt()
prompt.append(the_role.agent.agent_prompt)
prompt.append(self.get_workflow_rule_prompt())
prompt.append(the_role.get_prompt())
@@ -382,7 +382,7 @@ class Workflow:
#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 = LLMPrompt()
msg_prompt.messages = [{"role":"user","content":f"user name is {msg.sender}, his question is :{msg.body}"}]
prompt.append(msg_prompt)
@@ -461,20 +461,20 @@ class Workflow:
# message will be saved in role.process_message
pass
this_llm_resp_prompt = AgentPrompt()
this_llm_resp_prompt = LLMPrompt()
this_llm_resp_prompt.messages = [{"role":"assistant","content":result_str}]
prompt.append(this_llm_resp_prompt)
result_prompt = AgentPrompt()
result_prompt = LLMPrompt()
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:
async def _get_prompt_from_session(self,the_role:AIRole,chatsession:AIChatSession) -> LLMPrompt:
messages = chatsession.read_history(the_role.history_len) # read last 10 message
result_prompt = AgentPrompt()
result_prompt = LLMPrompt()
for msg in reversed(messages):
if msg.sender == the_role.role_id:
@@ -484,10 +484,10 @@ class Workflow:
return result_prompt
def _get_knowlege_prompt(self,role_name:str) -> AgentPrompt:
def _get_knowlege_prompt(self,role_name:str) -> LLMPrompt:
pass
def get_workflow_rule_prompt(self) -> AgentPrompt:
def get_workflow_rule_prompt(self) -> LLMPrompt:
return self.rule_prompt
# def _env_event_to_msg(self,env_event:EnvironmentEvent) -> AgentMsg: