Support custom agent

This commit is contained in:
wugren
2023-11-15 18:43:27 +08:00
parent 87fdba9714
commit bddeebdad0
3 changed files with 232 additions and 177 deletions
+112 -111
View File
@@ -12,7 +12,8 @@ import datetime
import copy
import sys
from .agent_base import AgentMsg, AgentMsgStatus, AgentMsgType,FunctionItem,LLMResult,AgentPrompt,AgentReport,AgentTodo,AgentTodoResult,AgentWorkLog
from .agent_base import AgentMsg, AgentMsgStatus, AgentMsgType, FunctionItem, LLMResult, AgentPrompt, AgentReport, \
AgentTodo, AgentTodoResult, AgentWorkLog, BaseAIAgent
from .chatsession import AIChatSession
from .compute_task import ComputeTaskResult,ComputeTaskResultCode
from .ai_function import AIFunction
@@ -116,7 +117,7 @@ class AIAgentTemplete:
return True
class AIAgent:
class AIAgent(BaseAIAgent):
def __init__(self) -> None:
self.role_prompt:AgentPrompt = None
self.agent_prompt:AgentPrompt = None
@@ -128,7 +129,7 @@ class AIAgent:
self.last_recover_time = time.time()
self.enable_thread = False
self.can_do_unassigned_task = True
self.agent_id:str = None
self.template_id:str = None
@@ -137,7 +138,7 @@ class AIAgent:
self.enable = True
self.enable_kb = False
self.enable_timestamp = False
self.guest_prompt_str = None
self.guest_prompt_str = None
self.owner_promp_str = None
self.contact_prompt_str = None
self.history_len = 10
@@ -159,7 +160,7 @@ class AIAgent:
self.owner_env : Environment = None
self.owenr_bus = None
self.enable_function_list = None
@classmethod
def create_from_templete(cls,templete:AIAgentTemplete, fullname:str):
@@ -192,7 +193,7 @@ class AIAgent:
if config.get("prompt") is not None:
self.agent_prompt = AgentPrompt()
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.load_from_config(config["think_prompt"])
@@ -207,13 +208,13 @@ class AIAgent:
if config.get("owner_prompt") is not None:
self.owner_promp_str = config["owner_prompt"]
if config.get("contact_prompt") is not None:
self.contact_prompt_str = config["contact_prompt"]
if config.get("owner_env") is not None:
self.owner_env = config.get("owner_env")
if config.get("powerby") is not None:
self.powerby = config["powerby"]
@@ -232,7 +233,7 @@ class AIAgent:
if config.get("history_len"):
self.history_len = int(config.get("history_len"))
return True
def get_id(self) -> str:
return self.agent_id
@@ -245,22 +246,22 @@ class AIAgent:
def get_llm_model_name(self) -> str:
if self.llm_model_name is None:
return AIStorage.get_instance().get_user_config().get_value("llm_model_name")
return self.llm_model_name
def get_max_token_size(self) -> int:
return self.max_token_size
def get_llm_learn_token_limit(self) -> int:
return self.learn_token_limit
def get_learn_prompt(self) -> AgentPrompt:
return self.learn_prompt
def get_agent_role_prompt(self) -> AgentPrompt:
return self.role_prompt
def _get_remote_user_prompt(self,remote_user:str) -> AgentPrompt:
cm = ContactManager.get_instance()
contact = cm.find_contact_by_name(remote_user)
@@ -284,7 +285,7 @@ class AIAgent:
prompt = AgentPrompt()
prompt.system_message = {"role":"system","content":real_str}
return prompt
return None
def _get_inner_functions(self) -> dict:
@@ -334,13 +335,13 @@ class AIAgent:
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,self.llm_model_name,self.max_token_size,inner_functions)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"_execute_func llm compute error:{task_result.error_str}")
return task_result
ineternal_call_record.result_str = task_result.result_str
ineternal_call_record.done_time = time.time()
if org_msg:
@@ -356,10 +357,10 @@ class AIAgent:
return await self._execute_func(inner_func_call_node,prompt,org_msg,stack_limit-1)
else:
return task_result
def get_agent_prompt(self) -> AgentPrompt:
return self.agent_prompt
async def _get_agent_think_prompt(self) -> AgentPrompt:
return self.agent_think_prompt
@@ -374,11 +375,11 @@ class AIAgent:
async def _handle_event(self,event):
if event.type == "AgentThink":
return await self.do_self_think()
# async def _process_group_chat_msg(self,msg:AgentMsg) -> AgentMsg:
# async def _process_group_chat_msg(self,msg:AgentMsg) -> AgentMsg:
# session_topic = msg.target + "#" + msg.topic
# chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.chat_db)
# workspace = self.get_current_workspace()
@@ -410,7 +411,7 @@ class AIAgent:
# self._format_msg_by_env_value(prompt)
# inner_functions,function_token_len = self._get_inner_functions()
# system_prompt_len = prompt.get_prompt_token_len()
# input_len = len(msg.body)
@@ -423,7 +424,7 @@ class AIAgent:
# if task_result.result_code != ComputeTaskResultCode.OK:
# error_resp = msg.create_error_resp(task_result.error_str)
# return error_resp
# final_result = task_result.result_str
# llm_result : LLMResult = LLMResult.from_str(final_result)
# is_ignore = False
@@ -456,12 +457,12 @@ class AIAgent:
# return None
def get_workspace_by_msg(self,msg:AgentMsg) -> WorkspaceEnvironment:
return self.agent_workspace
def need_session_summmary(self,msg:AgentMsg,session:AIChatSession) -> bool:
return False
async def _create_openai_thread(self) -> str:
return None
return None
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
msg_prompt = AgentPrompt()
@@ -475,7 +476,7 @@ class AIAgent:
if self.agent_id in msg.mentions:
need_process = True
logger.info(f"agent {self.agent_id} recv a group chat message from {msg.sender},but is not mentioned,ignore!")
if need_process is not True:
chatsession.append(msg)
resp_msg = msg.create_group_resp_msg(self.agent_id,"")
@@ -491,7 +492,7 @@ class AIAgent:
need_create_thread = True
else:
need_create_thread = True
if need_create_thread:
openai_thread_id = await self._create_openai_thread()
if openai_thread_id is not None:
@@ -508,12 +509,12 @@ class AIAgent:
prompt.append(self.get_agent_prompt())
prompt.append(self._get_remote_user_prompt(msg.sender))
self._format_msg_by_env_value(prompt)
if self.need_session_summmary(msg,chatsession):
# get relate session(todos) summary
summary = self.llm_select_session_summary(msg,chatsession)
prompt.append(AgentPrompt(summary))
known_info_str = "# Known information\n"
have_known_info = False
todos_str,todo_count = await workspace.get_todo_tree()
@@ -526,25 +527,25 @@ class AIAgent:
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)
else:
history_str,history_token_len = await self.get_prompt_from_session(chatsession,system_prompt_len + function_token_len,input_len)
history_str,history_token_len = await self.get_prompt_from_session(chatsession,system_prompt_len + function_token_len,input_len)
if history_str:
have_known_info = True
known_info_str += history_str
if have_known_info:
known_info_prompt = AgentPrompt(known_info_str)
prompt.append(known_info_prompt) # chat context
prompt.append(msg_prompt)
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} ")
#task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
task_result = await self._do_llm_complection(prompt,inner_functions,msg)
if task_result.result_code != ComputeTaskResultCode.OK:
error_resp = msg.create_error_resp(task_result.error_str)
return error_resp
final_result = task_result.result_str
if final_result is not None:
llm_result : LLMResult = LLMResult.from_str(final_result)
@@ -594,11 +595,11 @@ class AIAgent:
return None
async def _get_history_prompt_for_think(self,chatsession:AIChatSession,summary:str,system_token_len:int,pos:int)->(AgentPrompt,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()
@@ -606,7 +607,7 @@ class AIAgent:
if summary is not None:
if len(summary) > 1:
have_summary = True
if have_summary:
result_prompt.messages.append({"role":"user","content":summary})
result_token_len -= len(summary)
@@ -628,10 +629,10 @@ class AIAgent:
if history_len < 0:
logger.warning(f"_get_prompt_from_session reach limit of token,just read {read_history_msg} history message.")
break
result_prompt.messages.append({"role":"user","content":history_str})
return result_prompt,pos+read_history_msg
async def _get_prompt_from_session_for_groupchat(self,chatsession:AIChatSession,system_token_len,input_token_len,is_groupchat=False):
history_len = (self.max_token_size * 0.7) - system_token_len - input_token_len
messages = chatsession.read_history(self.history_len) # read
@@ -648,7 +649,7 @@ class AIAgent:
result_prompt.messages.append({"role":"assistant","content":f"(create on {formatted_time}) {msg.body} "})
else:
result_prompt.messages.append({"role":"assistant","content":msg.body})
else:
if self.enable_timestamp:
result_prompt.messages.append({"role":"user","content":f"(create on {formatted_time}) {msg.body} "})
@@ -666,11 +667,11 @@ class AIAgent:
async def _llm_summary_work(self,workspace:WorkspaceEnvironment):
# read report ,and update work summary of
# read report ,and update work summary of
# build todo list from work summary and goals
#
#
report_list = self.get_unread_reports()
for report in report_list:
if self.agent_energy <= 0:
break
@@ -691,7 +692,7 @@ class AIAgent:
async def _llm_review_unassigned_todos(self,workspace:WorkspaceEnvironment):
pass
async def _llm_read_report(self,report:AgentReport,worksapce:WorkspaceEnvironment):
work_summary = worksapce.get_work_summary(self.agent_id)
prompt : AgentPrompt = AgentPrompt()
@@ -710,7 +711,7 @@ class AIAgent:
worksapce.set_work_summary(self.agent_id,task_result.result_str)
# 尝试完成自己的TOOD (不依赖任何其他Agnet)
async def do_my_work(self) -> None:
workspace : WorkspaceEnvironment = self.get_workspace_by_msg(None)
@@ -723,7 +724,7 @@ class AIAgent:
todo_list = await workspace.get_todo_list(self.agent_id)
check_count = 0
do_count = 0
for todo in todo_list:
if self.agent_energy <= 0:
break
@@ -740,7 +741,7 @@ class AIAgent:
case AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR:
continue
case AgentTodoResult.TODO_RESULT_CODE_OK:
await workspace.update_todo(todo.todo_id,AgentTodo.TODO_STATE_DONE)
await workspace.update_todo(todo.todo_id,AgentTodo.TODO_STATE_DONE)
case AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR:
await workspace.update_todo(todo.todo_id,AgentTodo.TDDO_STATE_CHECKFAILED)
@@ -748,7 +749,7 @@ class AIAgent:
self.agent_energy -= 1
check_count += 1
elif await self.can_do(todo,workspace):
do_result : AgentTodoResult = await self._llm_do(todo,workspace)
do_result : AgentTodoResult = await self._llm_do(todo,workspace)
todo.last_do_time = datetime.datetime.now().timestamp()
todo.retry_count += 1
@@ -756,14 +757,14 @@ class AIAgent:
case AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR:
continue
case AgentTodoResult.TODO_RESULT_CODE_OK:
await workspace.update_todo(todo.todo_id,AgentTodo.TODO_STATE_WAITING_CHECK)
await workspace.update_todo(todo.todo_id,AgentTodo.TODO_STATE_WAITING_CHECK)
case AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR:
await workspace.update_todo(todo.todo_id,AgentTodo.TODO_STATE_EXEC_FAILED)
await workspace.append_worklog(todo,do_result)
await workspace.append_worklog(todo,do_result)
self.agent_energy -= 2
do_count += 1
logger.info(f"agent {self.agent_id} ,check:{check_count} todo,do:{do_count} todo.")
def get_review_todo_prompt(self,todo:AgentTodo) -> AgentPrompt:
@@ -784,50 +785,50 @@ class AIAgent:
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"_llm_review_todos compute error:{task_result.error_str}")
return
return
return
def get_do_prompt(self,todo:AgentTodo) -> AgentPrompt:
return self.do_prompt
def get_prompt_from_todo(self,todo:AgentTodo) -> AgentPrompt:
json_str = json.dumps(todo.raw_obj)
return AgentPrompt(json_str)
async def need_review_todo(self,todo:AgentTodo,workspace:WorkspaceEnvironment) -> bool:
return False
async def can_check(self,todo:AgentTodo,workspace:WorkspaceEnvironment) -> bool:
if self.get_check_prompt(todo) is None:
return False
if todo.can_check() is False:
return False
if todo.checker is not None:
if todo.checker != self.agent_id:
return False
else:
if self.can_do_unassigned_task is False:
return False
else:
else:
todo.checker = self.agent_id
return True
async def can_do(self,todo:AgentTodo,workspace:WorkspaceEnvironment) -> bool:
if todo.can_do() is False:
return False
if todo.worker is not None:
if todo.worker != self.agent_id:
return False
else:
if self.can_do_unassigned_task is False:
return False
else:
todo.worker = self.agent_id
else:
todo.worker = self.agent_id
return True
async def _llm_do(self,todo:AgentTodo,workspace:WorkspaceEnvironment) -> AgentTodoResult:
@@ -835,7 +836,7 @@ class AIAgent:
prompt : AgentPrompt = AgentPrompt()
#prompt.append(self.agent_prompt)
prompt.append(workspace.get_role_prompt(self.agent_id))
do_prompt = workspace.get_do_prompt(todo)
if do_prompt is None:
do_prompt = self.get_do_prompt(todo)
@@ -854,7 +855,7 @@ class AIAgent:
result.result_code = AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR
result.error_str = task_result.error_str
return result
llm_result = LLMResult.from_str(task_result.result_str)
# result_str is the explain of how to do this todo
result.result_str = llm_result.resp
@@ -874,19 +875,19 @@ class AIAgent:
result.result_code = AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR
#result.error_str = error_str
return result
return result
async def append_toddo_result(self,todo,worksapce,llm_result,result_str):
pass
def get_check_prompt(self,todo:AgentTodo) -> AgentPrompt:
return self.check_prompt
async def _llm_check_todo(self, todo:AgentTodo,workspace:WorkspaceEnvironment) :
if self.get_check_prompt(todo) is None:
return None
prompt : AgentPrompt = AgentPrompt()
prompt.append(self.agent_prompt)
prompt.append(workspace.get_role_prompt(self.agent_id))
@@ -907,11 +908,11 @@ class AIAgent:
return True
todo.last_check_result = task_result.result_str
return False
# 尝试自我学习,会主动获取、读取资料并进行整理
# LLM的本质能力是处理海量知识,应该让LLM能基于知识把自己的工作处理的更好
async def do_self_learn(self) -> None:
# 不同的workspace是否应该有不同的学习方法?
# 不同的workspace是否应该有不同的学习方法?
workspace = self.get_workspace_by_msg(None)
hash_list = workspace.kb_db.get_knowledge_without_llm_title()
for hash in hash_list:
@@ -953,7 +954,7 @@ class AIAgent:
# self.llm_read_book(kb,item)
# learn_power -= 1
# case "article":
#
#
# self.llm_read_article(kb,item)
# learn_power -= 1
# case "video":
@@ -982,8 +983,8 @@ class AIAgent:
current_list = kb.get_list(current_path)
self_assessment_with_goal = self.get_self_assessment_with_goal()
learn_goal = {}
llm_blance_knowledge_base(current_path,current_list,self_assessment_with_goal,learn_goal,learn_power)
# 主动学习
@@ -992,7 +993,7 @@ class AIAgent:
self.llm_learn_with_search_engine(kb,goal,learn_power)
if learn_power <= 0:
break
def parser_learn_llm_result(self,llm_result:LLMResult):
pass
@@ -1011,13 +1012,13 @@ class AIAgent:
known_obj["summary"] = summary
tags = knowledge_item.get("tags")
if tags:
known_obj["tags"] = tags
known_obj["tags"] = tags
if need_catalogs:
catalogs = knowledge_item.get("catalogs")
if catalogs:
known_obj["catalogs"] = catalogs
if temp_meta:
if temp_meta:
for key in temp_meta.keys():
known_obj[key] = temp_meta[key]
@@ -1030,7 +1031,7 @@ class AIAgent:
# Objectives:
# Obtain better titles, abstracts, table of contents (if necessary), tags
# Determine the appropriate place to put it (in line with the organization's goals)
# Known information:
# Known information:
# The reason why the target service's learn_prompt is being sorted
# Summary of the organization's work (if any)
# The current structure of the knowledge base (note the size control) gen_kb_tree_prompt (when empty, LLM should generate an appropriate initial directory structure)
@@ -1040,20 +1041,20 @@ class AIAgent:
# Indicate that the input is part of the content, let LLM generate intermediate results for the task
# Enter the content in sequence, when the last content block is input, LLM gets the result
#full_content = item.get_article_full_content()
workspace = self.get_workspace_by_msg(None)
full_content = await workspace.load_knowledge_content(full_path)
if full_content is None:
return None
if len(full_content) < 16:
logger.warning(f"llm_read_article: article {knowledge_item['path']} is too short,just read summary!")
return None
return None
full_content_len = self.token_len(full_content)
if full_content_len < self.get_llm_learn_token_limit():
# 短文章不用总结catelog
#path_list,summary = llm_get_summary(summary,full_content)
#prompt = self.get_agent_role_prompt()
@@ -1069,7 +1070,7 @@ class AIAgent:
result_obj = {}
result_obj["error_str"] = task_result.error_str
return result_obj
result_obj = json.loads(task_result.result_str)
return result_obj
@@ -1102,14 +1103,14 @@ class AIAgent:
result_obj = {}
result_obj["error_str"] = task_result.error_str
return result_obj
result_obj = json.loads(task_result.result_str)
temp_meta_data = result_obj.get("metadata")
if is_final:
return result_obj
return None
async def do_self_think(self):
session_id_list = AIChatSession.list_session(self.agent_id,self.chat_db)
@@ -1126,8 +1127,8 @@ class AIAgent:
used_energy = await self.think_todo_log(todo_log)
self.agent_energy -= used_energy
return
return
async def think_todo_log(self,todo_log:AgentWorkLog):
pass
@@ -1160,24 +1161,24 @@ class AIAgent:
else:
new_summary= task_result.result_str
logger.info(f"agent {self.agent_id} think session {session_id} from {cur_pos} to {next_pos} summary:{new_summary}")
chatsession.update_think_progress(next_pos,new_summary)
return
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:
# TODO: get prompt from group chat is different from single chat
if self.enable_thread:
return None
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
read_history_msg = 0
have_known_info = False
known_info = ""
if chatsession.summary is not None:
if len(chatsession.summary) > 1:
if len(chatsession.summary) > 1:
known_info += f"## Recent conversation summary \n {chatsession.summary}\n"
result_token_len -= len(chatsession.summary)
have_known_info = True
@@ -1196,13 +1197,13 @@ class AIAgent:
if history_len < 0:
logger.warning(f"_get_prompt_from_session reach limit of token,just read {read_history_msg} history message.")
break
known_info += f"## Recent conversation history \n {histroy_str}\n"
if have_known_info:
return known_info,result_token_len
return None,0
async def _do_llm_complection(self,prompt:AgentPrompt,inner_functions:dict=None,org_msg:AgentMsg=None,is_json_resp = False) -> ComputeTaskResult:
from .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} ")
@@ -1223,28 +1224,28 @@ class AIAgent:
if inner_func_call_node:
call_prompt : AgentPrompt = copy.deepcopy(prompt)
task_result = await self._execute_func(inner_func_call_node,call_prompt,inner_functions,org_msg)
return task_result
def need_work(self) -> bool:
if self.do_prompt is not None:
return True
if self.check_prompt is not None:
return True
if self.agent_energy > 2:
return True
return False
def need_self_think(self) -> bool:
return False
def need_self_learn(self) -> bool:
if self.learn_prompt is not None:
return True
return False
return False
def wake_up(self) -> None:
if self.agent_task is None:
self.agent_task = asyncio.create_task(self._on_timer())
@@ -1288,7 +1289,7 @@ class AIAgent:
def token_len(self,text:str) -> int:
return ComputeKernel.llm_num_tokens_from_text(text,self.get_llm_model_name())
+85 -44
View File
@@ -1,13 +1,16 @@
import abc
import copy
from abc import abstractmethod
from datetime import datetime, timedelta
import logging
from enum import Enum
import uuid
import time
import time
import re
import shlex
import json
from typing import List
from .ai_function import FunctionItem
from .compute_task import ComputeTaskResult
@@ -47,9 +50,9 @@ class AgentMsg:
def __init__(self,msg_type=AgentMsgType.TYPE_MSG) -> None:
self.msg_id = "msg#" + uuid.uuid4().hex
self.msg_type:AgentMsgType = msg_type
self.prev_msg_id:str = None
self.quote_msg_id:str = None
self.quote_msg_id:str = None
self.rely_msg_id:str = None # if not none means this is a respone msg
self.session_id:str = None
@@ -68,7 +71,7 @@ class AgentMsg:
self.body_mime:str = None #//default is "text/plain",encode is utf8
#type is call / action
self.func_name = None
self.func_name = None
self.args = None
self.result_str = None
@@ -95,7 +98,7 @@ class AgentMsg:
msg.prev_msg_id = prev_msg_id
msg.sender = caller
return msg
def create_action_msg(self,action_name:str,args:dict,caller:str):
msg = AgentMsg(AgentMsgType.TYPE_ACTION)
msg.create_time = time.time()
@@ -105,11 +108,11 @@ class AgentMsg:
msg.topic = self.topic
msg.sender = caller
return msg
def create_error_resp(self,error_msg:str):
resp_msg = AgentMsg(AgentMsgType.TYPE_SYSTEM)
resp_msg.create_time = time.time()
resp_msg.rely_msg_id = self.msg_id
resp_msg.body = error_msg
resp_msg.topic = self.topic
@@ -129,7 +132,7 @@ class AgentMsg:
resp_msg.topic = self.topic
return resp_msg
def create_group_resp_msg(self,sender_id,resp_body):
resp_msg = AgentMsg(AgentMsgType.TYPE_GROUPMSG)
resp_msg.create_time = time.time()
@@ -158,13 +161,13 @@ class AgentMsg:
def get_target(self) -> str:
return self.target
def get_prev_msg_id(self) -> str:
return self.prev_msg_id
def get_quote_msg_id(self) -> str:
return self.quote_msg_id
@classmethod
def parse_function_call(cls,func_string:str):
str_list = shlex.split(func_string)
@@ -237,9 +240,9 @@ class LLMResult:
def __init__(self) -> None:
self.state : str = "ignore"
self.resp : str = ""
self.raw_resp = None
self.raw_resp = None
self.paragraphs : dict[str,FunctionItem] = []
self.post_msgs : List[AgentMsg] = []
self.send_msgs : List[AgentMsg] = []
@@ -281,14 +284,14 @@ class LLMResult:
@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)
@@ -300,7 +303,7 @@ class LLMResult:
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
@@ -313,7 +316,7 @@ class LLMResult:
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
@@ -333,9 +336,9 @@ class LLMResult:
if func_name in valid_func:
r.paragraphs[func_name] = func_item
return True
return False
current_func : FunctionItem = None
for line in lines:
@@ -361,11 +364,11 @@ class LLMResult:
else:
r.state = "reponsed"
return r
return r
class AgentReport:
def __init__(self):
pass
pass
class AgentTodoResult:
TODO_RESULT_CODE_OK = 0,
@@ -386,7 +389,7 @@ class AgentTodoResult:
result["error_str"] = self.error_str
result["op_list"] = self.op_list
return result
class AgentTodo:
TODO_STATE_WAIT_ASSIGN = "wait_assign"
@@ -400,7 +403,7 @@ class AgentTodo:
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
@@ -409,9 +412,9 @@ class AgentTodo:
#self.parent = None
self.create_time = time.time()
self.state = "wait_assign"
self.worker = None
self.checker = None
self.state = "wait_assign"
self.worker = None
self.checker = None
self.createor = None
self.need_check = True
@@ -433,7 +436,7 @@ class 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")
@@ -448,7 +451,7 @@ class AgentTodo:
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")
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")
@@ -492,21 +495,21 @@ class AgentTodo:
result["createor"] = self.createor
result["retry_count"] = self.retry_count
return result
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 False
return True
def can_do(self) -> bool:
match self.state:
case AgentTodo.TODO_STATE_DONE:
@@ -522,32 +525,70 @@ class AgentTodo:
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
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
return False
logger.info(f"todo {self.title} can do.")
return True
class AgentWorkLog:
def __init__(self) -> None:
pass
class BaseAIAgent:
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
@abstractmethod
def get_llm_learn_token_limit(self) -> int:
pass
@abstractmethod
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
pass
class CustomAIAgent(BaseAIAgent):
def __init__(self, agent_id: str, llm_model_name: str, max_token_size: int, llm_learn_token_limit: int) -> None:
self.agent_id = agent_id
self.llm_model_name = llm_model_name
self.max_token_size = max_token_size
self.llm_learn_token_limit = llm_learn_token_limit
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
def get_llm_learn_token_limit(self) -> int:
return self.llm_learn_token_limit
+35 -22
View File
@@ -1,11 +1,13 @@
import importlib
import logging
import toml
import os
import sys
import runpy
from typing import Any, Callable, Dict, List, Optional, Union
from aios_kernel import AIAgent,AIAgentTemplete,AIStorage,Environment
from aios_kernel.agent_base import BaseAIAgent
from package_manager import PackageEnv,PackageEnvManager,PackageMediaInfo,PackageInstallTask
logger = logging.getLogger(__name__)
@@ -17,19 +19,19 @@ cache = "./.agents"
class AgentManager:
_instance = None
@classmethod
def get_instance(cls)->'AgentManager':
if cls._instance is None:
cls._instance = AgentManager()
return cls._instance
def __init__(self) -> None:
self.agent_templete_env : PackageEnv = None
self.agent_env : PackageEnv = None
self.db_path : str = None
self.loaded_agent_instance : Dict[str,AIAgent] = None
self.db_path : str = None
self.loaded_agent_instance : Dict[str,BaseAIAgent] = None
async def initial(self) -> None:
system_app_dir = AIStorage.get_instance().get_system_app_dir()
user_data_dir = AIStorage.get_instance().get_myai_dir()
@@ -43,13 +45,13 @@ class AgentManager:
self.db_path = f"{user_data_dir}/messages.db"
self.loaded_agent_instance = {}
return True
async def scan_all_agent(self)->None:
pass
async def is_exist(self,agent_id:str) -> bool:
the_aget = await self.get(agent_id)
if the_aget:
@@ -60,17 +62,17 @@ class AgentManager:
the_agent = self.loaded_agent_instance.get(agent_id)
if the_agent:
return the_agent
# try load from disk
agent_media_info = self.agent_env.load(agent_id)
if agent_media_info is None:
return None
the_agent : AIAgent = await self._load_agent_from_media(agent_media_info)
if the_agent is None:
logger.warn(f"load agent {agent_id} from media failed!")
return None
the_agent.chat_db = self.db_path
return the_agent
@@ -86,19 +88,19 @@ class AgentManager:
def install(self,templete_id) -> PackageInstallTask:
installer = self.agent_templete_env.get_installer()
return installer.install(templete_id)
def uninstall(self,templete_id) -> int:
pass
pass
async def _load_templete_from_media(self,templete_media:PackageMediaInfo) -> AIAgentTemplete:
pass
async def _load_agent_from_media(self,agent_media:PackageMediaInfo) -> AIAgent:
async def _load_agent_from_media(self,agent_media:PackageMediaInfo) -> BaseAIAgent:
reader = self.agent_env._create_media_loader(agent_media)
if reader is None:
logger.error(f"create media loader for {agent_media} failed!")
return None
try:
config_file = await reader.read("agent.toml","r")
if config_file is None:
@@ -125,11 +127,22 @@ class AgentManager:
return None
return result_agent
except Exception as e:
logger.error(f"read agent.toml cfg from {agent_media} failed! unexpected error occurred: {str(e)}")
return None
custom_agent = os.path.join(agent_media.full_path,"agent.py")
if not os.path.exists(custom_agent):
logger.error(f"read agent.toml cfg from {agent_media} failed! unexpected error occurred: {str(e)}")
return None
agent_name = os.path.split(agent_media.full_path)[1]
spec = importlib.util.spec_from_file_location(agent_name, custom_agent)
the_api = importlib.util.module_from_spec(spec)
spec.loader.exec_module(the_api)
if not hasattr(the_api,"Agent"):
logger.error(f"read agent.toml cfg from {agent_media} failed! unexpected error occurred: {str(e)}")
return None
return the_api.Agent()
def create(self,template,agent_name,agent_last_name,agent_introduce) -> AIAgent:
pass