2023-08-20 22:53:35 -07:00
|
|
|
from typing import Optional
|
2023-08-30 12:30:41 -07:00
|
|
|
|
2023-08-27 18:07:33 -07:00
|
|
|
from asyncio import Queue
|
|
|
|
|
import asyncio
|
2023-08-20 22:53:35 -07:00
|
|
|
import logging
|
2023-08-27 18:07:33 -07:00
|
|
|
import uuid
|
|
|
|
|
import time
|
2023-09-10 20:50:37 -07:00
|
|
|
import json
|
2023-09-19 18:25:25 -07:00
|
|
|
import shlex
|
2023-08-27 18:07:33 -07:00
|
|
|
|
2023-09-19 21:36:56 -07:00
|
|
|
from .agent_message import AgentMsg, AgentMsgStatus, AgentMsgType,FunctionItem,LLMResult
|
2023-08-30 12:30:41 -07:00
|
|
|
from .chatsession import AIChatSession
|
2023-09-10 20:50:37 -07:00
|
|
|
from .compute_task import ComputeTaskResult
|
|
|
|
|
from .ai_function import AIFunction
|
|
|
|
|
from .environment import Environment
|
2023-08-20 22:53:35 -07:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2023-08-23 11:19:16 -07:00
|
|
|
class AgentPrompt:
|
2023-08-20 22:53:35 -07:00
|
|
|
def __init__(self) -> None:
|
2023-08-27 18:07:33 -07:00
|
|
|
self.messages = []
|
2023-08-20 22:53:35 -07:00
|
|
|
|
2023-08-22 17:11:20 -07:00
|
|
|
def as_str(self)->str:
|
2023-08-27 18:07:33 -07:00
|
|
|
result_str = ""
|
|
|
|
|
if self.messages:
|
|
|
|
|
for msg in self.messages:
|
|
|
|
|
result_str += msg.get("role") + ":" + msg.get("content") + "\n"
|
2023-08-22 17:11:20 -07:00
|
|
|
|
2023-08-27 18:07:33 -07:00
|
|
|
return result_str
|
|
|
|
|
|
|
|
|
|
def append(self,prompt):
|
2023-08-30 12:30:41 -07:00
|
|
|
if prompt is None:
|
|
|
|
|
return
|
|
|
|
|
|
2023-08-27 18:07:33 -07:00
|
|
|
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 = config
|
|
|
|
|
return True
|
2023-08-20 22:53:35 -07:00
|
|
|
|
2023-08-22 17:11:20 -07:00
|
|
|
|
2023-08-23 11:19:16 -07:00
|
|
|
class AIAgentTemplete:
|
2023-08-22 17:11:20 -07:00
|
|
|
def __init__(self) -> None:
|
2023-08-27 18:07:33 -07:00
|
|
|
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:AgentPrompt = 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 = AgentPrompt()
|
|
|
|
|
if self.prompt.load_from_config(config["prompt"]) is False:
|
|
|
|
|
logger.error("load prompt from config failed!")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return True
|
2023-08-22 17:11:20 -07:00
|
|
|
|
2023-08-27 18:07:33 -07:00
|
|
|
|
2023-08-23 11:19:16 -07:00
|
|
|
class AIAgent:
|
2023-08-20 22:53:35 -07:00
|
|
|
def __init__(self) -> None:
|
2023-08-27 18:07:33 -07:00
|
|
|
self.prompt:AgentPrompt = None
|
|
|
|
|
self.llm_model_name:str = None
|
2023-08-30 12:30:41 -07:00
|
|
|
self.max_token_size:int = 3600
|
2023-09-14 01:50:18 -07:00
|
|
|
self.agent_id:str = None
|
2023-08-27 18:07:33 -07:00
|
|
|
self.template_id:str = None
|
|
|
|
|
self.fullname:str = None
|
|
|
|
|
self.powerby = None
|
|
|
|
|
self.enable = True
|
|
|
|
|
|
2023-08-30 12:30:41 -07:00
|
|
|
self.chat_db = None
|
2023-08-27 18:07:33 -07:00
|
|
|
self.unread_msg = Queue() # msg from other agent
|
2023-09-10 20:50:37 -07:00
|
|
|
self.owner_env : Environment = None
|
2023-09-16 11:41:59 -07:00
|
|
|
self.owenr_bus = None
|
2023-08-27 18:07:33 -07:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def create_from_templete(cls,templete:AIAgentTemplete, fullname:str):
|
|
|
|
|
# Agent just inherit from templete on craete,if template changed,agent will not change
|
|
|
|
|
result_agent = AIAgent()
|
|
|
|
|
result_agent.llm_model_name = templete.llm_model_name
|
|
|
|
|
result_agent.max_token_size = templete.max_token_size
|
|
|
|
|
result_agent.template_id = templete.template_id
|
2023-09-14 01:50:18 -07:00
|
|
|
result_agent.agent_id = "agent#" + uuid.uuid4().hex
|
2023-08-27 18:07:33 -07:00
|
|
|
result_agent.fullname = fullname
|
|
|
|
|
result_agent.powerby = templete.author
|
|
|
|
|
result_agent.prompt = templete.prompt
|
|
|
|
|
return result_agent
|
|
|
|
|
|
|
|
|
|
def load_from_config(self,config:dict) -> bool:
|
|
|
|
|
if config.get("instance_id") is None:
|
|
|
|
|
logger.error("agent instance_id is None!")
|
|
|
|
|
return False
|
2023-09-14 01:50:18 -07:00
|
|
|
self.agent_id = config["instance_id"]
|
2023-08-27 18:07:33 -07:00
|
|
|
|
|
|
|
|
if config.get("fullname") is None:
|
2023-09-14 01:50:18 -07:00
|
|
|
logger.error(f"agent {self.agent_id} fullname is None!")
|
2023-08-27 18:07:33 -07:00
|
|
|
return False
|
|
|
|
|
self.fullname = config["fullname"]
|
|
|
|
|
|
|
|
|
|
if config.get("prompt") is not None:
|
|
|
|
|
self.prompt = AgentPrompt()
|
|
|
|
|
self.prompt.load_from_config(config["prompt"])
|
|
|
|
|
|
|
|
|
|
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"]
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2023-09-19 21:36:56 -07:00
|
|
|
def _get_llm_result_type(self,llm_result_str:str) -> LLMResult:
|
|
|
|
|
r = LLMResult()
|
|
|
|
|
if llm_result_str is None:
|
|
|
|
|
r.state = "ignore"
|
|
|
|
|
return r
|
|
|
|
|
if llm_result_str == "ignore":
|
|
|
|
|
r.state = "ignore"
|
|
|
|
|
return r
|
2023-08-27 18:07:33 -07:00
|
|
|
|
2023-09-19 21:36:56 -07:00
|
|
|
lines = llm_result_str.splitlines()
|
|
|
|
|
is_need_wait = False
|
|
|
|
|
|
|
|
|
|
def check_args(func_item:FunctionItem):
|
|
|
|
|
match func_name:
|
|
|
|
|
case "send_msg":# sendmsg($target_id,$msg_content)
|
|
|
|
|
if len(func_args) != 1:
|
|
|
|
|
logger.error(f"parse sendmsg failed! {func_call}")
|
|
|
|
|
return False
|
|
|
|
|
new_msg = AgentMsg()
|
|
|
|
|
target_id = func_item.args[0]
|
|
|
|
|
msg_content = func_item.body
|
|
|
|
|
new_msg.set(self.agent_id,target_id,msg_content)
|
|
|
|
|
|
|
|
|
|
r.send_msgs.append(new_msg)
|
|
|
|
|
is_need_wait = True
|
|
|
|
|
|
|
|
|
|
case "post_msg":# postmsg($target_id,$msg_content)
|
|
|
|
|
if len(func_args) != 1:
|
|
|
|
|
logger.error(f"parse postmsg failed! {func_call}")
|
|
|
|
|
return False
|
|
|
|
|
new_msg = AgentMsg()
|
|
|
|
|
target_id = func_item.args[0]
|
|
|
|
|
msg_content = func_item.body
|
|
|
|
|
new_msg.set(self.agent_id,target_id,msg_content)
|
|
|
|
|
r.post_msgs.append(new_msg)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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
|
2023-09-10 20:50:37 -07:00
|
|
|
|
|
|
|
|
def _get_inner_functions(self) -> dict:
|
|
|
|
|
if self.owner_env is None:
|
|
|
|
|
return None
|
|
|
|
|
|
2023-09-19 21:36:56 -07:00
|
|
|
return None
|
|
|
|
|
|
2023-09-10 20:50:37 -07:00
|
|
|
all_inner_function = self.owner_env.get_all_ai_functions()
|
|
|
|
|
if all_inner_function is None:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
result_func = []
|
|
|
|
|
for inner_func in all_inner_function:
|
|
|
|
|
this_func = {}
|
|
|
|
|
this_func["name"] = inner_func.get_name()
|
|
|
|
|
this_func["description"] = inner_func.get_description()
|
|
|
|
|
this_func["parameters"] = inner_func.get_parameters()
|
|
|
|
|
result_func.append(this_func)
|
|
|
|
|
|
|
|
|
|
return result_func
|
|
|
|
|
|
2023-09-14 01:50:18 -07:00
|
|
|
async def _execute_func(self,inenr_func_call_node:dict,prompt:AgentPrompt,org_msg:AgentMsg) -> str:
|
2023-09-10 20:50:37 -07:00
|
|
|
from .compute_kernel import ComputeKernel
|
|
|
|
|
|
|
|
|
|
func_name = inenr_func_call_node.get("name")
|
|
|
|
|
arguments = json.loads(inenr_func_call_node.get("arguments"))
|
|
|
|
|
|
|
|
|
|
func_node : AIFunction = self.owner_env.get_ai_function(func_name)
|
|
|
|
|
if func_node is None:
|
|
|
|
|
return "execute failed,function not found"
|
|
|
|
|
|
2023-09-14 01:50:18 -07:00
|
|
|
ineternal_call_record = AgentMsg.create_internal_call_msg(func_name,arguments,org_msg.get_msg_id(),org_msg.target)
|
|
|
|
|
|
2023-09-10 20:50:37 -07:00
|
|
|
result_str:str = await func_node.execute(**arguments)
|
2023-09-14 01:50:18 -07:00
|
|
|
|
2023-09-10 20:50:37 -07:00
|
|
|
inner_functions = self._get_inner_functions()
|
2023-09-14 01:50:18 -07:00
|
|
|
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
|
2023-09-16 11:41:59 -07:00
|
|
|
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
|
2023-09-10 20:50:37 -07:00
|
|
|
|
2023-09-14 01:50:18 -07:00
|
|
|
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)
|
|
|
|
|
|
2023-09-10 20:50:37 -07:00
|
|
|
inner_func_call_node = task_result.result_message.get("function_call")
|
|
|
|
|
if inner_func_call_node:
|
2023-09-14 01:50:18 -07:00
|
|
|
return await self._execute_func(inner_func_call_node,prompt,org_msg)
|
2023-09-10 20:50:37 -07:00
|
|
|
else:
|
|
|
|
|
return task_result.result_str
|
2023-08-27 18:07:33 -07:00
|
|
|
|
|
|
|
|
async def _process_msg(self,msg:AgentMsg) -> AgentMsg:
|
|
|
|
|
from .compute_kernel import ComputeKernel
|
2023-09-19 21:36:56 -07:00
|
|
|
from .bus import AIBus
|
2023-09-10 20:50:37 -07:00
|
|
|
|
2023-08-30 12:30:41 -07:00
|
|
|
session_topic = msg.get_sender() + "#" + msg.topic
|
2023-09-14 01:50:18 -07:00
|
|
|
chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.chat_db)
|
|
|
|
|
if msg.mentions is not None:
|
|
|
|
|
if not self.agent_id in msg.mentions:
|
|
|
|
|
chatsession.append(msg)
|
|
|
|
|
logger.info(f"agent {self.agent_id} recv a group chat message from {msg.sender},but is not mentioned,ignore!")
|
|
|
|
|
return None
|
|
|
|
|
|
2023-08-27 18:07:33 -07:00
|
|
|
prompt = AgentPrompt()
|
|
|
|
|
prompt.append(self.prompt)
|
|
|
|
|
# prompt.append(self._get_knowlege_prompt(the_role.get_name()))
|
2023-08-30 12:30:41 -07:00
|
|
|
prompt.append(await self._get_prompt_from_session(chatsession)) # chat context
|
|
|
|
|
|
|
|
|
|
msg_prompt = AgentPrompt()
|
|
|
|
|
msg_prompt.messages = [{"role":"user","content":msg.body}]
|
|
|
|
|
prompt.append(msg_prompt)
|
2023-09-10 20:50:37 -07:00
|
|
|
|
|
|
|
|
inner_functions = self._get_inner_functions()
|
|
|
|
|
|
2023-09-16 11:41:59 -07:00
|
|
|
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
|
2023-09-10 20:50:37 -07:00
|
|
|
final_result = task_result.result_str
|
|
|
|
|
|
|
|
|
|
inner_func_call_node = task_result.result_message.get("function_call")
|
|
|
|
|
if inner_func_call_node:
|
2023-09-14 01:50:18 -07:00
|
|
|
#TODO to save more token ,can i use msg_prompt?
|
|
|
|
|
final_result = await self._execute_func(inner_func_call_node,prompt,msg)
|
2023-09-10 20:50:37 -07:00
|
|
|
|
2023-09-19 21:36:56 -07:00
|
|
|
llm_result : LLMResult = self._get_llm_result_type(final_result)
|
2023-08-27 18:07:33 -07:00
|
|
|
is_ignore = False
|
2023-09-19 21:36:56 -07:00
|
|
|
result_prompt_str = ""
|
|
|
|
|
match llm_result.state:
|
2023-08-27 18:07:33 -07:00
|
|
|
case "ignore":
|
|
|
|
|
is_ignore = True
|
2023-09-19 21:36:56 -07:00
|
|
|
case "waiting":
|
|
|
|
|
for sendmsg in llm_result.send_msgs:
|
|
|
|
|
target = sendmsg.target
|
|
|
|
|
sendmsg.topic = msg.topic
|
|
|
|
|
sendmsg.prev_msg_id = msg.get_msg_id()
|
|
|
|
|
send_resp = await AIBus.get_default_bus().send_message(sendmsg)
|
|
|
|
|
if send_resp is not None:
|
|
|
|
|
result_prompt_str += f"\n{target} response is :{send_resp.body}"
|
|
|
|
|
agent_sesion = AIChatSession.get_session(self.agent_id,f"{sendmsg.target}#{sendmsg.topic}",self.chat_db)
|
|
|
|
|
agent_sesion.append(sendmsg)
|
|
|
|
|
agent_sesion.append(send_resp)
|
2023-08-27 18:07:33 -07:00
|
|
|
|
2023-09-19 21:36:56 -07:00
|
|
|
final_result = llm_result.resp + result_prompt_str
|
|
|
|
|
|
2023-08-27 18:07:33 -07:00
|
|
|
if is_ignore is not True:
|
2023-09-14 01:50:18 -07:00
|
|
|
resp_msg = msg.create_resp_msg(final_result)
|
|
|
|
|
chatsession.append(msg)
|
|
|
|
|
chatsession.append(resp_msg)
|
2023-08-27 18:07:33 -07:00
|
|
|
|
|
|
|
|
return resp_msg
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
2023-08-22 17:11:20 -07:00
|
|
|
def get_id(self) -> str:
|
2023-09-14 01:50:18 -07:00
|
|
|
return self.agent_id
|
2023-08-27 18:07:33 -07:00
|
|
|
|
|
|
|
|
def get_fullname(self) -> str:
|
|
|
|
|
return self.fullname
|
2023-08-22 17:11:20 -07:00
|
|
|
|
|
|
|
|
def get_template_id(self) -> str:
|
|
|
|
|
return self.template_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
|
2023-08-30 12:30:41 -07:00
|
|
|
|
2023-09-14 01:50:18 -07:00
|
|
|
async def _get_prompt_from_session(self,chatsession:AIChatSession,is_groupchat=False) -> AgentPrompt:
|
|
|
|
|
# TODO: get prompt from group chat is different from single chat
|
2023-09-18 23:25:44 -07:00
|
|
|
messages = chatsession.read_history() # read
|
2023-08-30 12:30:41 -07:00
|
|
|
result_prompt = AgentPrompt()
|
|
|
|
|
for msg in reversed(messages):
|
2023-09-18 23:25:44 -07:00
|
|
|
if msg.sender == self.agent_id:
|
2023-08-30 12:30:41 -07:00
|
|
|
result_prompt.messages.append({"role":"assistant","content":msg.body})
|
2023-09-18 23:25:44 -07:00
|
|
|
else:
|
|
|
|
|
result_prompt.messages.append({"role":"user","content":msg.body})
|
2023-08-30 12:30:41 -07:00
|
|
|
return result_prompt
|
2023-08-20 22:53:35 -07:00
|
|
|
|