Files
opendan/src/aios/environment/workspace_env.py
T

322 lines
11 KiB
Python
Raw Normal View History

2023-10-18 11:19:11 -07:00
import json
2023-11-01 19:29:55 -07:00
import logging
2023-09-21 23:18:52 -07:00
import os
2023-11-03 01:16:32 -07:00
import aiofiles
2023-12-04 17:15:09 +08:00
import sqlite3
import asyncio
from typing import Any,List,Dict
2023-10-18 11:19:11 -07:00
import chardet
2023-12-01 14:29:10 +08:00
from ..agent.agent_base import AgentMsg,AgentTodo,AgentPrompt,AgentTodoResult
from ..agent.ai_function import AIFunction,SimpleAIFunction, SimpleAIOperation
from ..storage.storage import AIStorage,ResourceLocation
2023-12-01 14:29:10 +08:00
from .environment import SimpleEnvironment, CompositeEnvironment
2023-12-01 14:29:10 +08:00
logger = logging.getLogger(__name__)
2023-12-01 14:29:10 +08:00
class TodoListType:
TO_WORK = "work"
TO_LEARN = "learn"
2023-12-01 14:29:10 +08:00
class TodoListEnvironment(SimpleEnvironment):
2023-12-04 17:15:09 +08:00
def __init__(self, workspace, list_type) -> None:
super().__init__(workspace)
self.root_path = os.path.join(workspace, list_type)
2023-12-01 14:29:10 +08:00
if not os.path.exists(self.root_path):
os.makedirs(self.root_path)
2023-12-04 17:15:09 +08:00
self.db_path = os.path.join(self.root_path, "todo.db")
self.conn = None
try:
self.conn = sqlite3.connect(self.db_path)
except Exception as e:
logger.error("Error occurred while connecting to database: %s", e)
return None
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS todo_list (
id TEXT,
path TEXT
)
''')
self.conn.commit()
2023-12-01 14:29:10 +08:00
async def create_todo(params):
todoObj = AgentTodo.from_dict(params["todo"])
parent_id = params.get("parent")
return await self.create_todo(parent_id,todoObj)
self.add_ai_operation(SimpleAIOperation(
op="create_todo",
description="create todo",
func_handler=create_todo,
))
async def update_todo(params):
todo_id = params["id"]
new_stat = params["state"]
return await self.update_todo(todo_id,new_stat)
self.add_ai_operation(SimpleAIOperation(
op="update_todo",
description="update todo",
func_handler=update_todo,
))
2023-12-04 17:15:09 +08:00
def _get_todo_path(self,todo_id:str) -> str:
cursor = self.conn.cursor()
cursor.execute('''
SELECT path FROM todo_list WHERE id = ?
''',(todo_id,))
row = cursor.fetchone()
if row:
return row[0]
else:
return None
def _save_todo_path(self,todo_id:str,path:str):
cursor = self.conn.cursor()
cursor.execute('''
INSERT INTO todo_list (id,path) VALUES (?,?)
''',(todo_id,path))
self.conn.commit()
2023-12-01 14:29:10 +08:00
# Task/todo system , create,update,delete,query
2023-11-03 01:16:32 -07:00
async def get_todo_tree(self,path:str = None,deep:int = 4):
if path:
2023-12-01 14:29:10 +08:00
directory_path = os.path.join(self.root_path, path)
2023-11-03 01:16:32 -07:00
else:
2023-12-01 14:29:10 +08:00
directory_path = self.root_path
2023-11-03 01:16:32 -07:00
2023-11-03 22:31:23 -07:00
2023-11-03 01:16:32 -07:00
str_result:str = "/todos\n"
todo_count:int = 0
async def scan_dir(directory_path:str,deep:int):
nonlocal str_result
nonlocal todo_count
if deep <= 0:
return
2023-11-03 22:31:23 -07:00
if os.path.exists(directory_path) is False:
return
2023-11-03 01:16:32 -07:00
for entry in os.scandir(directory_path):
is_dir = entry.is_dir()
if not is_dir:
continue
2023-11-03 22:31:23 -07:00
if entry.name.startswith("."):
continue
2023-11-03 01:16:32 -07:00
todo_count = todo_count + 1
2023-11-05 15:21:27 -08:00
str_result = str_result + f"{' '*(4-deep)}{entry.name}\n"
2023-11-03 01:16:32 -07:00
await scan_dir(entry.path,deep-1)
2023-11-01 19:29:55 -07:00
2023-11-03 01:16:32 -07:00
await scan_dir(directory_path,deep)
return str_result,todo_count
2023-11-01 19:29:55 -07:00
2023-11-03 22:31:23 -07:00
async def get_todo_list(self,agent_id:str,path:str = None)->List[AgentTodo]:
2023-11-05 15:21:27 -08:00
logger.info("get_todo_list:%s,%s",agent_id,path)
2023-11-03 22:31:23 -07:00
if path:
2023-12-01 14:29:10 +08:00
directory_path = os.path.join(self.root_path, path)
2023-11-03 22:31:23 -07:00
else:
2023-12-01 14:29:10 +08:00
directory_path = self.root_path
2023-11-03 22:31:23 -07:00
result_list:List[AgentTodo] = []
2023-11-01 19:29:55 -07:00
2023-11-03 22:31:23 -07:00
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:
if todo.worker:
if todo.worker != agent_id:
continue
2023-11-03 22:31:23 -07:00
if parent:
2023-11-05 15:21:27 -08:00
parent.sub_todos[todo.todo_id] = todo
2023-11-03 22:31:23 -07:00
result_list.append(todo)
todo.rank = int(todo.create_time)>>deep
await scan_dir(entry.path,deep + 1,todo)
2023-11-03 22:31:23 -07:00
return
await scan_dir(directory_path,0)
#sort by rank
result_list.sort(key=lambda x:(x.rank,x.title))
2023-11-05 15:21:27 -08:00
logger.info("get_todo_list return,todolist.length() is %d",len(result_list))
2023-11-03 22:31:23 -07:00
return result_list
async def get_todo_by_fullpath(self,path:str) -> AgentTodo:
logger.info("get_todo_by_fullpath:%s",path)
2023-11-03 22:31:23 -07:00
detail_path = path + "/detail"
try:
2023-12-05 18:50:32 +08:00
with open(detail_path, mode='r', encoding="utf-8") as f:
todo_dict = json.load(f)
result_todo = AgentTodo.from_dict(todo_dict)
if result_todo:
2023-12-01 14:29:10 +08:00
relative_path = os.path.relpath(path, self.root_path)
if not relative_path.startswith('/'):
relative_path = '/' + relative_path
result_todo.todo_path = relative_path
else:
logger.error("get_todo_by_path:%s,parse failed!",path)
return result_todo
except Exception as e:
logger.error("get_todo_by_path:%s,failed:%s",path,e)
return None
2023-12-04 17:15:09 +08:00
2023-11-01 19:29:55 -07:00
async def create_todo(self,parent_id:str,todo:AgentTodo) -> str:
try:
if parent_id:
2023-12-04 17:15:09 +08:00
parent_path = self._get_todo_path(parent_id)
2023-12-05 18:50:32 +08:00
todo_path = f"{parent_path}/{todo.todo_id}-{todo.title}"
else:
2023-12-05 18:50:32 +08:00
todo_path = f"{todo.todo_id}-{todo.title}"
2023-11-03 22:31:23 -07:00
2023-12-01 14:29:10 +08:00
dir_path = f"{self.root_path}/{todo_path}"
os.makedirs(dir_path)
detail_path = f"{dir_path}/detail"
if todo.todo_path is None:
todo.todo_path = todo_path
2023-12-04 17:15:09 +08:00
self._save_todo_path(todo.todo_id,todo_path)
logger.info("create_todo %s",detail_path)
2023-11-03 22:31:23 -07:00
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(todo.to_dict()))
except Exception as e:
logger.error("create_todo failed:%s",e)
return str(e)
2023-11-03 22:31:23 -07:00
return None
async def update_todo(self,todo_id:str,new_stat:str)->str:
try:
2023-12-04 17:15:09 +08:00
todo_path = self._get_todo_path(todo_id)
2023-12-05 18:50:32 +08:00
full_path = f"{self.root_path}/{todo_path}"
todo : AgentTodo = await self.get_todo_by_fullpath(full_path)
if todo:
todo.state = new_stat
2023-12-05 18:50:32 +08:00
detail_path = f"{full_path}/detail"
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(todo.to_dict()))
return None
else:
return "todo not found."
except Exception as e:
return str(e)
2023-11-03 22:31:23 -07:00
2023-12-05 18:50:32 +08:00
async def wait_todo_done(self,todo_id:str,state=AgentTodo.TODO_STATE_WAITING_CHECK) -> AgentTodo:
2023-12-04 17:15:09 +08:00
todo_path = self._get_todo_path(todo_id)
2023-12-05 18:50:32 +08:00
full_path = f"{self.root_path}/{todo_path}"
2023-12-04 17:15:09 +08:00
async def check_done():
while True:
2023-12-05 18:50:32 +08:00
todo : AgentTodo = await self.get_todo_by_fullpath(full_path)
if todo is None:
continue
if todo.state == AgentTodo.TODO_STATE_CANCEL:
break
elif todo.state == AgentTodo.TODO_STATE_EXPIRED:
break
elif todo.state == AgentTodo.TODO_STATE_WAITING_CHECK:
if state == AgentTodo.TODO_STATE_WAITING_CHECK:
break
elif todo.state == AgentTodo.TODO_STATE_DONE:
if state == AgentTodo.TODO_STATE_WAITING_CHECK:
2023-12-04 17:15:09 +08:00
break
2023-12-05 18:50:32 +08:00
elif todo.state == AgentTodo.TODO_STATE_DONE:
break
elif todo.state == AgentTodo.TODO_STATE_REVIEWED:
break
2023-12-04 17:15:09 +08:00
await asyncio.sleep(1)
2023-12-05 18:50:32 +08:00
await check_done()
return await self.get_todo_by_fullpath(full_path)
2023-12-04 17:15:09 +08:00
2023-12-01 14:29:10 +08:00
async def append_worklog(self, todo:AgentTodo, result:AgentTodoResult):
worklog = f"{self.root_path}/{todo.todo_path}/.worklog"
async with aiofiles.open(worklog, mode='w+', encoding="utf-8") as f:
content = await f.read()
if len(content) > 0:
json_obj = json.loads(content)
else:
json_obj = {}
logs = json_obj.get("logs")
if logs is None:
logs = []
logs.append(result.to_dict())
json_obj["logs"] = logs
await f.write(json.dumps(json_obj))
2023-09-21 23:18:52 -07:00
2023-12-01 14:29:10 +08:00
class WorkspaceEnvironment(CompositeEnvironment):
def __init__(self, env_id: str) -> None:
myai_path = AIStorage.get_instance().get_myai_dir()
2023-12-04 17:15:09 +08:00
root_path = f"{myai_path}/workspace/{env_id}"
super().__init__(root_path)
self.root_path = root_path
2023-12-01 14:29:10 +08:00
if not os.path.exists(self.root_path):
os.makedirs()
2023-12-04 17:15:09 +08:00
self.todo_list: Dict[str, TodoListEnvironment] = {}
2023-12-01 14:29:10 +08:00
self.todo_list[TodoListType.TO_WORK] = TodoListEnvironment(self.root_path,TodoListType.TO_WORK)
self.todo_list[TodoListType.TO_LEARN] = TodoListEnvironment(self.root_path,TodoListType.TO_LEARN)
# default environments in workspace
self.add_env(self.todo_list[TodoListType.TO_WORK])
def set_root_path(self,path:str):
self.root_path = path
def get_prompt(self) -> AgentMsg:
return None
def get_role_prompt(self,role_id:str) -> AgentPrompt:
return None
def get_do_prompt(self,todo:AgentTodo=None)->AgentPrompt:
return None
# result mean: list[op_error_str],have_error
async def exec_op_list(self,oplist:List,agent_id:str)->tuple[List[str],bool]:
result_str = "op list is none"
if oplist is None:
return None,False
result_str = []
have_error = False
for op in oplist:
operation = self.get_ai_operation(op["op"])
if operation:
error_str = await operation.execute(op)
else:
logger.error(f"execute op list failed: unknown op:{op['op']}")
error_str = f"execute op list failed: unknown op:{op['op']}"
if error_str:
have_error = True
result_str.append(error_str)
else:
result_str.append(f"execute success!")
return result_str,have_error