Implement simple "Agent Think Frame" , Tracy can do teach summary now.

This commit is contained in:
Liu Zhicong
2023-10-01 16:33:49 -07:00
parent ae99571b28
commit 9932b55ae8
4 changed files with 220 additions and 18 deletions
+17 -5
View File
@@ -5,9 +5,21 @@ fullname = "Tracy"
role = "system" role = "system"
content = """ content = """
Your name is Tracy, and you are my advanced private English tutor. Your name is Tracy, and you are my advanced private English tutor.
## You will assess my English proficiency based on all available information, using a 5-point scale. 1. Engage in a simulated dialogue with me smoothly, helping me practice everyday English. While conversing with me, if necessary, you will adjust my input to sound more like authentic American English.
## While interacting with me normally, you will adjust my input into more idiomatic American sentences. 2. Depending on my level of English, you will annotate potentially incorrect words with phonetic symbols or provide expanded explanations for certain words and phrases.
## Depending on my level of English, you will annotate potentially incorrect words with phonetic symbols or provide expanded explanations for certain words and phrases. 3. If I send you something that is not in English, it means I don't know how to say it in American English. You will first translate what I've sent into English and then respond according to the above rules.
## If I send you something that is not in English, it means I don't know how to say it in American English. You will first translate what I've sent into English and then respond according to the above rules. 4. You will chat with me like a friend, rather than just teaching me lessons.
## You will chat with me like a friend, rather than just teaching me lessons.
The first message I sent you might be a work summary from your past. Please use this work summary to guide subsequent teaching.
"""
[[think_prompt]]
role = "system"
content = """
Your name is Tracy, and you are my advanced private English tutor.
You will receive two pieces of information from me next. The first is a work summary you previously organized, and the second is a record of your recent teaching work. You need to combine these two records, engage in deep introspective thinking, and produce a work summary.
1. A comprehensive assessment of the students' English proficiency.
2. Evaluation of students' personalities and hobbies, along with suggestions for teaching methods they might prefer.
3. Assessment of past teaching methods and thoughts on improvements.
4. If there are specific unfinished tasks, key information should be recorded.
""" """
+105 -3
View File
@@ -107,6 +107,7 @@ class AIAgentTemplete:
class AIAgent: class AIAgent:
def __init__(self) -> None: def __init__(self) -> None:
self.agent_prompt:AgentPrompt = None self.agent_prompt:AgentPrompt = None
self.agent_think_prompt:AgentPrompt = None
self.llm_model_name:str = None self.llm_model_name:str = None
self.max_token_size:int = 3600 self.max_token_size:int = 3600
self.agent_id:str = None self.agent_id:str = None
@@ -155,6 +156,10 @@ class AIAgent:
self.agent_prompt = AgentPrompt() self.agent_prompt = AgentPrompt()
self.agent_prompt.load_from_config(config["prompt"]) 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"])
if config.get("guest_prompt") is not None: if config.get("guest_prompt") is not None:
self.guest_prompt_str = config["guest_prompt"] self.guest_prompt_str = config["guest_prompt"]
@@ -202,7 +207,7 @@ class AIAgent:
match func_name: match func_name:
case "send_msg":# sendmsg($target_id,$msg_content) case "send_msg":# sendmsg($target_id,$msg_content)
if len(func_args) != 1: if len(func_args) != 1:
logger.error(f"parse sendmsg failed! {func_call}") logger.error(f"parse sendmsg failed! {func_name}")
return False return False
new_msg = AgentMsg() new_msg = AgentMsg()
target_id = func_item.args[0] target_id = func_item.args[0]
@@ -214,7 +219,7 @@ class AIAgent:
case "post_msg":# postmsg($target_id,$msg_content) case "post_msg":# postmsg($target_id,$msg_content)
if len(func_args) != 1: if len(func_args) != 1:
logger.error(f"parse postmsg failed! {func_call}") logger.error(f"parse postmsg failed! {func_name}")
return False return False
new_msg = AgentMsg() new_msg = AgentMsg()
target_id = func_item.args[0] target_id = func_item.args[0]
@@ -353,6 +358,9 @@ class AIAgent:
async def _get_agent_prompt(self) -> AgentPrompt: async def _get_agent_prompt(self) -> AgentPrompt:
return self.agent_prompt return self.agent_prompt
async def _get_agent_think_prompt(self) -> AgentPrompt:
return self.agent_think_prompt
def _format_msg_by_env_value(self,prompt:AgentPrompt): def _format_msg_by_env_value(self,prompt:AgentPrompt):
if self.owner_env is None: if self.owner_env is None:
return return
@@ -361,6 +369,57 @@ class AIAgent:
old_content = msg.get("content") old_content = msg.get("content")
msg["content"] = old_content.format_map(self.owner_env) msg["content"] = old_content.format_map(self.owner_env)
async def _handle_event(self,event):
if event.type == "AgentThink":
return await self._do_think()
async def _do_think(self):
#1) load all sessions
session_id_list = AIChatSession.list_session(self.agent_id,self.chat_db)
#2) get history from session in token limit
for session_id in session_id_list:
await self.think_chatsession(session_id)
#4) advanced: reload all chatrecord,and think the topic of message.
#5) some topic could be end(not be thinked in futured )
return
async def think_chatsession(self,session_id):
if self.agent_think_prompt is None:
return
logger.info(f"agent {self.agent_id} think session {session_id}")
from .compute_kernel import ComputeKernel
chatsession = AIChatSession.get_session_by_id(session_id,self.chat_db)
while True:
cur_pos = chatsession.summarize_pos
summary = chatsession.summary
prompt:AgentPrompt = AgentPrompt()
#prompt.append(self._get_agent_prompt())
prompt.append(await self._get_agent_think_prompt())
system_prompt_len = prompt.get_prompt_token_len()
#think env?
history_prompt,next_pos = await self._get_history_prompt_for_think(chatsession,summary,system_prompt_len,cur_pos)
prompt.append(history_prompt)
is_finish = next_pos - cur_pos < 2
if is_finish:
logger.info(f"agent {self.agent_id} think session {session_id} is finished!,no more history")
break
#3) llm summarize chat history
task_result:ComputeTaskResult = await ComputeKernel.get_instance().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,None)
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"llm compute error:{task_result.error_str}")
break
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
async def _process_group_chat_msg(self,msg:AgentMsg) -> AgentMsg: async def _process_group_chat_msg(self,msg:AgentMsg) -> AgentMsg:
from .compute_kernel import ComputeKernel from .compute_kernel import ComputeKernel
from .bus import AIBus from .bus import AIBus
@@ -534,6 +593,42 @@ class AIAgent:
def get_max_token_size(self) -> int: def get_max_token_size(self) -> int:
return self.max_token_size return self.max_token_size
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()
have_summary = False
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)
else:
result_prompt.messages.append({"role":"user","content":"There is no summary yet."})
result_token_len -= 6
read_history_msg = 0
history_str : str = ""
for msg in messages:
read_history_msg += 1
dt = datetime.datetime.fromtimestamp(float(msg.create_time))
formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
history_str = history_str + record_str
history_len -= len(msg.body)
result_token_len += len(msg.body)
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): 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 history_len = (self.max_token_size * 0.7) - system_token_len - input_token_len
messages = chatsession.read_history(self.history_len) # read messages = chatsession.read_history(self.history_len) # read
@@ -565,13 +660,20 @@ class AIAgent:
return result_prompt,result_token_len return result_prompt,result_token_len
async def _get_prompt_from_session(self,chatsession:AIChatSession,system_token_len,input_token_len,is_groupchat=False) -> AgentPrompt: 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 # TODO: get prompt from group chat is different from single chat
history_len = (self.max_token_size * 0.7) - system_token_len - input_token_len history_len = (self.max_token_size * 0.7) - system_token_len - input_token_len
messages = chatsession.read_history(self.history_len) # read messages = chatsession.read_history(self.history_len) # read
result_token_len = 0 result_token_len = 0
result_prompt = AgentPrompt() result_prompt = AgentPrompt()
read_history_msg = 0 read_history_msg = 0
if chatsession.summary is not None:
if len(chatsession.summary) > 1:
result_prompt.messages.append({"role":"user","content":chatsession.summary})
result_token_len -= len(chatsession.summary)
for msg in reversed(messages): for msg in reversed(messages):
read_history_msg += 1 read_history_msg += 1
dt = datetime.datetime.fromtimestamp(float(msg.create_time)) dt = datetime.datetime.fromtimestamp(float(msg.create_time))
+90 -8
View File
@@ -50,7 +50,9 @@ class ChatSessionDB:
SessionID TEXT PRIMARY KEY, SessionID TEXT PRIMARY KEY,
SessionOwner TEXT, SessionOwner TEXT,
SessionTopic TEXT, SessionTopic TEXT,
StartTime TEXT StartTime TEXT,
SummarizePos INTEGER,
Summary TEXT
); );
""") """)
@@ -92,8 +94,8 @@ class ChatSessionDB:
try: try:
conn = self._get_conn() conn = self._get_conn()
conn.execute(""" conn.execute("""
INSERT INTO ChatSessions (SessionID, SessionOwner,SessionTopic, StartTime) INSERT INTO ChatSessions (SessionID, SessionOwner,SessionTopic, StartTime,SummarizePos,Summary)
VALUES (?,?, ?, ?) VALUES (?,?, ?, ?,0,"")
""", (session_id, session_owner,session_topic, start_time)) """, (session_id, session_owner,session_topic, start_time))
conn.commit() conn.commit()
return 0 # return 0 if successful return 0 # return 0 if successful
@@ -159,16 +161,17 @@ class ChatSessionDB:
chatsession = c.fetchone() chatsession = c.fetchone()
return chatsession return chatsession
def get_chatsessions(self, limit, offset): def list_chatsessions(self, owner_id, limit, offset):
""" retrieve sessions with pagination """ """ retrieve sessions with pagination """
try: try:
conn = self._get_conn() conn = self._get_conn()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(""" cursor.execute("""
SELECT * FROM ChatSessions SELECT SessionID FROM ChatSessions
WHERE SessionOwner = ?
ORDER BY StartTime DESC ORDER BY StartTime DESC
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
""", (limit, offset)) """, (owner_id,limit, offset))
results = cursor.fetchall() results = cursor.fetchall()
#self.close() #self.close()
return results # return 0 and the result if successful return results # return 0 and the result if successful
@@ -184,6 +187,25 @@ class ChatSessionDB:
message = c.fetchone() message = c.fetchone()
return message return message
# read message from begin->now
def read_message(self,session_id,limit,offset):
try:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages
WHERE SessionID = ?
ORDER BY Timestamp
LIMIT ? OFFSET ?
""", (session_id, limit, offset))
results = cursor.fetchall()
#self.close()
return results # return 0 and the result if successful
except Error as e:
logging.error("Error occurred while getting messages: %s", e)
return -1, None # return -1 and None if an error occurs
# read message from now->beign
def get_messages(self, session_id, limit, offset): def get_messages(self, session_id, limit, offset):
""" retrieve messages of a session with pagination """ """ retrieve messages of a session with pagination """
try: try:
@@ -217,6 +239,20 @@ class ChatSessionDB:
logging.error("Error occurred while updating message status: %s", e) logging.error("Error occurred while updating message status: %s", e)
return -1 # return -1 if an error occurs return -1 # return -1 if an error occurs
def update_session_summary(self, session_id, summarize_pos, summary):
""" update the summary of a session """
try:
conn = self._get_conn()
conn.execute("""
UPDATE ChatSessions
SET SummarizePos = ?, Summary = ?
WHERE SessionID = ?
""", (summarize_pos, summary, session_id))
conn.commit()
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while updating session summary: %s", e)
return -1
# chat session store the chat history between owner and agent # chat session store the chat history between owner and agent
# chat session might be large, so can read / write at stream mode. # chat session might be large, so can read / write at stream mode.
@@ -232,7 +268,7 @@ class AIChatSession:
# #result = AIChatSession() # #result = AIChatSession()
@classmethod @classmethod
def get_session(cls,owner_id:str,session_topic:str,db_path:str,auto_create = True) -> str: def get_session(cls,owner_id:str,session_topic:str,db_path:str,auto_create = True) -> 'AIChatSession':
db = cls._dbs.get(db_path) db = cls._dbs.get(db_path)
if db is None: if db is None:
db = ChatSessionDB(db_path) db = ChatSessionDB(db_path)
@@ -248,9 +284,43 @@ class AIChatSession:
else: else:
result = AIChatSession(owner_id,session[0],db) result = AIChatSession(owner_id,session[0],db)
result.topic = session_topic result.topic = session_topic
result.summarize_pos = session[4]
result.summary = session[5]
return result return result
@classmethod
def get_session_by_id(cls,session_id:str,db_path:str)->'AIChatSession':
db = cls._dbs.get(db_path)
if db is None:
db = ChatSessionDB(db_path)
cls._dbs[db_path] = db
result = None
session = db.get_chatsession_by_id(session_id)
if session is None:
return None
else:
result = AIChatSession(session[1],session[0],db)
result.topic = session[2]
result.summarize_pos = session[4]
result.summary = session[5]
return result
@classmethod
def list_session(cls,owner_id:str,db_path:str) -> list[str]:
db = cls._dbs.get(db_path)
if db is None:
db = ChatSessionDB(db_path)
cls._dbs[db_path] = db
result = db.list_chatsessions(owner_id,16,0)
result_ids = []
for r in result:
result_ids.append(r[0])
return result_ids
def __init__(self,owner_id:str, session_id:str, db:ChatSessionDB) -> None: def __init__(self,owner_id:str, session_id:str, db:ChatSessionDB) -> None:
self.owner_id :str = owner_id self.owner_id :str = owner_id
@@ -259,12 +329,18 @@ class AIChatSession:
self.topic : str = None self.topic : str = None
self.start_time : str = None self.start_time : str = None
self.summarize_pos : int = 0
self.summary = None
def get_owner_id(self) -> str: def get_owner_id(self) -> str:
return self.owner_id return self.owner_id
def read_history(self, number:int=10,offset=0) -> [AgentMsg]: def read_history(self, number:int=10,offset=0,order="revers") -> [AgentMsg]:
if order == "revers":
msgs = self.db.get_messages(self.session_id, number, offset) msgs = self.db.get_messages(self.session_id, number, offset)
else:
msgs = self.db.read_message(self.session_id, number, offset)
result = [] result = []
for msg in msgs: for msg in msgs:
agent_msg = AgentMsg() agent_msg = AgentMsg()
@@ -294,6 +370,12 @@ class AIChatSession:
msg.session_id = self.session_id msg.session_id = self.session_id
self.db.insert_message(msg) self.db.insert_message(msg)
def update_think_progress(self,progress:int,new_summary:str) -> None:
self.db.update_session_summary(self.session_id,progress,new_summary)
self.summarize_pos = progress
self.summary = new_summary
#def attach_event_handler(self,handler) -> None: #def attach_event_handler(self,handler) -> None:
# """chat session changed event handler""" # """chat session changed event handler"""
# pass # pass
+6
View File
@@ -473,6 +473,12 @@ class AIOS_Shell:
return await self.handle_knowledge_commands(args) return await self.handle_knowledge_commands(args)
case 'contact': case 'contact':
return await self.handle_contact_commands(args) return await self.handle_contact_commands(args)
case 'think':
if len(args) >= 1:
target_id = args[0]
the_agent = await AgentManager.get_instance().get(target_id)
if the_agent is not None:
await the_agent._do_think()
case 'open': case 'open':
if len(args) >= 1: if len(args) >= 1:
target_id = args[0] target_id = args[0]