Complete AI Function support for Agent.

This commit is contained in:
Liu Zhicong
2023-09-10 20:50:37 -07:00
parent 6b45cb9570
commit 7e1f7173b4
10 changed files with 173 additions and 39 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ The previous plan, please see here: [MVP Plan](./mvp%20plan.md)
- [ ] MPT-7B, S2 - [ ] MPT-7B, S2
- [ ] Vicuna, S2 - [ ] Vicuna, S2
- [ ] Embeding,@photosssa,@lurenpluto , A4 - [ ] Embeding,@photosssa,@lurenpluto , A4
- [ ] Txt2img,@glen0125,A4 - [x] Txt2img,@glen0125,A4
- [ ] Img2txt(0.5.2),A3 - [ ] Img2txt(0.5.2),A3
- [ ] Txt2voice,A3 - [ ] Txt2voice,A3
- [ ] Voice2txt, @wugren,A3 - [ ] Voice2txt, @wugren,A3
+55 -5
View File
@@ -5,9 +5,13 @@ import asyncio
import logging import logging
import uuid import uuid
import time import time
import json
from .agent_message import AgentMsg from .agent_message import AgentMsg
from .chatsession import AIChatSession from .chatsession import AIChatSession
from .compute_task import ComputeTaskResult
from .ai_function import AIFunction
from .environment import Environment
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -77,6 +81,7 @@ class AIAgent:
self.chat_db = None self.chat_db = None
self.unread_msg = Queue() # msg from other agent self.unread_msg = Queue() # msg from other agent
self.owner_env : Environment = None
@classmethod @classmethod
def create_from_templete(cls,templete:AIAgentTemplete, fullname:str): def create_from_templete(cls,templete:AIAgentTemplete, fullname:str):
@@ -124,14 +129,52 @@ class AIAgent:
return "text" return "text"
def _get_inner_functions(self) -> dict:
if self.owner_env is None:
return None
all_inner_function = self.owner_env.get_all_ai_functions()
if all_inner_function is None:
return None
result_func = []
for inner_func in all_inner_function:
this_func = {}
this_func["name"] = inner_func.get_name()
this_func["description"] = inner_func.get_description()
this_func["parameters"] = inner_func.get_parameters()
result_func.append(this_func)
return result_func
async def _execute_func(self,inenr_func_call_node:dict,msg_prompt:AgentPrompt) -> str:
from .compute_kernel import ComputeKernel
func_name = inenr_func_call_node.get("name")
arguments = json.loads(inenr_func_call_node.get("arguments"))
func_node : AIFunction = self.owner_env.get_ai_function(func_name)
if func_node is None:
return "execute failed,function not found"
result_str:str = await func_node.execute(**arguments)
inner_functions = self._get_inner_functions()
msg_prompt.messages.append({"role":"function","content":result_str,"name":func_name})
task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(msg_prompt,self.llm_model_name,self.max_token_size,inner_functions)
inner_func_call_node = task_result.result_message.get("function_call")
if inner_func_call_node:
return await self._execute_func(inner_func_call_node,msg_prompt)
else:
return task_result.result_str
async def _process_msg(self,msg:AgentMsg) -> AgentMsg: async def _process_msg(self,msg:AgentMsg) -> AgentMsg:
from .compute_kernel import ComputeKernel from .compute_kernel import ComputeKernel
session_topic = msg.get_sender() + "#" + msg.topic session_topic = msg.get_sender() + "#" + msg.topic
chatsession = AIChatSession.get_session(self.instance_id,session_topic,self.chat_db) chatsession = AIChatSession.get_session(self.instance_id,session_topic,self.chat_db)
prompt = AgentPrompt() prompt = AgentPrompt()
prompt.append(self.prompt) prompt.append(self.prompt)
# prompt.append(self._get_function_prompt(the_role.get_name()))
# prompt.append(self._get_knowlege_prompt(the_role.get_name())) # prompt.append(self._get_knowlege_prompt(the_role.get_name()))
prompt.append(await self._get_prompt_from_session(chatsession)) # chat context prompt.append(await self._get_prompt_from_session(chatsession)) # chat context
@@ -139,9 +182,16 @@ class AIAgent:
msg_prompt.messages = [{"role":"user","content":msg.body}] msg_prompt.messages = [{"role":"user","content":msg.body}]
prompt.append(msg_prompt) prompt.append(msg_prompt)
result = await ComputeKernel().do_llm_completion(prompt,self.llm_model_name,self.max_token_size) inner_functions = self._get_inner_functions()
final_result = result
result_type : str = self._get_llm_result_type(result) task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(prompt,self.llm_model_name,self.max_token_size,inner_functions)
final_result = task_result.result_str
inner_func_call_node = task_result.result_message.get("function_call")
if inner_func_call_node:
final_result = await self._execute_func(inner_func_call_node,msg_prompt)
result_type : str = self._get_llm_result_type(final_result)
is_ignore = False is_ignore = False
match result_type: match result_type:
+47 -4
View File
@@ -1,9 +1,9 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Dict from typing import Dict,Coroutine,Callable
class AIFunction: class AIFunction:
def __init__(self) -> None: def __init__(self) -> None:
self.intro : str = None self.description : str = None
@abstractmethod @abstractmethod
def get_name(self) -> str: def get_name(self) -> str:
@@ -17,7 +17,7 @@ class AIFunction:
""" """
return a detailed description of what the function does return a detailed description of what the function does
""" """
pass return self.description
@abstractmethod @abstractmethod
def get_parameters(self) -> Dict: def get_parameters(self) -> Dict:
@@ -25,11 +25,23 @@ class AIFunction:
Return the list of parameters to execute this function in the form of Return the list of parameters to execute this function in the form of
JSON schema as specified in the OpenAI documentation: JSON schema as specified in the OpenAI documentation:
https://platform.openai.com/docs/api-reference/chat/create#chat/create-parameters 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"
}
}
}
""" """
pass pass
@abstractmethod @abstractmethod
def execute(self, **kwargs) -> Dict: async def execute(self, **kwargs) -> str:
""" """
Execute the function and return a JSON serializable dict. Execute the function and return a JSON serializable dict.
The parameters are passed in the form of kwargs The parameters are passed in the form of kwargs
@@ -67,3 +79,34 @@ class CallChain:
async def execute(self): async def execute(self):
pass pass
class SimpleAIFunction(AIFunction):
def __init__(self,func_id:str,description:str,func_handler:Coroutine,parameters:Dict = None) -> None:
self.func_id = func_id
self.description = description
self.func_handler = func_handler
self.parameters = parameters
def get_name(self) -> str:
return self.func_id
def get_parameters(self) -> Dict:
if self.parameters is not None:
return self.parameters
return {"type": "object", "properties": {}}
async def execute(self,**kwargs) -> str:
if self.func_handler is None:
return "error: function not implemented"
return await self.func_handler(**kwargs)
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
+5 -6
View File
@@ -54,7 +54,6 @@ class ComputeKernel:
async def _run_task_loop(): async def _run_task_loop():
while True: while True:
logger.info("compute_kernel is waiting for task...")
task = await self.task_queue.get() task = await self.task_queue.get()
logger.info(f"compute_kernel get task: {task.display()}") logger.info(f"compute_kernel get task: {task.display()}")
c_node: ComputeNode = self._schedule(task) c_node: ComputeNode = self._schedule(task)
@@ -91,16 +90,16 @@ class ComputeKernel:
return True return True
# friendly interface for use: # friendly interface for use:
def llm_completion(self, prompt: AgentPrompt, mode_name: Optional[str] = None, max_token: int = 0): def llm_completion(self, prompt: AgentPrompt, mode_name: Optional[str] = None, max_token: int = 0,inner_functions = None):
# craete a llm_work_task ,push on queue's end # craete a llm_work_task ,push on queue's end
# then task_schedule would run this task.(might schedule some work_task to another host) # then task_schedule would run this task.(might schedule some work_task to another host)
task_req = ComputeTask() task_req = ComputeTask()
task_req.set_llm_params(prompt, mode_name, max_token) task_req.set_llm_params(prompt, mode_name, max_token,inner_functions)
self.run(task_req) self.run(task_req)
return task_req return task_req
async def do_llm_completion(self, prompt: AgentPrompt, mode_name: Optional[str] = None, max_token: int = 0) -> str: async def do_llm_completion(self, prompt: AgentPrompt, mode_name: Optional[str] = None, max_token: int = 0, inner_functions = None) -> str:
task_req = self.llm_completion(prompt, mode_name, max_token) task_req = self.llm_completion(prompt, mode_name, max_token,inner_functions)
async def check_timer(): async def check_timer():
check_times = 0 check_times = 0
@@ -120,6 +119,6 @@ class ComputeKernel:
await asyncio.create_task(check_timer()) await asyncio.create_task(check_timer())
if task_req.state == ComputeTaskState.DONE: if task_req.state == ComputeTaskState.DONE:
return task_req.result.result_str return task_req.result
return "error!" return "error!"
+9 -5
View File
@@ -11,7 +11,6 @@ class ComputeTaskState(Enum):
ERROR = 3 ERROR = 3
PENDING = 4 PENDING = 4
class ComputeTaskType(Enum): class ComputeTaskType(Enum):
NONE = -1 NONE = -1
LLM_COMPLETION = 0 LLM_COMPLETION = 0
@@ -36,7 +35,7 @@ class ComputeTask:
self.result = None self.result = None
self.error_str = None self.error_str = None
def set_llm_params(self, prompts, model_name, max_token_size, callchain_id=None): def set_llm_params(self, prompts, model_name, max_token_size, inner_functions = None, callchain_id=None):
self.task_type = ComputeTaskType.LLM_COMPLETION self.task_type = ComputeTaskType.LLM_COMPLETION
self.create_time = time.time() self.create_time = time.time()
self.task_id = uuid.uuid4().hex self.task_id = uuid.uuid4().hex
@@ -46,8 +45,14 @@ class ComputeTask:
self.params["model_name"] = model_name self.params["model_name"] = model_name
else: else:
self.params["model_name"] = "gpt-4-0613" self.params["model_name"] = "gpt-4-0613"
if max_token_size is None:
self.params["max_token_size"] = 4000
else:
self.params["max_token_size"] = max_token_size self.params["max_token_size"] = max_token_size
if inner_functions is not None:
self.params["inner_functions"] = inner_functions
def display(self) -> str: def display(self) -> str:
return f"ComputeTask: {self.task_id} {self.task_type} {self.state}" return f"ComputeTask: {self.task_id} {self.task_type} {self.state}"
@@ -59,9 +64,8 @@ class ComputeTaskResult:
self.callchain_id: str = None self.callchain_id: str = None
self.worker_id: str = None self.worker_id: str = None
self.result_code: int = 0 self.result_code: int = 0
self.result_str: str = None self.result_str: str = None # easy to use,can read from result
self.result_message: dict = {}
self.result: dict = {}
self.result_refers: dict = None self.result_refers: dict = None
self.pading_data: bytearray = None self.pading_data: bytearray = None
+22
View File
@@ -5,6 +5,8 @@ from abc import ABC, abstractmethod
from typing import Any, Callable, Optional,Dict,Awaitable,List from typing import Any, Callable, Optional,Dict,Awaitable,List
import logging import logging
from .ai_function import AIFunction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class EnvironmentEvent(ABC): class EnvironmentEvent(ABC):
@@ -33,6 +35,8 @@ class Environment:
# self.valid_keys:Dict[str,bool] = None # self.valid_keys:Dict[str,bool] = None
self.event_handlers:Dict[str,List[EnvironmentEventHandler]]= {} self.event_handlers:Dict[str,List[EnvironmentEventHandler]]= {}
self.functions : Dict[str,AIFunction] = {}
def get_id(self) -> str: def get_id(self) -> str:
return self.env_id return self.env_id
@@ -44,6 +48,24 @@ class Environment:
#def get_env_prompt(self) -> str: #def get_env_prompt(self) -> str:
# pass # pass
def add_ai_function(self,func:AIFunction) -> None:
if self.functions.get(func.get_name()) is not None:
logger.warn(f"add ai_function {func.get_name()} in env {self.env_id}:function already exist")
self.functions[func.get_name()] = func
def get_ai_function(self,func_name:str) -> AIFunction:
return self.functions.get(func_name)
#def enable_ai_function(self,func_name:str) -> None:
# pass
#def disable_ai_function(self,func_name:str) -> None:
# pass
def get_all_ai_functions(self) -> List[AIFunction]:
return self.functions.values()
@abstractmethod @abstractmethod
def _do_get_value(self,key:str) -> Optional[str]: def _do_get_value(self,key:str) -> Optional[str]:
pass pass
+14 -7
View File
@@ -56,28 +56,35 @@ class OpenAI_ComputeNode(ComputeNode):
logger.info(f"call openai {mode_name} prompts: {prompts}") logger.info(f"call openai {mode_name} prompts: {prompts}")
resp = openai.ChatCompletion.create(model=mode_name, resp = openai.ChatCompletion.create(model=mode_name,
messages=prompts, messages=prompts,
max_tokens=4000, functions=task.params["inner_functions"],
temperature=1.2) max_tokens=task.params["max_token_size"],
temperature=0.7) # TODO: add temperature to task params?
logger.info(f"openai response: {resp}") logger.info(f"openai response: {resp}")
result = ComputeTaskResult()
result.set_from_task(task)
status_code = resp["choices"][0]["finish_reason"] status_code = resp["choices"][0]["finish_reason"]
if status_code != "stop": match status_code:
case "function_call":
task.state = ComputeTaskState.DONE
case "stop":
task.state = ComputeTaskState.DONE
case _:
task.state = ComputeTaskState.ERROR task.state = ComputeTaskState.ERROR
task.error_str = f"The status code was {status_code}." task.error_str = f"The status code was {status_code}."
return None return None
result = ComputeTaskResult()
result.set_from_task(task)
result.worker_id = self.node_id result.worker_id = self.node_id
result.result_str = resp["choices"][0]["message"]["content"] result.result_str = resp["choices"][0]["message"]["content"]
result.result = resp["choices"][0]["message"] result.result_message = resp["choices"][0]["message"]
return result return result
def start(self): def start(self):
async def _run_task_loop(): async def _run_task_loop():
while True: while True:
logger.info("openai_node is waiting for task...")
task = await self.task_queue.get() task = await self.task_queue.get()
logger.info(f"openai_node get task: {task.display()}") logger.info(f"openai_node get task: {task.display()}")
result = self._run_task(task) result = self._run_task(task)
+3 -1
View File
@@ -13,6 +13,7 @@ from .chatsession import AIChatSession
from .role import AIRole,AIRoleGroup from .role import AIRole,AIRoleGroup
from .ai_function import CallChain from .ai_function import CallChain
from .compute_kernel import ComputeKernel from .compute_kernel import ComputeKernel
from .compute_task import ComputeTask,ComputeTaskResult,ComputeTaskState
from .bus import AIBus from .bus import AIBus
from .workflow_env import WorkflowEnvironment from .workflow_env import WorkflowEnvironment
@@ -354,7 +355,8 @@ class Workflow:
async def _do_process_msg(): async def _do_process_msg():
#TODO: send msg to agent might be better? #TODO: send msg to agent might be better?
result_str = await ComputeKernel().do_llm_completion(prompt,the_role.agent.get_llm_model_name(),the_role.agent.get_max_token_size()) task_result:ComputeTaskResult = await ComputeKernel().do_llm_completion(prompt,the_role.agent.get_llm_model_name(),the_role.agent.get_max_token_size())
result_str = task_result.result_str
result = Workflow.prase_llm_result(result_str) result = Workflow.prase_llm_result(result_str)
logger.info(f"{the_role.role_id} process {msg.sender}:{msg.body},llm str is :{result_str}") logger.info(f"{the_role.role_id} process {msg.sender}:{msg.body},llm str is :{result_str}")
for postmsg in result.post_msgs: for postmsg in result.post_msgs:
+8 -2
View File
@@ -7,9 +7,11 @@ import threading
import logging import logging
from typing import Optional from typing import Optional
from .environment import Environment,EnvironmentEvent from .environment import Environment,EnvironmentEvent
from .ai_function import SimpleAIFunction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CalenderEvent(EnvironmentEvent): class CalenderEvent(EnvironmentEvent):
def __init__(self,data) -> None: def __init__(self,data) -> None:
super().__init__() super().__init__()
@@ -17,7 +19,7 @@ class CalenderEvent(EnvironmentEvent):
self.data = data self.data = data
def display(self) -> str: def display(self) -> str:
return f"#event timer:{self.event_data}" return f"#event timer:{self.data}"
# AI Calender GOAL: Let user use "create notify after 2 days" to create a timer event # AI Calender GOAL: Let user use "create notify after 2 days" to create a timer event
class CalenderEnvironment(Environment): class CalenderEnvironment(Environment):
@@ -25,6 +27,10 @@ class CalenderEnvironment(Environment):
super().__init__(env_id) super().__init__(env_id)
self.is_run = False self.is_run = False
self.add_ai_function(SimpleAIFunction("get_time",
"get current time",
self.get_now))
def _do_get_value(self,key:str) -> Optional[str]: def _do_get_value(self,key:str) -> Optional[str]:
return None return None
@@ -52,7 +58,7 @@ class CalenderEnvironment(Environment):
def stop(self): def stop(self):
self.is_run = False self.is_run = False
def get_now(self,key:str) -> str: async def get_now(self) -> str:
now = datetime.now() now = datetime.now()
formatted_time = now.strftime('%Y-%m-%d %H:%M:%S') formatted_time = now.strftime('%Y-%m-%d %H:%M:%S')
return formatted_time return formatted_time
+1
View File
@@ -44,6 +44,7 @@ class AIOS_Shell:
target_id = msg.target.split(".")[0] target_id = msg.target.split(".")[0]
agent : AIAgent = await AgentManager().get(target_id) agent : AIAgent = await AgentManager().get(target_id)
if agent is not None: if agent is not None:
agent.owner_env = Environment.get_env_by_id("calender")
bus.register_message_handler(target_id,agent._process_msg) bus.register_message_handler(target_id,agent._process_msg)
return True return True