Agent can do TODOs.
This commit is contained in:
+59
-40
@@ -114,12 +114,12 @@ class AIAgent:
|
||||
|
||||
self.review_todo_prompt = None
|
||||
|
||||
self.read_report_prompt = AgentPrompt(DEFAULT_AGENT_READ_REPORT_PROMPT)
|
||||
self.read_report_prompt = None
|
||||
|
||||
self.do_prompt = AgentPrompt(DEFAULT_AGENT_DO_PROMPT)
|
||||
self.self_check_prompt = AgentPrompt(DEFAULT_AGENT_SELF_CHECK_PROMPT)
|
||||
self.do_prompt = None
|
||||
self.check_prompt = None
|
||||
|
||||
self.goal_to_todo_prompt = AgentPrompt(DEFAULT_AGENT_GOAL_TO_TODO_PROMPT)
|
||||
self.goal_to_todo_prompt = None
|
||||
|
||||
self.learn_token_limit = 500
|
||||
self.learn_prompt = AgentPrompt(DEFAULT_AGENT_LEARN_PROMPT)
|
||||
@@ -164,6 +164,11 @@ class AIAgent:
|
||||
self.agent_think_prompt = AgentPrompt()
|
||||
self.agent_think_prompt.load_from_config(config["think_prompt"])
|
||||
|
||||
if config.get("do_prompt") is not None:
|
||||
self.do_prompt = AgentPrompt()
|
||||
self.do_prompt.load_from_config(config["do_prompt"])
|
||||
self.wake_up()
|
||||
|
||||
if config.get("guest_prompt") is not None:
|
||||
self.guest_prompt_str = config["guest_prompt"]
|
||||
|
||||
@@ -500,7 +505,7 @@ class AIAgent:
|
||||
|
||||
final_result = llm_result.resp
|
||||
|
||||
await workspace.exec_op_list(llm_result.op_list)
|
||||
await workspace.exec_op_list(llm_result.op_list,self.agent_id)
|
||||
|
||||
is_ignore = False
|
||||
result_prompt_str = ""
|
||||
@@ -653,13 +658,13 @@ class AIAgent:
|
||||
|
||||
# 尝试完成自己的TOOD (不依赖任何其他Agnet)
|
||||
async def do_my_work(self) -> None:
|
||||
workspace = self.get_current_workspace()
|
||||
workspace : WorkspaceEnvironment = self.get_workspace_by_msg(None)
|
||||
|
||||
# review todo能更整体的思考一次todo的优先级
|
||||
if await self.need_review_todos():
|
||||
await self._llm_review_todos(workspace)
|
||||
|
||||
todo_list = workspace.get_todo_list(self.agent_id)
|
||||
todo_list = await workspace.get_todo_list(self.agent_id)
|
||||
|
||||
for todo in todo_list:
|
||||
if self.agent_energy <= 0:
|
||||
@@ -668,11 +673,11 @@ class AIAgent:
|
||||
if await self.can_do(todo,workspace) is False:
|
||||
continue
|
||||
|
||||
if todo.try_count() < 2:
|
||||
if todo.retry_count < 2:
|
||||
need_think_todo_from_goal = False
|
||||
do_result : AgentTodoResult = await self._llm_do(todo,workspace)
|
||||
await self._llm_do(todo,workspace)
|
||||
self.agent_energy -= 1
|
||||
if do_result.result_state == "done":
|
||||
if todo.state == "done":
|
||||
await self._llm_check_todo(todo,workspace)
|
||||
self.agent_energy -= 1
|
||||
|
||||
@@ -702,20 +707,24 @@ class AIAgent:
|
||||
|
||||
return
|
||||
|
||||
def get_do_prompt(self,todo_type:str) -> AgentPrompt:
|
||||
def get_do_prompt(self,todo_type:str=None) -> 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 can_do(self,todo:AgentTodo,workspace:WorkspaceEnvironment) -> bool:
|
||||
return True
|
||||
return todo.can_do()
|
||||
|
||||
async def _llm_do(self,todo:AgentTodo,workspace:WorkspaceEnvironment) -> AgentTodoResult:
|
||||
prompt : AgentPrompt = AgentPrompt()
|
||||
prompt.append(self.agent_prompt)
|
||||
#prompt.append(self.agent_prompt)
|
||||
prompt.append(workspace.get_role_prompt(self.agent_id))
|
||||
|
||||
do_prompt = workspace.get_do_prompt(todo.type)
|
||||
do_prompt = workspace.get_do_prompt()
|
||||
if do_prompt is None:
|
||||
do_prompt = self.get_do_prompt(todo.type)
|
||||
do_prompt = self.get_do_prompt()
|
||||
|
||||
prompt.append(do_prompt)
|
||||
|
||||
@@ -725,17 +734,18 @@ class AIAgent:
|
||||
#prompt.append(do_log_prompt)
|
||||
prompt.append(self.get_prompt_from_todo(todo))
|
||||
|
||||
task_result:ComputeTaskResult = await self._do_llm_complection(prompt,workspace.get_inner_functions(todo.type))
|
||||
|
||||
task_result:ComputeTaskResult = await self._do_llm_complection(prompt)
|
||||
if task_result.error_str is not None:
|
||||
logger.error(f"_llm_do compute error:{task_result.error_str}")
|
||||
|
||||
llm_result = LLMResult.from_str(task_result.result_str)
|
||||
todo.append_do_result(self.agent_id,llm_result)
|
||||
|
||||
|
||||
await workspace.exec_op_list(llm_result.op_list,self.agent_id)
|
||||
await workspace.append_do_result(self.agent_id,llm_result)
|
||||
|
||||
return task_result
|
||||
|
||||
def get_check_prompt(self) -> AgentPrompt:
|
||||
return self.check_prompt
|
||||
|
||||
async def _llm_check_todo(self, todo:AgentTodo,workspace:WorkspaceEnvironment) -> bool:
|
||||
if self.get_check_prompt(todo) is None:
|
||||
@@ -978,14 +988,14 @@ class AIAgent:
|
||||
return True
|
||||
|
||||
def need_self_think(self) -> bool:
|
||||
return True
|
||||
return False
|
||||
|
||||
def need_self_learn(self) -> bool:
|
||||
return True
|
||||
return False
|
||||
|
||||
def wake_up(self) -> None:
|
||||
if self.agent_task is None:
|
||||
self.agent_task = asyncio.create_task(self._on_timer)
|
||||
self.agent_task = asyncio.create_task(self._on_timer())
|
||||
else:
|
||||
logger.warning(f"agent {self.agent_id} is already wake up!")
|
||||
|
||||
@@ -993,27 +1003,36 @@ class AIAgent:
|
||||
async def _on_timer(self):
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
now = time.time()
|
||||
if now - self.last_recover_time > 60:
|
||||
self.agent_energy += (now - self.last_recover_time) / 60
|
||||
self.last_recover_time = now
|
||||
else:
|
||||
return
|
||||
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
|
||||
|
||||
# complete todo
|
||||
if self.need_work():
|
||||
await self.do_my_work()
|
||||
|
||||
# review other's todo
|
||||
# self.review_other_works()
|
||||
if self.agent_energy <= 1:
|
||||
continue
|
||||
|
||||
# do work summary
|
||||
if self.need_self_think():
|
||||
await self.do_self_think()
|
||||
# complete todo
|
||||
if self.need_work():
|
||||
await self.do_my_work()
|
||||
|
||||
#
|
||||
if self.need_self_learn():
|
||||
await self.do_self_learn()
|
||||
# review other's todo
|
||||
# self.review_other_works()
|
||||
|
||||
# do work summary
|
||||
if self.need_self_think():
|
||||
await self.do_self_think()
|
||||
|
||||
#
|
||||
if self.need_self_learn():
|
||||
await self.do_self_learn()
|
||||
except Exception as e:
|
||||
logger.error(f"agent {self.agent_id} on timer error:{e}")
|
||||
continue
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import copy
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from enum import Enum
|
||||
import uuid
|
||||
@@ -243,8 +243,7 @@ class LLMResult:
|
||||
self.send_msgs : List[AgentMsg] = []
|
||||
self.calls : List[FunctionItem] = []
|
||||
self.post_calls : List[FunctionItem] = []
|
||||
self.op_list : 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()
|
||||
@@ -271,12 +270,16 @@ 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)
|
||||
|
||||
lines = llm_result_str.splitlines()
|
||||
is_need_wait = False
|
||||
@@ -361,15 +364,44 @@ class AgentReport:
|
||||
class AgentTodoResult:
|
||||
def __init__(self) -> None:
|
||||
self.result_state = "error"
|
||||
|
||||
|
||||
class AgentTodo:
|
||||
|
||||
|
||||
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.create_time = time.time()
|
||||
self.due_date = time.time() + 3600 * 24 * 2
|
||||
self.state = "pending"
|
||||
|
||||
self.depend_todo_ids = []
|
||||
self.sub_todos = []
|
||||
|
||||
self.need_check = True
|
||||
self.result : ComputeTaskResult = None
|
||||
self.last_check_result = None
|
||||
|
||||
self.worker = None
|
||||
self.checker = None
|
||||
self.createor = 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.parent_id = json_obj.get("parent_id")
|
||||
|
||||
todo.title = json_obj.get("title")
|
||||
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:
|
||||
@@ -383,14 +415,16 @@ class AgentTodo:
|
||||
todo.checker = json_obj.get("checker")
|
||||
todo.createor = json_obj.get("createor")
|
||||
#todo.retry_count = json_obj.get("retry_count")
|
||||
todo.raw_obj = json_obj
|
||||
|
||||
return todo
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
result = {}
|
||||
result["id"] = self.todo_id
|
||||
result["parent_id"] = self.parent_id
|
||||
#result["parent_id"] = self.parent_id
|
||||
result["title"] = self.title
|
||||
result["create_time"] = datetime.fromtimestamp(self.create_time).isoformat()
|
||||
result["detail"] = self.detail
|
||||
result["due_date"] = datetime.fromtimestamp(self.due_date).isoformat()
|
||||
result["depend_todo_ids"] = self.depend_todo_ids
|
||||
@@ -401,33 +435,18 @@ class AgentTodo:
|
||||
result["retry_count"] = self.retry_count
|
||||
|
||||
return result
|
||||
|
||||
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.create_time = time.time()
|
||||
self.due_date = time.time() + 3600 * 24 * 2
|
||||
|
||||
self.depend_todo_ids = []
|
||||
|
||||
self.need_check = True
|
||||
self.result : ComputeTaskResult = None
|
||||
self.last_check_result = None
|
||||
|
||||
self.worker = None
|
||||
self.checker = None
|
||||
self.createor = None
|
||||
|
||||
self.retry_count = 0
|
||||
|
||||
def can_do(self) -> bool:
|
||||
for sub_todo in self.sub_todos:
|
||||
if sub_todo.state != "done":
|
||||
return False
|
||||
|
||||
now = datetime.now().timestamp()
|
||||
time_diff = now - self.due_date
|
||||
|
||||
if time_diff > 7*24*3600:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def save(self):
|
||||
pass
|
||||
|
||||
|
||||
class AgentWorkLog:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@@ -17,7 +17,7 @@ from typing import Any,List
|
||||
import os
|
||||
import chardet
|
||||
|
||||
from .agent_base import AgentMsg,AgentTodo
|
||||
from .agent_base import AgentMsg,AgentTodo,AgentPrompt
|
||||
from .environment import Environment,EnvironmentEvent
|
||||
from .ai_function import AIFunction,SimpleAIFunction
|
||||
from .storage import AIStorage,ResourceLocation
|
||||
@@ -31,6 +31,8 @@ class WorkspaceEnvironment(Environment):
|
||||
self.root_path = f"{myai_path}/workspace/{env_id}"
|
||||
if not os.path.exists(self.root_path):
|
||||
os.makedirs(self.root_path+"/todos")
|
||||
|
||||
self.known_todo = {}
|
||||
|
||||
|
||||
def set_root_path(self,path:str):
|
||||
@@ -39,20 +41,23 @@ class WorkspaceEnvironment(Environment):
|
||||
def get_prompt(self) -> AgentMsg:
|
||||
return None
|
||||
|
||||
def get_role_prompt(self,role_id:str) -> AgentMsg:
|
||||
def get_role_prompt(self,role_id:str) -> AgentPrompt:
|
||||
return None
|
||||
|
||||
def get_knowledge_base(self) -> str:
|
||||
pass
|
||||
|
||||
async def exec_op_list(self,oplist:List)->None:
|
||||
def get_do_prompt(self,todo_type:str=None)->AgentPrompt:
|
||||
return None
|
||||
|
||||
async def exec_op_list(self,oplist:List,agent_id:str)->None:
|
||||
if oplist is None:
|
||||
return
|
||||
|
||||
for op in oplist:
|
||||
if op["op"] == "create":
|
||||
return await self.create(op["path"],op["content"])
|
||||
elif op["op"] == "write":
|
||||
elif op["op"] == "write_file":
|
||||
return await self.write(op["path"],op["content"],op["mode"])
|
||||
elif op["op"] == "delete":
|
||||
return await self.delete(op["path"])
|
||||
@@ -62,8 +67,14 @@ class WorkspaceEnvironment(Environment):
|
||||
return await self.mkdir(op["path"])
|
||||
elif op["op"] == "create_todo":
|
||||
todoObj = AgentTodo.from_dict(op["todo"])
|
||||
path = op.get("path")
|
||||
return await self.create_todo(path,todoObj)
|
||||
todoObj.worker = agent_id
|
||||
todoObj.createor = agent_id
|
||||
parent_id = op.get("parent")
|
||||
await self.create_todo(parent_id,todoObj)
|
||||
elif op["op"] == "update_todo":
|
||||
todo_id = op["id"]
|
||||
new_stat = op["state"]
|
||||
await self.update_todo(todo_id,new_stat)
|
||||
|
||||
else:
|
||||
logger.error(f"execute op list failed: unknown op:{op['op']}")
|
||||
@@ -92,6 +103,10 @@ class WorkspaceEnvironment(Environment):
|
||||
content = await f.read(2048)
|
||||
return content
|
||||
|
||||
# use diff to update large file content
|
||||
async def write_diff(self,path:str,diff):
|
||||
pass
|
||||
|
||||
async def write(self,path:str,content:str,is_append:bool=False) -> str:
|
||||
file_path = self.root_path + path
|
||||
if is_append:
|
||||
@@ -136,6 +151,7 @@ class WorkspaceEnvironment(Environment):
|
||||
else:
|
||||
directory_path = self.root_path + "/todos"
|
||||
|
||||
|
||||
str_result:str = "/todos\n"
|
||||
todo_count:int = 0
|
||||
|
||||
@@ -145,10 +161,16 @@ class WorkspaceEnvironment(Environment):
|
||||
if deep <= 0:
|
||||
return
|
||||
|
||||
if os.path.exists(directory_path) is False:
|
||||
return
|
||||
|
||||
for entry in os.scandir(directory_path):
|
||||
is_dir = entry.is_dir()
|
||||
if not is_dir:
|
||||
continue
|
||||
|
||||
if entry.name.startswith("."):
|
||||
continue
|
||||
|
||||
todo_count = todo_count + 1
|
||||
str_result = str_result + f"{' '*(4-deep)}└─{entry.name}\n"
|
||||
@@ -157,25 +179,99 @@ class WorkspaceEnvironment(Environment):
|
||||
await scan_dir(directory_path,deep)
|
||||
return str_result,todo_count
|
||||
|
||||
|
||||
async def get_todo_by_path(self,path:str) -> AgentTodo:
|
||||
pass
|
||||
|
||||
async def get_todo(self,id:str) -> AgentTodo:
|
||||
pass
|
||||
|
||||
async def create_todo(self,path:str,todo:AgentTodo) -> None:
|
||||
if path is None:
|
||||
dir_path = f"/todos/{todo.title}"
|
||||
async def get_todo_list(self,agent_id:str,path:str = None)->List[AgentTodo]:
|
||||
if path:
|
||||
directory_path = self.root_path + "/todos/" + path
|
||||
else:
|
||||
dir_path = f"/todos/{path}/{todo.title}"
|
||||
directory_path = self.root_path + "/todos"
|
||||
|
||||
os.makedirs(self.root_path + dir_path)
|
||||
result_list:List[AgentTodo] = []
|
||||
|
||||
async def scan_dir(directory_path:str,deep:int,parent:AgentTodo=None):
|
||||
nonlocal result_list
|
||||
if os.path.exists(directory_path) is False:
|
||||
return
|
||||
|
||||
for entry in os.scandir(directory_path):
|
||||
is_dir = entry.is_dir()
|
||||
if not is_dir:
|
||||
continue
|
||||
|
||||
if entry.name.startswith("."):
|
||||
continue
|
||||
|
||||
todo = await self.get_todo_by_fullpath(entry.path)
|
||||
if todo.worker != agent_id:
|
||||
continue
|
||||
|
||||
todo.rank = int(todo.create_time)>>deep
|
||||
|
||||
if todo:
|
||||
if parent:
|
||||
parent.sub_todos.append(todo)
|
||||
result_list.append(todo)
|
||||
|
||||
await scan_dir(entry.path,deep + 1,todo)
|
||||
|
||||
return
|
||||
|
||||
await scan_dir(directory_path,0)
|
||||
#sort by rank
|
||||
result_list.sort(key=lambda x:(x.rank,x.title))
|
||||
|
||||
return result_list
|
||||
|
||||
async def get_todo_by_fullpath(self,path:str) -> AgentTodo:
|
||||
logger.info("get_todo_by_fullpath:%s",path)
|
||||
detail_path = path + "/detail"
|
||||
|
||||
async with aiofiles.open(detail_path, mode='r', encoding="utf-8") as f:
|
||||
content = await f.read(4096)
|
||||
logger.debug("get_todo_by_fullpath:%s,content:%s",path,content)
|
||||
todo_dict = json.loads(content)
|
||||
result_todo = AgentTodo.from_dict(todo_dict)
|
||||
if result_todo:
|
||||
result_todo.todo_path = path
|
||||
self.known_todo[result_todo.todo_id] = result_todo
|
||||
else:
|
||||
logger.error("get_todo_by_path:%s,parse failed!",path)
|
||||
|
||||
return result_todo
|
||||
|
||||
async def get_todo(self,id:str) -> AgentTodo:
|
||||
return self.known_todo.get(id)
|
||||
|
||||
async def create_todo(self,parent_id:str,todo:AgentTodo) -> None:
|
||||
if parent_id:
|
||||
parent_path = self.known_todo.get(parent_id).todo_path
|
||||
dir_path = f"{parent_path}/{todo.title}"
|
||||
else:
|
||||
dir_path = f"{self.root_path}/todos/{todo.title}"
|
||||
|
||||
os.makedirs(dir_path)
|
||||
detail_path = f"{dir_path}/detail"
|
||||
await self.create(detail_path,json.dumps(todo.to_dict()))
|
||||
logger.info("create_todo %s",detail_path)
|
||||
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
|
||||
await f.write(json.dumps(todo.to_dict()))
|
||||
return True
|
||||
|
||||
async def update_todo(self,path:str,todo:AgentTodo)->None:
|
||||
pass
|
||||
async def update_todo(self,todo_id:str,new_stat:str)->bool:
|
||||
todo : AgentTodo = self.known_todo.get(todo_id)
|
||||
if todo:
|
||||
todo.status = new_stat
|
||||
if todo.raw_obj is None:
|
||||
todo.raw_obj = todo.to_dict()
|
||||
todo.raw_obj["status"] = new_stat
|
||||
|
||||
detail_path = todo.todo_path + "/detail"
|
||||
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
|
||||
await f.write(json.dumps(todo.raw_obj))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def append_do_result(self,todo:AgentTodo,result):
|
||||
return True
|
||||
|
||||
class CodeInterpreter:
|
||||
def __init__(self, language, debug_mode):
|
||||
|
||||
Reference in New Issue
Block a user