Files
opendan/src/aios/agent/agent.py
T
Liu Zhicong b6395c2195 Fix bugs.
2023-12-31 00:20:48 -08:00

567 lines
21 KiB
Python

import traceback
from typing import Optional
from asyncio import Queue
import asyncio
import logging
import uuid
import time
import json
import shlex
import datetime
import copy
import sys
from ..proto.agent_msg import AgentMsg
from ..proto.ai_function import *
from ..proto.agent_task import AgentTaskState,AgentTask,AgentTodo,AgentTodoResult
from ..proto.compute_task import *
from .agent_base import *
from .llm_process import *
from .chatsession import *
from ..environment.workspace_env import WorkspaceEnvironment, TodoListType
from ..frame.contact_manager import ContactManager,Contact,FamilyMember
from ..frame.compute_kernel import ComputeKernel
from ..frame.bus import AIBus
from ..environment.environment import *
from ..environment.workspace_env import WorkspaceEnvironment
from ..storage.storage import AIStorage
from ..knowledge import *
from ..utils import video_utils, image_utils
from ..proto.compute_task import ComputeTaskResult,ComputeTaskResultCode,LLMPrompt,LLMResult
logger = logging.getLogger(__name__)
class AIAgentTemplete:
def __init__(self) -> None:
self.llm_model_name:str = "gpt-4-0613"
self.max_token_size:int = 0
self.template_id:str = None
self.introduce:str = None
self.author:str = None
self.prompt:LLMPrompt = None
def load_from_config(self,config:dict) -> bool:
if config.get("llm_model_name") is not None:
self.llm_model_name = config["llm_model_name"]
if config.get("max_token_size") is not None:
self.max_token_size = config["max_token_size"]
if config.get("template_id") is not None:
self.template_id = config["template_id"]
if config.get("prompt") is not None:
self.prompt = LLMPrompt()
if self.prompt.load_from_config(config["prompt"]) is False:
logger.error("load prompt from config failed!")
return False
class AIAgent(BaseAIAgent):
def __init__(self) -> 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
self.agent_task = None
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
self.fullname:str = None
self.powerby = None
self.enable = True
self.enable_kb = False
self.enable_timestamp = False
self.guest_prompt_str = None
self.owner_promp_str = None
self.contact_prompt_str = None
self.history_len = 10
self.read_report_prompt = None
todo_prompts = {}
todo_prompts[TodoListType.TO_WORK] = {
"do": None,
"check": None,
"review": None,
}
todo_prompts[TodoListType.TO_LEARN] = {
"do": None,
"check": None,
"review": None,
}
self.todo_prompts = todo_prompts
self.chat_db = None
self.unread_msg = Queue() # msg from other agent
self.owenr_bus = None
self.memory : AgentMemory = None
self.prviate_workspace : AgentWorkspace = None
self.behaviors:Dict[str,BaseLLMProcess] = {}
async def initial(self,params:Dict = None):
self.memory = AgentMemory(self.agent_id,self.chat_db)
self.prviate_workspace = AgentWorkspace(self.agent_id)
init_params = {}
init_params["memory"] = self.memory
init_params["workspace"] = self.prviate_workspace
for process_name in self.behaviors.keys():
init_result = await self.behaviors[process_name].initial(init_params)
if init_result is False:
logger.error(f"llm process {process_name} initial failed! initial return False")
return False
self.wake_up()
return True
async def load_from_config(self,config:dict) -> bool:
if config.get("instance_id") is None:
logger.error("agent instance_id is None!")
return False
self.agent_id = config["instance_id"]
if config.get("fullname") is None:
logger.error(f"agent {self.agent_id} fullname is None!")
return False
self.fullname = config["fullname"]
if config.get("enable_thread") is not None:
self.enable_thread = bool(config["enable_thread"])
if config.get("powerby") is not None:
self.powerby = config["powerby"]
if config.get("template_id") is not None:
self.template_id = config["template_id"]
if config.get("llm_model_name") is not None:
self.llm_model_name = config["llm_model_name"]
if config.get("max_token_size") is not None:
self.max_token_size = config["max_token_size"]
if config.get("enable_function") is not None:
self.enable_function_list = config["enable_function"]
if config.get("enable_kb") is not None:
self.enable_kb = bool(config["enable_kb"])
if config.get("enable_timestamp") is not None:
self.enable_timestamp = bool(config["enable_timestamp"])
if config.get("history_len"):
self.history_len = int(config.get("history_len"))
#load all LLMProcess
self.behaviors = {}
behaviors = config.get("behavior")
for process_config_name in behaviors.keys():
process_config = behaviors[process_config_name]
real_config = {}
real_config.update(config)
real_config.update(process_config)
load_result = await LLMProcessLoader.get_instance().load_from_config(real_config)
if load_result:
self.behaviors[process_config_name] = load_result
else:
logger.error(f"load LLMProcess {process_config_name} failed!")
return False
return True
def get_id(self) -> str:
return self.agent_id
def get_fullname(self) -> str:
return self.fullname
def get_template_id(self) -> str:
return self.template_id
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_agent_role_prompt(self) -> LLMPrompt:
return self.role_prompt
def get_agent_prompt(self) -> LLMPrompt:
return self.agent_prompt
async def llm_process_msg(self,msg:AgentMsg) -> AgentMsg:
need_process:bool = True
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
need_process = False
session_topic = msg.target + "#" + msg.topic
chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.chat_db)
if msg.mentions is not None:
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,"")
return resp_msg
input_parms = {
"msg":msg
}
msg_process = self.behaviors.get("on_message")
llm_result : LLMResult = await msg_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
error_resp = msg.create_error_resp(llm_result.error_str)
return error_resp
elif llm_result.state == LLMResultStates.IGNORE:
return None
else: # OK
resp_msg = llm_result.raw_result.get("_resp_msg")
return resp_msg
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
msg.context_info = {}
msg.context_info["location"] = "SanJose"
msg.context_info["now"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg.context_info["weather"] = "Partly Cloudy, 60°F"
return await self.llm_process_msg(msg)
async def llm_review_tasklist(self):
llm_process : BaseLLMProcess = self.behaviors.get("review_task")
if llm_process:
if self.prviate_workspace:
tasklist = await self.prviate_workspace.task_mgr.list_task()
if tasklist:
for agent_task in tasklist:
if self.agent_energy <= 0:
break
if agent_task.state == AgentTaskState.TASK_STATE_WAIT:
input_parms = {
"task":agent_task
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process review_task error:{llm_result.error_str}")
continue
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process review_task ignore!")
continue
else:
determine = llm_result.raw_result.get("determine")
logger.info(f"llm process review_task ok!,think is:{determine}")
self.agent_energy -= 1
async def _llm_run_todo_list(self, todo_list_type: TodoListType):
workspace : WorkspaceEnvironment = self.get_workspace_by_msg(None)
logger.info(f"agent {self.agent_id} do my work start!")
# review todolist
#if await self.need_review_todolist():
# await self._llm_review_todolist(workspace)
todo_list = workspace.todo_list[todo_list_type]
need_todo = await todo_list.get_todo_list(self.agent_id)
check_count = 0
do_count = 0
review_count = 0
for todo in need_todo:
if self.agent_energy <= 0:
break
do_prompts = self._can_do_todo(todo_list_type, todo)
if do_prompts:
prompt : LLMPrompt = LLMPrompt()
prompt.append(self.agent_prompt)
prompt.append(workspace.get_role_prompt(self.agent_id))
prompt.append(do_prompts)
prompt.append(todo.to_prompt())
do_result : AgentTodoResult = await self._llm_do_todo(todo, prompt, workspace)
todo.last_do_time = datetime.datetime.now().timestamp()
todo.retry_count += 1
match do_result.result_code:
case AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR:
continue
case AgentTodoResult.TODO_RESULT_CODE_OK:
todo.result = do_result
await todo_list.update_todo(todo.todo_id,AgentTodo.TODO_STATE_WAITING_CHECK)
case AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR:
await todo_list.update_todo(todo.todo_id,AgentTodo.TODO_STATE_EXEC_FAILED)
await todo_list.append_worklog(todo,do_result)
self.agent_energy -= 2
do_count += 1
# review_result = await self._llm_review_todo(todo,workspace)
# todo.last_review_time = datetime.datetime.now().timestamp()
continue
check_prompts = self._can_check_todo(todo_list_type, todo)
if check_prompts:
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(LLMPrompt(todo.last_check_result))
prompt.append(todo.detail)
prompt.append(todo.result)
check_result: AgentTodoResult = await self._llm_check_todo(todo, prompt, workspace)
todo.last_check_time = datetime.datetime.now().timestamp()
match check_result.result_code:
case AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR:
continue
case AgentTodoResult.TODO_RESULT_CODE_OK:
await todo_list.update_todo(todo.todo_id,AgentTodo.TODO_STATE_DONE)
case AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR:
await todo_list.update_todo(todo.todo_id,AgentTodo.TDDO_STATE_CHECKFAILED)
await todo_list.append_worklog(todo, check_result)
self.agent_energy -= 1
check_count += 1
continue
review_prompts = self._can_review_todo(todo_list_type, todo)
if review_prompts:
prompt.append(workspace.get_prompt())
prompt.append(workspace.get_role_prompt(self.agent_id))
prompt.append(review_prompts)
todo_tree = todo_list.get_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()
match do_result.result_code:
case AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR:
continue
case AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR:
continue
case AgentTodoResult.TODO_RESULT_CODE_OK:
await todo_list.update_todo(todo.todo_id,AgentTodo.TODO_STATE_REVIEWED)
await todo_list.append_worklog(todo,do_result)
self.agent_energy -= 1
review_count += 1
continue
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) -> LLMPrompt:
do_prompts = self.todo_prompts[todo_list_type].get("review")
if not do_prompts:
return None
if todo.can_review() is False:
return None
return do_prompts
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
if todo.can_check() is False:
return None
if todo.checker is not None:
if todo.checker != self.agent_id:
return None
else:
if self.can_do_unassigned_task is False:
return None
else:
todo.checker = self.agent_id
return do_prompts
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
if todo.can_do() is False:
return None
if todo.worker is not None:
if todo.worker != self.agent_id:
return None
else:
if self.can_do_unassigned_task is False:
return None
else:
todo.worker = self.agent_id
return do_prompts
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)
if task_result.error_str is not None:
logger.error(f"_llm_do compute error:{task_result.error_str}")
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
result.op_list = llm_result.op_list
if llm_result.post_msgs is not None:
for msg in llm_result.post_msgs:
msg.sender = self.agent_id
msg.topic = f"{todo.title}##{todo.todo_id}"
#msg.prev_msg_id = todo.todo_id
chatsession = AIChatSession.get_session(self.agent_id,f"{msg.target}#{msg.topic}",self.chat_db)
chatsession.append(msg)
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.action_list, self.agent_id)
if have_error:
result.result_code = AgentTodoResult.TODO_RESULT_CODE_EXEC_OP_ERROR
#result.error_str = error_str
return result
result.result_str = result_str
return result
async def _llm_check_todo(self, todo: AgentTodo, prompt: LLMPrompt, workspace: WorkspaceEnvironment) -> AgentTodoResult:
result = AgentTodoResult()
inner_functions,_ = BaseAIAgent.get_inner_functions(workspace)
task_result:ComputeTaskResult = await self.do_llm_complection(prompt,inner_functions=inner_functions,is_json_resp=True)
if task_result.error_str is not None:
logger.error(f"_llm_do compute error:{task_result.error_str}")
result.result_code = AgentTodoResult.TODO_RESULT_CODE_LLM_ERROR
result.error_str = task_result.error_str
return result
result.result_str = task_result.result_str
todo.last_check_result = task_result.result_str
return result
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)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"_llm_review_todos compute error:{task_result.error_str}")
return
return
# async def do_blance_knowledge_base(selft):
# # 整理自己的知识库(让分类更平衡,更由于自己以后的工作),并尝试更新学习目标
# current_path = "/"
# 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)
# # 主动学习
# # 方法目前只有使用搜索引擎一种?
# for goal in learn_goal.items():
# self.llm_learn_with_search_engine(kb,goal,learn_power)
# if learn_power <= 0:
# break
async def do_self_think(self):
session_id_list = AIChatSession.list_session(self.agent_id,self.chat_db)
for session_id in session_id_list:
if self.agent_energy <= 0:
break
used_energy = await self.think_chatsession(session_id)
self.agent_energy -= used_energy
# todo_logs = await self.get_todo_logs()
# for todo_log in todo_logs:
# if self.agent_energy <= 0:
# break
# used_energy = await self.think_todo_log(todo_log)
# self.agent_energy -= used_energy
return
#async def think_todo_log(self,todo_log:AgentWorkLog):
# pass
def need_self_think(self) -> bool:
return False
def wake_up(self) -> None:
if self.agent_task is None:
self.agent_task = asyncio.create_task(self._on_timer())
else:
logger.warning(f"agent {self.agent_id} is already wake up!")
# agent loop
async def _on_timer(self):
while True:
await asyncio.sleep(15)
try:
now = time.time()
if self.last_recover_time is None:
self.last_recover_time = now
else:
if now - self.last_recover_time > 60:
self.agent_energy += (now - self.last_recover_time) / 60
self.last_recover_time = now
if self.agent_energy <= 1:
continue
await self.llm_review_tasklist()
# complete & check todo
#await self._llm_run_todo_list(TodoListType.TO_WORK)
##await self._llm_run_todo_list(TodoListType.TO_LEARN)
if self.need_self_think():
await self.do_self_think()
# review other's todo
# self.review_other_works()
except Exception as e:
tb_str = traceback.format_exc()
logger.error(f"agent {self.agent_id} on timer error:{e},{tb_str}")
continue