Refactor the Action/Function components, and refactor the basic architecture of Agent Task/Todo.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
# pylint:disable=E0402
|
||||
import json
|
||||
import logging
|
||||
import shlex
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
# pylint:disable=E0402
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional
|
||||
import datetime
|
||||
|
||||
+135
-55
@@ -1,11 +1,23 @@
|
||||
# pylint:disable=E0402
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict,Coroutine,Callable,List
|
||||
|
||||
class ParameterDefine:
|
||||
def __init__(self) -> None:
|
||||
self.name = None
|
||||
self.type = None
|
||||
self.description = None
|
||||
def __init__(self,name:str,desc:str) -> None:
|
||||
self.name:str = name
|
||||
self.type:str = "string"
|
||||
self.enum:List[str] = None
|
||||
self.description = desc
|
||||
self.is_required = False
|
||||
|
||||
@classmethod
|
||||
def create_parameters(cls,json_obj:dict) -> Dict[str,'ParameterDefine']:
|
||||
result = {}
|
||||
for k,v in json_obj.items():
|
||||
param = ParameterDefine(k,v)
|
||||
result[k] = param
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class AIFunction:
|
||||
@@ -23,32 +35,125 @@ class AIFunction:
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_detail_description(self) -> str:
|
||||
"""
|
||||
return a detailed description of what the function does
|
||||
"""
|
||||
parameters = self.get_parameters()
|
||||
parameters_str = ""
|
||||
for k,v in parameters.items():
|
||||
if len(v.description) <= 0:
|
||||
parameters_str +=f"{k},"
|
||||
else:
|
||||
if v.description == k:
|
||||
parameters_str += f"{k},"
|
||||
else:
|
||||
if v.is_required:
|
||||
parameters_str += f"{k}: {v.description},"
|
||||
else:
|
||||
parameters_str += f"{k} (Optional): {v.description},"
|
||||
if len(parameters_str) > 0:
|
||||
return f"{self.get_description} Parameters: {parameters_str}"
|
||||
return f"f{self.get_description()}, no parameters"
|
||||
|
||||
@abstractmethod
|
||||
def get_parameters(self) -> Dict:
|
||||
def get_parameters(self) -> Dict[str,ParameterDefine]:
|
||||
pass
|
||||
|
||||
def get_openai_parameters(self) -> Dict:
|
||||
"""
|
||||
Return the list of parameters to execute this function in the form of
|
||||
JSON schema as specified in the OpenAI documentation:
|
||||
https://platform.openai.com/docs/api-reference/chat/create#chat/create-parameters
|
||||
|
||||
str = run_code(code:str)
|
||||
parameters = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "Python code which needs to be executed"
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
"description": "The temperature unit to use. Infer this from the users location.",
|
||||
},
|
||||
},
|
||||
"required": ["location", "format"],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_n_day_weather_forecast",
|
||||
"description": "Get an N-day weather forecast",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
"description": "The temperature unit to use. Infer this from the users location.",
|
||||
},
|
||||
"num_days": {
|
||||
"type": "integer",
|
||||
"description": "The number of days to forecast",
|
||||
}
|
||||
},
|
||||
"required": ["location", "format", "num_days"]
|
||||
},
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
"""
|
||||
pass
|
||||
parameters = self.get_parameters()
|
||||
if parameters is not None:
|
||||
result = {}
|
||||
result["type"] = "object"
|
||||
required = []
|
||||
parm_defines = {}
|
||||
for parm_name,parm in parameters.items():
|
||||
parm_item = {}
|
||||
parm_item["type"] = parm.type
|
||||
parm_item["description"] = parm.description
|
||||
if parm.enum is not None:
|
||||
parm_item["enum"] = parm.enum
|
||||
parm_defines[parm_name] = parm_item
|
||||
if parm.is_required:
|
||||
required.append(parm_name)
|
||||
result["properties"] = parm_defines
|
||||
result["required"] = required
|
||||
return result
|
||||
|
||||
return {"type": "object", "properties": {}}
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, **kwargs) -> str:
|
||||
"""
|
||||
Execute the function and return a JSON serializable dict.
|
||||
Execute the function and return a JSON serializable dict by LLM
|
||||
The parameters are passed in the form of kwargs
|
||||
|
||||
[{'id': 'call_fLsKR5vGllhbWxvpqsDT3jBj',
|
||||
'type': 'function',
|
||||
'function': {'name': 'get_n_day_weather_forecast',
|
||||
'arguments': '{"location": "San Francisco, CA", "format": "celsius", "num_days": 4}'}},
|
||||
{'id': 'call_CchlsGE8OE03QmeyFbg7pkDz',
|
||||
'type': 'function',
|
||||
'function': {'name': 'get_n_day_weather_forecast',
|
||||
'arguments': '{"location": "Glasgow", "format": "celsius", "num_days": 4}'}}
|
||||
]
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -70,10 +175,8 @@ class AIFunction:
|
||||
def is_ready_only(self) -> bool:
|
||||
pass
|
||||
|
||||
#def load_from_config(self,config:dict) -> bool:
|
||||
# pass
|
||||
|
||||
class ActionItem:
|
||||
#TODO need to be upgrade
|
||||
class ActionNode:
|
||||
def __init__(self,name:str,args:List[str]) -> None:
|
||||
self.name:str= name
|
||||
self.args:List[str]= args
|
||||
@@ -90,9 +193,9 @@ class ActionItem:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def from_json(cls,json_obj:dict) -> 'ActionItem':
|
||||
def from_json(cls,json_obj:dict) -> 'ActionNode':
|
||||
args = json_obj.get("args",[])
|
||||
r = ActionItem(json_obj["name"],args)
|
||||
r = ActionNode(json_obj["name"],args)
|
||||
if json_obj.get("body"):
|
||||
r.body = json_obj["body"]
|
||||
r.parms = json_obj
|
||||
@@ -100,23 +203,12 @@ class ActionItem:
|
||||
return r
|
||||
|
||||
|
||||
# call chain is a combination of ai_function,group of ai_function.
|
||||
class CallChain:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def load_from_config(self,config:dict) -> bool:
|
||||
pass
|
||||
|
||||
async def execute(self):
|
||||
pass
|
||||
|
||||
class SimpleAIFunction(AIFunction):
|
||||
def __init__(self,func_id:str,description:str,func_handler:Coroutine,parameters:Dict = None) -> None:
|
||||
def __init__(self,func_id:str,description:str,func_handler:Coroutine,parameters:Dict[str,ParameterDefine] = None) -> None:
|
||||
self.func_id = func_id
|
||||
self.description = description
|
||||
self.func_handler = func_handler
|
||||
self.parameters = parameters
|
||||
self.parameters:Dict[str,ParameterDefine] = parameters
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self.func_id
|
||||
@@ -124,24 +216,12 @@ class SimpleAIFunction(AIFunction):
|
||||
def get_description(self) -> str:
|
||||
return self.description
|
||||
|
||||
def get_parameters(self) -> Dict:
|
||||
if self.parameters is not None:
|
||||
result = {}
|
||||
result["type"] = "object"
|
||||
parm_defines = {}
|
||||
for parm,desc in self.parameters.items():
|
||||
parm_item = {}
|
||||
parm_item["type"] = "string"
|
||||
parm_item["description"] = desc
|
||||
parm_defines[parm] = parm_item
|
||||
result["properties"] = parm_defines
|
||||
return result
|
||||
return {"type": "object", "properties": {}}
|
||||
|
||||
|
||||
def get_parameters(self) -> Dict[str,ParameterDefine]:
|
||||
return self.parameters
|
||||
|
||||
async def execute(self,**kwargs) -> str:
|
||||
if self.func_handler is None:
|
||||
return "error: function not implemented"
|
||||
return f"error: function {self.func_id} not implemented"
|
||||
|
||||
return await self.func_handler(**kwargs)
|
||||
|
||||
@@ -154,7 +234,7 @@ class SimpleAIFunction(AIFunction):
|
||||
def is_ready_only(self) -> bool:
|
||||
return False
|
||||
|
||||
class AIOperation:
|
||||
class AIAction:
|
||||
@abstractmethod
|
||||
def get_name(self) -> str:
|
||||
"""
|
||||
@@ -178,7 +258,7 @@ class AIOperation:
|
||||
"""
|
||||
pass
|
||||
|
||||
class SimpleAIOperation(AIOperation):
|
||||
class SimpleAIAction(AIAction):
|
||||
def __init__(self,op:str,description:str,func_handler:Coroutine) -> None:
|
||||
self.op = op
|
||||
self.description = description
|
||||
@@ -197,7 +277,7 @@ class SimpleAIOperation(AIOperation):
|
||||
return await self.func_handler(params)
|
||||
|
||||
|
||||
class AIFunctionOperation(AIOperation):
|
||||
class AIFunction2Action(AIAction):
|
||||
def __init__(self, func: AIFunction) -> None:
|
||||
self.func = func
|
||||
super().__init__()
|
||||
@@ -208,7 +288,7 @@ class AIFunctionOperation(AIOperation):
|
||||
|
||||
@abstractmethod
|
||||
def get_description(self) -> str:
|
||||
return self.func.get_description()
|
||||
return self.func.get_detail_description()
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, params: dict) -> str:
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
|
||||
# pylint:disable=E0402
|
||||
import copy
|
||||
from enum import Enum
|
||||
import json
|
||||
import shlex
|
||||
import uuid
|
||||
import time
|
||||
from typing import List, Union
|
||||
from .ai_function import *
|
||||
from .agent_msg import *
|
||||
from typing import List, Union,Dict
|
||||
from .ai_function import AIFunction,ActionNode
|
||||
from .agent_msg import AgentMsg
|
||||
from ..knowledge import ObjectID
|
||||
from ..storage.storage import AIStorage
|
||||
|
||||
@@ -33,13 +33,15 @@ class ComputeTaskState(Enum):
|
||||
class ComputeTaskType(Enum):
|
||||
NONE = "None"
|
||||
LLM_COMPLETION = "llm_completion"
|
||||
TEXT_EMBEDDING ="text_embedding"
|
||||
IMAGE_EMBEDDING ="image_embedding"
|
||||
|
||||
TEXT_2_IMAGE = "text_2_image"
|
||||
IMAGE_2_TEXT = "image_2_text"
|
||||
IMAGE_2_IMAGE = "image_2_image"
|
||||
VOICE_2_TEXT = "voice_2_text"
|
||||
TEXT_2_VOICE = "text_2_voice"
|
||||
TEXT_EMBEDDING ="text_embedding"
|
||||
IMAGE_EMBEDDING ="image_embedding"
|
||||
|
||||
|
||||
# class Function(TypedDict, total=False):
|
||||
# name: Required[str]
|
||||
@@ -155,11 +157,8 @@ class LLMResult:
|
||||
self.compute_error_str = None
|
||||
self.resp : str = "" # llm say:
|
||||
self.raw_result = None # raw result from compute kernel
|
||||
self.inner_functions : List[AIFunction] = []
|
||||
self.action_list : List[ActionItem] = [] # op_list is a optimize design for saving token
|
||||
|
||||
#self.post_msgs : List[AgentMsg] = [] # move to op_list
|
||||
# self.send_msgs : List[AgentMsg] = [] # move to op_list
|
||||
#self.inner_functions : List[AIFunction] = []
|
||||
self.action_list : List[ActionNode] = [] # op_list is a optimize design for saving token
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -185,9 +184,11 @@ class LLMResult:
|
||||
r.resp = llm_json.get("resp")
|
||||
r.raw_result = llm_json
|
||||
action_list = llm_json.get("actions")
|
||||
for action in action_list:
|
||||
action_item = ActionItem.from_json(action)
|
||||
r.action_list.append(action_item)
|
||||
if action_list:
|
||||
for action in action_list:
|
||||
action_item = ActionNode.from_json(action)
|
||||
if action_item:
|
||||
r.action_list.append(action_item)
|
||||
|
||||
return r
|
||||
|
||||
@@ -215,7 +216,7 @@ class LLMResult:
|
||||
lines = llm_result_str.splitlines()
|
||||
is_need_wait = False
|
||||
|
||||
def check_args(action_item:ActionItem):
|
||||
def check_args(action_item:ActionNode):
|
||||
match action_item.name:
|
||||
case "post_msg":# /post_msg $target_id
|
||||
if len(action_item.args) != 1:
|
||||
@@ -232,7 +233,7 @@ class LLMResult:
|
||||
return False
|
||||
|
||||
|
||||
current_action : ActionItem = None
|
||||
current_action : ActionNode = None
|
||||
for line in lines:
|
||||
if line.startswith("##/"):
|
||||
if current_action:
|
||||
@@ -242,7 +243,7 @@ class LLMResult:
|
||||
r.action_list.append(current_action)
|
||||
|
||||
action_name,action_args = LLMResult.parse_action(line[3:])
|
||||
current_action = ActionItem(action_name,action_args)
|
||||
current_action = ActionNode(action_name,action_args)
|
||||
else:
|
||||
if current_action:
|
||||
current_action.append_body(line + "\n")
|
||||
|
||||
Reference in New Issue
Block a user