fix bugs.

This commit is contained in:
Liu Zhicong
2024-01-05 20:02:58 -08:00
parent b6395c2195
commit 3663f140fc
17 changed files with 159 additions and 121 deletions
+1
View File
@@ -10,6 +10,7 @@ from .agent.agent import AIAgent,AIAgentTemplete, BaseAIAgent
from .agent.role import AIRole,AIRoleGroup
from .agent.workflow import Workflow
from .agent.agent_memory import AgentMemory
from .agent.workspace import AgentWorkspace
from .agent.llm_context import LLMProcessContext,GlobaToolsLibrary,SimpleLLMContext
from .frame.compute_kernel import ComputeKernel,ComputeTask,ComputeTaskResult,ComputeTaskState,ComputeTaskType
+43 -40
View File
@@ -17,11 +17,11 @@ class LLMProcessContext:
def function2action(ai_func:AIFunction) -> AIAction:
async def exec_func(params:Dict) -> str:
return await ai_func.execute(params)
return SimpleAIAction(ai_func.get_name(),ai_func.get_detail_description(),exec_func)
return SimpleAIAction(ai_func.get_id(),ai_func.get_detail_description(),exec_func)
@staticmethod
def aifunction_to_inner_function(self,all_inner_function:List[AIFunction]) -> List[Dict]:
def aifunctions_to_inner_functions(all_inner_function:List[AIFunction]) -> List[Dict]:
result_func = []
result_len = 0
for inner_func in all_inner_function:
@@ -34,6 +34,7 @@ class LLMProcessContext:
result_func.append(this_func)
return result_func
@abstractmethod
def get_ai_function(self,func_name:str) -> AIFunction:
pass
@@ -63,13 +64,14 @@ class LLMProcessContext:
def get_value(self,key:str) -> Optional[str]:
pass
def list_actions(self,path:str) -> List[AIAction]:
return "No more actions!"
#def list_actions(self,path:str) -> List[AIAction]:
# return "No more actions!"
def list_functions(self,path:str) -> List[AIFunction]:
return "No more tool functions!"
#def list_functions(self,path:str) -> List[AIFunction]:
# return "No more tool functions!"
class GlobaToolsLibrary:
_instance = None
@classmethod
def get_instance(cls) -> 'GlobaToolsLibrary':
if cls._instance is None:
@@ -89,10 +91,10 @@ class GlobaToolsLibrary:
return self.all_preset_context.get(preset_id)
def register_tool_function(self,function:AIFunction) -> None:
if self.all_tool_functions.get(function.get_name()):
logger.warning(f"Tool function {function.get_name()} already exists! will be replaced!")
if self.all_tool_functions.get(function.get_id()):
logger.warning(f"Tool function {function.get_id()} already exists! will be replaced!")
self.all_tool_functions[function.get_name()] = function
self.all_tool_functions[function.get_id()] = function
def get_tool_function(self,function_name:str) -> AIFunction:
return self.all_tool_functions.get(function_name)
@@ -139,18 +141,19 @@ class SimpleLLMContext(LLMProcessContext):
return None
disable_actions = config.get("disable")
for disable_action in disable_actions:
if result.get(disable_action):
result.pop(disable_action)
else:
func_set = GlobaToolsLibrary.get_instance().get_function_set(action_id)
if func_set:
for _func_id in func_set:
if result.get(_func_id):
result.pop(_func_id)
if disable_actions:
for disable_action in disable_actions:
if result.get(disable_action):
result.pop(disable_action)
else:
logger.error(f"load_action_set_from_config failed! disable action id {action_id} not found!")
return None
func_set = GlobaToolsLibrary.get_instance().get_function_set(action_id)
if func_set:
for _func_id in func_set:
if result.get(_func_id):
result.pop(_func_id)
else:
logger.error(f"load_action_set_from_config failed! disable action id {action_id} not found!")
return None
return result
@@ -182,18 +185,19 @@ class SimpleLLMContext(LLMProcessContext):
disable_functions = config.get("disable")
for disable_function in disable_functions:
if result.get(disable_function):
result.pop(disable_function)
else:
func_set = GlobaToolsLibrary.get_instance().get_function_set(func_id)
if func_set:
for func_id in func_set:
if result.get(func_id):
result.pop(func_id)
if disable_functions:
for disable_function in disable_functions:
if result.get(disable_function):
result.pop(disable_function)
else:
logger.error(f"load_function_set_from_config failed! disable function id {disable_function} not found!")
return None
func_set = GlobaToolsLibrary.get_instance().get_function_set(func_id)
if func_set:
for func_id in func_set:
if result.get(func_id):
result.pop(func_id)
else:
logger.error(f"load_function_set_from_config failed! disable function id {disable_function} not found!")
return None
return result
@@ -268,15 +272,14 @@ class SimpleLLMContext(LLMProcessContext):
def set_value(self,key:str,value:str):
self.values[key] = value
#def get_ai_function(self,func_name:str) -> AIFunction:
# func = self.functions.get(func_name)
# if func is not None:
# return func
# for set_name in self.func_sets.keys():
# func = self.func_sets[set_name].get(func_name)
# if func is not None:
# return func
def get_ai_function(self,func_name:str) -> AIFunction:
for func in self.functions.values():
if func.get_name() == func_name:
return func
#for set_name in self.func_sets.keys():
# func = self.func_sets[set_name].get(func_name)
# if func is not None:
# return func
def get_function_set(self,set_name:str = None) -> List[AIFunction]:
if set_name is None:
+32 -5
View File
@@ -80,6 +80,9 @@ class BaseLLMProcess(ABC):
def _format_content_by_env_value(self,content:str,env)->str:
return content.format_map(env)
def prepare_inner_function_context_for_exec(self,inner_func_name:str,parameters:Dict):
return
async def _execute_inner_func(self,inner_func_call_node:Dict,prompt: LLMPrompt,stack_limit = 1) -> ComputeTaskResult:
arguments = None
stack_limit = stack_limit - 1
@@ -92,7 +95,8 @@ class BaseLLMProcess(ABC):
if func_node is None:
result_str:str = f"execute {func_name} error,function not found"
else:
result_str:str = await func_node.execute(**arguments)
self.prepare_inner_function_context_for_exec(func_name,arguments)
result_str:str = await func_node.execute(arguments)
except Exception as e:
result_str = f"execute {func_name} error:{str(e)}"
logger.error(f"LLMProcess execute inner func:{func_name} error:\n\t{e}")
@@ -440,9 +444,8 @@ class LLMAgentMessageProcess(BaseLLMProcess):
## 可以使用的tools(inner function)的解释,注意不定义该tips,则不会导入任何workspace中的tools
if self.tools_tips:
system_prompt_dict["tools_tips"] = self.tools_tips
#prompt.append_system_message(self.tools_tips)
#self.llm_context.
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
if self.workspace:
#TODO eanble workspace functions?
logger.info(f"workspace is not none,enable workspace functions")
@@ -458,6 +461,8 @@ class LLMAgentMessageProcess(BaseLLMProcess):
return prompt
def prepare_inner_function_context_for_exec(self,inner_func_name:str,parameters:Dict):
parameters["_workspace"] = self.workspace
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
return self.llm_context.get_ai_function(func_name)
@@ -518,9 +523,30 @@ class ReviewTaskProcess(BaseLLMProcess):
return True
async def load_from_config(self, config: dict):
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
if await super().load_from_config(config) is False:
return False
self.role_description = config.get("role_desc")
if self.role_description is None:
logger.error(f"role_description not found in config")
return False
if config.get("process_description"):
self.process_description = config.get("process_description")
if config.get("reply_format"):
self.reply_format = config.get("reply_format")
if config.get("context"):
self.context = config.get("context")
self.llm_context = SimpleLLMContext()
if config.get("llm_context"):
self.llm_context.load_from_config(config.get("llm_context"))
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
agent_task = input.get("task")
@@ -529,7 +555,8 @@ class ReviewTaskProcess(BaseLLMProcess):
system_prompt_dict["role_description"] = self.role_description
system_prompt_dict["process_rule"] = self.process_description
system_prompt_dict["reply_format"] = self.reply_format
prompt.append_system_message(json.dumps(system_prompt_dict))
prompt.append_user_message(json.dumps(agent_task.to_dict()))
return prompt
+1 -1
View File
@@ -310,7 +310,7 @@ class Workflow:
result_func = []
for inner_func in all_inner_function:
func_name = inner_func.get_name()
func_name = inner_func.get_id()
if the_role.enable_function_list is not None:
if len(the_role.enable_function_list) > 0:
if func_name not in the_role.enable_function_list:
+46 -50
View File
@@ -8,7 +8,7 @@ from typing import List, Optional
import aiofiles
from ..proto.ai_function import AIFunction,SimpleAIFunction,ActionNode,SimpleAIAction
from ..proto.ai_function import AIFunction, ParameterDefine,SimpleAIFunction,ActionNode,SimpleAIAction
from ..proto.agent_task import AgentTask,AgentTodoTask,AgentWorkLog,AgentTaskManager
from ..storage.storage import AIStorage
from .llm_context import GlobaToolsLibrary
@@ -77,11 +77,12 @@ class LocalAgentTaskManger(AgentTaskManager):
logger.info("create_task at %s",detail_path)
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(task.to_dict()))
return "create task ok"
except Exception as e:
logger.error("create_task failed:%s",e)
return str(e)
return None
async def create_todos(self,owner_task_id:str,todos:List[AgentTodoTask]):
owner_task_path = self._get_obj_path(owner_task_id)
@@ -240,7 +241,6 @@ class LocalAgentTaskManger(AgentTaskManager):
async def list_task(self,filter:Optional[dict] = None ) -> List[AgentTask]:
directory_path = self.root_path
result_list:List[AgentTask] = []
@@ -251,7 +251,7 @@ class LocalAgentTaskManger(AgentTaskManager):
continue
if entry.name == "workspace":
continue
task_item = await self.get_task_by_path(entry.path)
task_item = await self._get_task_by_fullpath(entry.path)
if task_item:
if not task_item.is_finish():
result_list.append(task_item)
@@ -259,8 +259,6 @@ class LocalAgentTaskManger(AgentTaskManager):
return result_list
async def update_task(self,task:AgentTask):
detail_path = f"{self.root_path}/{task.task_path}/detail"
try:
@@ -316,70 +314,68 @@ class AgentWorkspace:
def __init__(self,owner_agent_id:str) -> None:
self.agent_id : str = owner_agent_id
self.task_mgr : AgentTaskManager = LocalAgentTaskManger(owner_agent_id)
self.actions : Dict[str,ActionNode] = {}
self.inner_functions : Dict[str,AIFunction] = {}
#self.init_actions()
#self.init_inner_functions()
@staticmethod
def register_actions():
def register_ai_functions():
async def create_task(params):
_self = params.get("_workspace")
if _self is None:
return "self not found"
_workspace = params.get("_workspace")
if _workspace is None:
return "_workspace not found"
taskObj = AgentTask.create_by_dict(params)
parent_id = params.get("parent")
return await _self.task_mgr.create_task(taskObj,parent_id)
create_task_action = SimpleAIAction(
return await _workspace.task_mgr.create_task(taskObj,parent_id)
parameters = ParameterDefine.create_parameters({
"title": {"type": "string", "description": "task title"},
"detail": {"type": "string", "description": "task detail(simple task can not be filled)"},
"tags": {"type": "string", "description": "optional,task tags"},
"due_date": {"type": "string", "description": "optional,task due date"},
"parent": {"type": "string", "description": "optional,parent task id"},
})
create_task_action = SimpleAIFunction(
"agent.workspace.create_task",
"Create a task in the task system, the supported parameters are: title, detail (simple task can not be filled), tags,due_date",
"Create a task",
create_task,
parameters,
)
GlobaToolsLibrary.get_instance().register_tool_function(create_task_action)
async def cancel_task(parameters):
_self = parameters.get("_workspace")
if _self is None:
return "self not found"
_workspace = parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
task = await _self.task_mgr.get_task(task_id)
task = await _workspace.task_mgr.get_task(task_id)
if task is None:
return f"task {task_id} not found"
task.state = "cancel"
return await _self.task_mgr.update_task(task)
cancel_task_action = SimpleAIAction(
await _workspace.task_mgr.update_task(task)
return "canncel task ok"
parameters = ParameterDefine.create_parameters({
"task_id": {"type": "string", "description": "task id which want to cancel"},
})
cancel_task_action = SimpleAIFunction(
"agent.workspace.cancel_task",
"Cancel this task",
cancel_task,
parameters
)
GlobaToolsLibrary.get_instance().register_tool_function(create_task_action)
GlobaToolsLibrary.get_instance().register_tool_function(cancel_task_action)
def get_actions(self) -> Dict:
return self.actions
def init_inner_functions(self):
async def list_tasks():
result = {}
fitler = {}
task_list = await self.task_mgr.list_task(fitler)
for task_item in task_list:
result[task_item.task_id] = task_item.title
return json.dumps(result)
self.inner_functions["list_tasks"] = SimpleAIFunction("list_tasks",
"list all tasks in json format like {{$task_id:$task_title}...}",
list_tasks)
def get_inner_function_desc(self) -> List[AIFunction]:
func_list = []
func_list.extend(self.inner_functions.values())
return func_list
def get_actions_for_task_review(self) -> Dict:
return self.actions
async def list_task(parameters):
_workspace = parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
all_task = await _workspace.task_mgr.list_task(None)
if all_task:
return json.dumps([task.to_dict() for task in all_task])
else :
return "no task"
list_task_ai_function = SimpleAIFunction("agent.workspace.list_task",
"list all tasks in json format",
list_task,{})
GlobaToolsLibrary.get_instance().register_tool_function(list_task_ai_function)
+2 -2
View File
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
class AsrFunction(AIFunction):
def __init__(self):
self.func_id = "aigc.speech_to_text"
self.func_id = "aigc.voice_to_text"
self.description = "Voice recognition, convert the voice into text"
self.parameters = ParameterDefine.create_parameters({
"audio_file": {"type": "string", "description": "Audio file path"},
@@ -22,7 +22,7 @@ class AsrFunction(AIFunction):
def register_function(self):
GlobaToolsLibrary.get_instance().register_tool_function(self)
def get_name(self) -> str:
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
@@ -9,8 +9,8 @@ from duckduckgo_search import AsyncDDGS
class DuckDuckGoTextSearchFunction(AIFunction):
def __init__(self):
self.name = "duckduckgo_text_search"
self.description = "Search text from duckduckgo.com"
self.name = "web.search.duckduckgo"
self.description = "Search web by text (powered by DuckDuckGo)"
self.region = "wt-wt"
self.safesearch = "moderate"
self.time = "y"
@@ -22,7 +22,7 @@ class DuckDuckGoTextSearchFunction(AIFunction):
def register_function(self):
GlobaToolsLibrary.get_instance().register_tool_function(self)
def get_name(self) -> str:
def get_id(self) -> str:
return self.name
def get_description(self) -> str:
@@ -21,7 +21,7 @@ class Image2TextFunction(AIFunction):
def register_function(self):
GlobaToolsLibrary.get_instance().register_tool_function(self)
def get_name(self) -> str:
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
@@ -45,7 +45,7 @@ class ScriptToSpeechFunction(AIFunction):
Path(self.speech_path).mkdir(exist_ok=True)
def get_name(self) -> str:
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
@@ -26,7 +26,7 @@ class GetTableInfosFunction(AIFunction):
self.name = "get_table_infos"
self.description = "Get table informations in the database"
def get_name(self) -> str:
def get_id(self) -> str:
return self.name
def get_description(self) -> str:
@@ -74,7 +74,7 @@ class ExecuteSqlFunction(AIFunction):
If an error is returned, rewrite the query, check the query, and try again.
"""
def get_name(self) -> str:
def get_id(self) -> str:
return self.name
def get_description(self) -> str:
@@ -27,7 +27,7 @@ class TextToSpeechFunction(AIFunction):
})
Path(self.speech_path).mkdir(exist_ok=True)
def get_name(self) -> str:
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
+2 -2
View File
@@ -73,7 +73,7 @@ class SimpleEnvironment(BaseEnvironment):
self.operations: Dict[str,AIAction] = {}
def add_ai_function(self,func:AIFunction) -> None:
self.functions[func.get_name()] = func
self.functions[func.get_id()] = func
def get_ai_function(self,func_name:str) -> AIFunction:
func = self.functions.get(func_name)
@@ -111,7 +111,7 @@ class CompositeEnvironment(SimpleEnvironment):
self.envs.append(env)
functions = env.get_all_ai_functions()
for func in functions:
self.functions[func.get_name()] = func
self.functions[func.get_id()] = func
operations = env.get_all_ai_operations()
for op in operations:
self.operations[op.get_name()] = op
+15 -5
View File
@@ -21,6 +21,13 @@ class ParameterDefine:
class AIFunction:
@abstractmethod
def get_id(self) -> str:
"""
return the id of the function (should be snake case)
"""
pass
@abstractmethod
def get_name(self) -> str:
"""
@@ -140,7 +147,7 @@ class AIFunction:
return {"type": "object", "properties": {}}
@abstractmethod
async def execute(self, **kwargs) -> str:
async def execute(self, arguments:Dict) -> str:
"""
Execute the function and return a JSON serializable dict by LLM
The parameters are passed in the form of kwargs
@@ -210,20 +217,23 @@ class SimpleAIFunction(AIFunction):
self.func_handler = func_handler
self.parameters:Dict[str,ParameterDefine] = parameters
def get_name(self) -> str:
def get_id(self) -> str:
return self.func_id
def get_name(self) -> str:
return self.func_id.split('.')[-1].strip()
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict[str,ParameterDefine]:
return self.parameters
async def execute(self,**kwargs) -> str:
async def execute(self,parameters:Dict) -> str:
if self.func_handler is None:
return f"error: function {self.func_id} not implemented"
return await self.func_handler(**kwargs)
return await self.func_handler(parameters)
def is_local(self) -> bool:
return True
@@ -284,7 +294,7 @@ class AIFunction2Action(AIAction):
@abstractmethod
def get_name(self) -> str:
return self.func.get_name()
return self.func.get_id()
@abstractmethod
def get_description(self) -> str: