2023-12-06 13:31:05 -08:00
|
|
|
# Old name is behavior, I belive new name "llm_process" is better
|
2023-12-17 18:23:40 -08:00
|
|
|
# pylint:disable=E0402
|
2024-02-08 17:39:46 +08:00
|
|
|
import os.path
|
|
|
|
|
|
2024-02-17 13:37:03 +08:00
|
|
|
from .chatsession import AIChatSession
|
2023-12-17 18:23:40 -08:00
|
|
|
from ..utils import video_utils,image_utils
|
|
|
|
|
|
|
|
|
|
from ..proto.compute_task import LLMPrompt,LLMResult,ComputeTaskResult,ComputeTaskResultCode
|
|
|
|
|
from ..proto.ai_function import AIFunction,AIAction,ActionNode
|
|
|
|
|
from ..proto.agent_msg import AgentMsg,AgentMsgType
|
|
|
|
|
|
|
|
|
|
from .agent_memory import AgentMemory
|
|
|
|
|
from .workspace import AgentWorkspace
|
|
|
|
|
from .llm_context import LLMProcessContext,GlobaToolsLibrary, SimpleLLMContext
|
|
|
|
|
|
|
|
|
|
from ..frame.compute_kernel import ComputeKernel
|
2024-04-23 04:56:22 -07:00
|
|
|
from ..knowledge.knowledge_base import BaseKnowledgeGraph
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
from abc import ABC,abstractmethod
|
|
|
|
|
import copy
|
|
|
|
|
import json
|
2023-12-17 18:23:40 -08:00
|
|
|
import datetime
|
|
|
|
|
from datetime import datetime
|
2023-12-09 18:39:42 -08:00
|
|
|
from typing import Any, Callable, Coroutine, Optional,Dict,Awaitable,List
|
2023-12-06 13:31:05 -08:00
|
|
|
from enum import Enum
|
|
|
|
|
import logging
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
MIN_PREDICT_TOKEN_LEN = 32
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
class BaseLLMProcess(ABC):
|
2023-12-06 13:31:05 -08:00
|
|
|
def __init__(self) -> None:
|
2023-12-09 18:39:42 -08:00
|
|
|
self.behavior:str = None #行为名字
|
|
|
|
|
self.goal:str = None #目标
|
|
|
|
|
self.input_example:str= None #输入样例
|
|
|
|
|
self.result_example:str = None #llm_result样例
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
self.enable_json_resp = False
|
2023-12-17 18:23:40 -08:00
|
|
|
#None means system default,
|
|
|
|
|
# TODO: support abcstract model name like: local-hight,local-low,local-medium,remote-hight,remote-low,remote-medium
|
2024-02-08 17:39:46 +08:00
|
|
|
self.model_name = None
|
2024-03-20 18:47:58 -07:00
|
|
|
self.max_token = 2000 # result_token
|
|
|
|
|
self.max_prompt_token = 2000 # not include input prompt
|
|
|
|
|
self.chat_summary_token_len = 500
|
2023-12-06 13:31:05 -08:00
|
|
|
self.timeout = 1800 # 30 min
|
|
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
self.llm_context:LLMProcessContext = None
|
|
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
def get_llm_model_name(self) -> str:
|
|
|
|
|
return self.model_name
|
|
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
@abstractmethod
|
2023-12-09 18:39:42 -08:00
|
|
|
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
|
2023-12-06 13:31:05 -08:00
|
|
|
pass
|
|
|
|
|
|
2024-01-06 13:08:41 -08:00
|
|
|
@abstractmethod
|
2023-12-17 18:23:40 -08:00
|
|
|
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
|
2024-01-06 13:08:41 -08:00
|
|
|
pass
|
2023-12-06 13:31:05 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
@abstractmethod
|
|
|
|
|
def prepare_inner_function_context_for_exec(self,inner_func_name:str,parameters:Dict):
|
2024-02-08 17:39:46 +08:00
|
|
|
return
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
@abstractmethod
|
2023-12-17 18:23:40 -08:00
|
|
|
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
|
2023-12-09 18:39:42 -08:00
|
|
|
pass
|
|
|
|
|
|
2024-03-20 18:47:58 -07:00
|
|
|
def get_remain_prompt_length(self,prompt:LLMPrompt,will_append_str:str) -> int:
|
|
|
|
|
return self.max_prompt_token - ComputeKernel.llm_num_tokens(prompt,self.model_name)
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
@abstractmethod
|
|
|
|
|
async def load_from_config(self,config:dict) -> bool:
|
|
|
|
|
#self.behavior = config.get("behavior")
|
|
|
|
|
#self.goal = config.get("goal")
|
|
|
|
|
self.input_example = config.get("input_example")
|
|
|
|
|
self.result_example = config.get("result_example")
|
|
|
|
|
|
|
|
|
|
if config.get("model_name"):
|
|
|
|
|
self.model_name = config.get("model_name")
|
|
|
|
|
if config.get("enable_json_resp"):
|
|
|
|
|
self.enable_json_resp = config.get("enable_json_resp") == "true"
|
|
|
|
|
if config.get("max_token"):
|
|
|
|
|
self.max_token = config.get("max_token")
|
|
|
|
|
if config.get("timeout"):
|
|
|
|
|
self.timeout = config.get("timeout")
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
|
|
|
|
|
return True
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
@abstractmethod
|
|
|
|
|
async def initial(self,params:Dict = None) -> bool:
|
|
|
|
|
pass
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
def _format_content_by_env_value(self,content:str,env)->str:
|
|
|
|
|
return content.format_map(env)
|
|
|
|
|
|
2024-01-05 20:02:58 -08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
async def _execute_inner_func(self,inner_func_call_node:Dict,prompt: LLMPrompt,stack_limit = 1) -> ComputeTaskResult:
|
2023-12-06 13:31:05 -08:00
|
|
|
arguments = None
|
2023-12-10 21:42:23 -08:00
|
|
|
stack_limit = stack_limit - 1
|
2023-12-06 13:31:05 -08:00
|
|
|
try:
|
|
|
|
|
func_name = inner_func_call_node.get("name")
|
|
|
|
|
arguments = json.loads(inner_func_call_node.get("arguments"))
|
2024-01-25 01:13:40 -08:00
|
|
|
logger.info(f"LLMProcess execute inner func:{func_name} :({json.dumps(arguments,ensure_ascii=False)})")
|
2023-12-06 13:31:05 -08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
func_node : AIFunction = await self.get_inner_function_for_exec(func_name)
|
2023-12-06 13:31:05 -08:00
|
|
|
if func_node is None:
|
|
|
|
|
result_str:str = f"execute {func_name} error,function not found"
|
|
|
|
|
else:
|
2024-01-05 20:02:58 -08:00
|
|
|
self.prepare_inner_function_context_for_exec(func_name,arguments)
|
|
|
|
|
result_str:str = await func_node.execute(arguments)
|
2023-12-06 13:31:05 -08:00
|
|
|
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}")
|
|
|
|
|
|
|
|
|
|
logger.info("LLMProcess execute inner func result:" + result_str)
|
|
|
|
|
|
|
|
|
|
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
|
|
|
|
|
if self.enable_json_resp:
|
|
|
|
|
resp_mode = "json"
|
|
|
|
|
else:
|
|
|
|
|
resp_mode = "text"
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
max_result_token = self.max_token - ComputeKernel.llm_num_tokens(prompt,self.model_name)
|
2023-12-06 13:31:05 -08:00
|
|
|
if max_result_token < MIN_PREDICT_TOKEN_LEN:
|
|
|
|
|
task_result = ComputeTaskResult()
|
|
|
|
|
task_result.result_code = ComputeTaskResultCode.ERROR
|
|
|
|
|
task_result.error_str = f"prompt too long,can not predict"
|
|
|
|
|
return task_result
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-10 21:42:23 -08:00
|
|
|
if stack_limit > 0:
|
|
|
|
|
inner_functions=prompt.inner_functions
|
|
|
|
|
else:
|
|
|
|
|
inner_functions = None
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
task_result: ComputeTaskResult = await (ComputeKernel.get_instance().do_llm_completion(
|
|
|
|
|
prompt,
|
|
|
|
|
resp_mode=resp_mode,
|
2024-02-04 17:28:25 -08:00
|
|
|
mode_name=self.get_llm_model_name(),
|
2023-12-06 13:31:05 -08:00
|
|
|
max_token=max_result_token,
|
2023-12-10 21:42:23 -08:00
|
|
|
inner_functions=inner_functions, #NOTICE: inner_function in prompt can be a subset of get_inner_function
|
2023-12-06 13:31:05 -08:00
|
|
|
timeout=self.timeout))
|
|
|
|
|
|
|
|
|
|
if task_result.result_code != ComputeTaskResultCode.OK:
|
|
|
|
|
logger.error(f"llm compute error:{task_result.error_str}")
|
|
|
|
|
return task_result
|
|
|
|
|
|
|
|
|
|
inner_func_call_node = None
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-10 21:42:23 -08:00
|
|
|
result_message : dict = task_result.result.get("message")
|
|
|
|
|
if result_message:
|
|
|
|
|
inner_func_call_node = result_message.get("function_call")
|
|
|
|
|
if inner_func_call_node:
|
|
|
|
|
func_msg = copy.deepcopy(result_message)
|
|
|
|
|
del func_msg["tool_calls"]#TODO: support tool_calls?
|
|
|
|
|
prompt.messages.append(func_msg)
|
|
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
|
|
|
|
|
if inner_func_call_node:
|
|
|
|
|
return await self._execute_inner_func(inner_func_call_node,prompt,stack_limit-1)
|
|
|
|
|
else:
|
|
|
|
|
return task_result
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
async def process(self,input:Dict) -> LLMResult:
|
2023-12-06 13:31:05 -08:00
|
|
|
if self.enable_json_resp:
|
|
|
|
|
resp_mode = "json"
|
|
|
|
|
else:
|
|
|
|
|
resp_mode = "text"
|
|
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
# Action define in prompt, will be execute after llm compute
|
2023-12-09 18:39:42 -08:00
|
|
|
prompt = await self.prepare_prompt(input)
|
2024-03-20 18:47:58 -07:00
|
|
|
if prompt is None:
|
|
|
|
|
logger.warn(f"prepare_prompt return None, break llm_process")
|
|
|
|
|
return LLMResult.from_error_str("prepare_prompt return None")
|
|
|
|
|
|
2024-02-17 13:37:03 +08:00
|
|
|
max_result_token = self.max_token - ComputeKernel.llm_num_tokens(prompt,self.get_llm_model_name())
|
2024-01-15 22:52:13 -08:00
|
|
|
#if max_result_token < MIN_PREDICT_TOKEN_LEN:
|
|
|
|
|
# return LLMResult.from_error_str(f"prompt too long,can not predict")
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
task_result: ComputeTaskResult = await (ComputeKernel.get_instance().do_llm_completion(
|
|
|
|
|
prompt,
|
|
|
|
|
resp_mode=resp_mode,
|
2024-02-04 17:28:25 -08:00
|
|
|
mode_name=self.get_llm_model_name(),
|
2023-12-06 13:31:05 -08:00
|
|
|
max_token=max_result_token,
|
2023-12-09 18:39:42 -08:00
|
|
|
inner_functions=prompt.inner_functions, #NOTICE: inner_function in prompt can be a subset of get_inner_function
|
2023-12-06 13:31:05 -08:00
|
|
|
timeout=self.timeout))
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
if task_result.result_code != ComputeTaskResultCode.OK:
|
|
|
|
|
err_str = f"do_llm_completion error:{task_result.error_str}"
|
|
|
|
|
logger.error(err_str)
|
|
|
|
|
return LLMResult.from_error_str(err_str)
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-06 13:31:05 -08:00
|
|
|
result_message = task_result.result.get("message")
|
|
|
|
|
inner_func_call_node = None
|
|
|
|
|
if result_message:
|
|
|
|
|
inner_func_call_node = result_message.get("function_call")
|
|
|
|
|
|
|
|
|
|
if inner_func_call_node:
|
|
|
|
|
call_prompt : LLMPrompt = copy.deepcopy(prompt)
|
|
|
|
|
func_msg = copy.deepcopy(result_message)
|
|
|
|
|
del func_msg["tool_calls"]
|
|
|
|
|
call_prompt.messages.append(func_msg)
|
|
|
|
|
task_result = await self._execute_inner_func(inner_func_call_node,call_prompt)
|
|
|
|
|
|
|
|
|
|
# parse task_result to LLM Result
|
|
|
|
|
if self.enable_json_resp:
|
2024-02-17 13:37:03 +08:00
|
|
|
try:
|
|
|
|
|
llm_result = LLMResult.from_json_str(task_result.result_str)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"parse llm result error:{e}")
|
|
|
|
|
llm_result = LLMResult.from_str(task_result.result_str)
|
2023-12-06 13:31:05 -08:00
|
|
|
else:
|
|
|
|
|
llm_result = LLMResult.from_str(task_result.result_str)
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
# use action to save history?
|
2023-12-17 18:23:40 -08:00
|
|
|
await self.post_llm_process(llm_result.action_list,input,llm_result)
|
2023-12-06 13:31:05 -08:00
|
|
|
|
|
|
|
|
return llm_result
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
class LLMAgentBaseProcess(BaseLLMProcess):
|
2023-12-09 18:39:42 -08:00
|
|
|
def __init__(self) -> None:
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
self.role_description:str = None
|
|
|
|
|
self.process_description:str = None
|
|
|
|
|
self.reply_format:str = None
|
|
|
|
|
self.context : str = None
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
self.workspace : AgentWorkspace = None # If Workspace is not none , enable Agent Tasklist
|
|
|
|
|
self.memory : AgentMemory = None
|
2024-04-23 04:56:22 -07:00
|
|
|
self.enable_kb_list : List[str] = None
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
async def initial(self,params:Dict = None) -> bool:
|
|
|
|
|
self.memory = params.get("memory")
|
|
|
|
|
if self.memory is None:
|
|
|
|
|
logger.error(f"LLMAgeMessageProcess initial failed! memory not found")
|
|
|
|
|
return False
|
|
|
|
|
self.workspace = params.get("workspace")
|
|
|
|
|
|
|
|
|
|
return True
|
2023-12-17 18:23:40 -08:00
|
|
|
async def load_default_config(self) -> bool:
|
|
|
|
|
return True
|
2024-02-08 17:39:46 +08:00
|
|
|
|
|
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
|
|
|
|
|
if is_load_default:
|
|
|
|
|
await self.load_default_config()
|
|
|
|
|
|
|
|
|
|
if await super().load_from_config(config) is False:
|
|
|
|
|
return False
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
self.role_description = config.get("role_desc")
|
|
|
|
|
if self.role_description is None:
|
|
|
|
|
logger.error(f"role_description not found in config")
|
|
|
|
|
return False
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
if config.get("process_description"):
|
|
|
|
|
self.process_description = config.get("process_description")
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
if config.get("reply_format"):
|
|
|
|
|
self.reply_format = config.get("reply_format")
|
|
|
|
|
|
|
|
|
|
if config.get("context"):
|
|
|
|
|
self.context = config.get("context")
|
|
|
|
|
|
2024-04-23 04:56:22 -07:00
|
|
|
if config.get("knowledge_grpah_introduce"):
|
|
|
|
|
self.knowledge_grpah_introduce = config.get("knowledge_grpah_introduce")
|
|
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
self.llm_context = SimpleLLMContext()
|
|
|
|
|
if config.get("llm_context"):
|
|
|
|
|
self.llm_context.load_from_config(config.get("llm_context"))
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-04-23 04:56:22 -07:00
|
|
|
def prepare_knowledge_grpah_prompt(self) -> Dict:
|
|
|
|
|
result = {}
|
|
|
|
|
|
|
|
|
|
result["introduce"] = BaseKnowledgeGraph.get_kb_default_desc_str()
|
|
|
|
|
result["knowledge_graph_list"] = {}
|
|
|
|
|
have_kb = False
|
|
|
|
|
|
|
|
|
|
if self.memory.enable_knowledge_graph:
|
|
|
|
|
result["knowledge_graph_list"][self.memory.knowledge_graph.kb_id] = self.memory.knowledge_graph.get_description()
|
|
|
|
|
have_kb = True
|
|
|
|
|
|
|
|
|
|
if self.enable_kb_list:
|
|
|
|
|
for kb_id in self.enable_kb_list:
|
|
|
|
|
kb = BaseKnowledgeGraph.get_kb(kb_id)
|
|
|
|
|
if kb:
|
|
|
|
|
have_kb = True
|
|
|
|
|
result["knowledge_graph_list"][kb_id] = kb.get_description()
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"knowledge base {kb_id} not found")
|
|
|
|
|
|
|
|
|
|
if have_kb is False:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
def prepare_role_system_prompt(self,context_info:Dict) -> Dict:
|
|
|
|
|
system_prompt_dict = {}
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-04-23 04:56:22 -07:00
|
|
|
system_prompt_dict["role_description"] = self.role_description
|
2024-01-07 12:44:47 -08:00
|
|
|
system_prompt_dict["process_rule"] = self.process_description
|
|
|
|
|
system_prompt_dict["reply_format"] = self.reply_format
|
2024-04-23 04:56:22 -07:00
|
|
|
|
|
|
|
|
kb_prompt = self.prepare_knowledge_grpah_prompt()
|
|
|
|
|
if kb_prompt:
|
|
|
|
|
system_prompt_dict["knowledge_graph"] = kb_prompt
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
## Context
|
2024-01-17 20:34:39 -08:00
|
|
|
if self.context:
|
|
|
|
|
context = self._format_content_by_env_value(self.context,context_info)
|
|
|
|
|
system_prompt_dict["context"] = context
|
|
|
|
|
#prompt.append_system_message(context)
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
system_prompt_dict["support_actions"] = self.get_action_desc()
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
return system_prompt_dict
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
def prepare_inner_function_context_for_exec(self,inner_func_name:str,parameters:Dict):
|
2024-02-08 17:39:46 +08:00
|
|
|
parameters["_workspace"] = self.workspace
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
def get_action_desc(self) -> Dict:
|
|
|
|
|
result = {}
|
2024-04-23 04:56:22 -07:00
|
|
|
actions_list = []
|
|
|
|
|
|
|
|
|
|
actions_list.extend(self.llm_context.get_all_ai_action())
|
|
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
for action in actions_list:
|
|
|
|
|
result[action.get_name()] = action.get_description()
|
2024-04-23 04:56:22 -07:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
return result
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
|
|
|
|
|
return self.llm_context.get_ai_function(func_name)
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
async def _execute_actions(self,actions:List[ActionNode],action_params:Dict):
|
|
|
|
|
for action_item in actions:
|
|
|
|
|
op : AIAction = self.llm_context.get_ai_action(action_item.name)
|
|
|
|
|
if op:
|
|
|
|
|
if action_item.parms is None:
|
|
|
|
|
action_item.parms = {}
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
real_parms = {**action_params,**action_item.parms}
|
2023-12-10 21:42:23 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
action_item.parms["_result"] = await op.execute(real_parms)
|
|
|
|
|
action_item.parms["_end_at"] = datetime.now()
|
|
|
|
|
else:
|
|
|
|
|
logger.warn(f"action {action_item.name} not found")
|
|
|
|
|
return False
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
class AgentMessageProcess(LLMAgentBaseProcess):
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
super().__init__()
|
2024-02-04 17:28:25 -08:00
|
|
|
self.mutil_model = None
|
|
|
|
|
self.enable_media2text = False
|
|
|
|
|
self.is_mutil_model = False
|
2024-02-08 17:39:46 +08:00
|
|
|
self.asr_model = None
|
|
|
|
|
self.tts_model = None
|
2023-12-09 18:39:42 -08:00
|
|
|
|
|
|
|
|
async def load_default_config(self) -> bool:
|
|
|
|
|
return True
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
|
|
|
|
|
if is_load_default:
|
|
|
|
|
await self.load_default_config()
|
|
|
|
|
|
|
|
|
|
if await super().load_from_config(config) is False:
|
|
|
|
|
return False
|
|
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
self.enable_media2text = config.get('enable_media2text', 'false').lower() in ('true', '1', 't', 'y', 'yes')
|
|
|
|
|
|
|
|
|
|
if config.get("mutil_model"):
|
|
|
|
|
self.mutil_model = config.get("mutil_model")
|
2024-02-08 17:39:46 +08:00
|
|
|
|
|
|
|
|
self.asr_model = config.get("asr_model")
|
|
|
|
|
self.tts_model = config.get("tts_model")
|
|
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
def get_llm_model_name(self) -> str:
|
|
|
|
|
if self.is_mutil_model:
|
|
|
|
|
return self.mutil_model
|
|
|
|
|
else:
|
|
|
|
|
return self.model_name
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
def check_and_to_base64(self, image_path: str) -> str:
|
|
|
|
|
if image_utils.is_file(image_path):
|
|
|
|
|
return image_utils.to_base64(image_path, (1024, 1024))
|
|
|
|
|
else:
|
|
|
|
|
return image_path
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
async def get_prompt_from_msg(self,msg:AgentMsg) -> LLMPrompt:
|
|
|
|
|
msg_prompt = LLMPrompt()
|
2024-02-04 17:28:25 -08:00
|
|
|
self.is_mutil_model = False
|
2024-02-08 17:39:46 +08:00
|
|
|
if msg.is_image_msg():
|
2024-02-04 17:28:25 -08:00
|
|
|
if self.enable_media2text:
|
|
|
|
|
logger.error(f"enable_media2text is not supported yet")
|
2023-12-09 18:39:42 -08:00
|
|
|
else:
|
2024-02-04 17:28:25 -08:00
|
|
|
image_prompt, images = msg.get_image_body()
|
|
|
|
|
if image_prompt is None:
|
|
|
|
|
msg_prompt.messages = [{"role": "user", "content": [{"type": "image_url", "image_url": {"url": self.check_and_to_base64(image)}} for image in images]}]
|
|
|
|
|
else:
|
|
|
|
|
content = [{"type": "text", "text": image_prompt}]
|
|
|
|
|
content.extend([{"type": "image_url", "image_url": {"url": self.check_and_to_base64(image)}} for image in images])
|
|
|
|
|
msg_prompt.messages = [{"role": "user", "content": content}]
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
if self.mutil_model:
|
|
|
|
|
self.is_mutil_model = True
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"mutil_model is not set!")
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
elif msg.is_video_msg():
|
2024-02-08 17:39:46 +08:00
|
|
|
if self.enable_media2text:
|
|
|
|
|
logger.error(f"enable_media2text is not supported yet")
|
2023-12-09 18:39:42 -08:00
|
|
|
else:
|
2024-02-08 17:39:46 +08:00
|
|
|
video_prompt, video = msg.get_video_body()
|
|
|
|
|
frames = video_utils.extract_frames(video, (1024, 1024))
|
|
|
|
|
audio_file = os.path.splitext(video)[0] + ".mp3"
|
|
|
|
|
video_utils.extract_audio(video, audio_file)
|
|
|
|
|
|
|
|
|
|
voice_content = None
|
|
|
|
|
if self.asr_model is not None:
|
|
|
|
|
resp = await (ComputeKernel.get_instance().do_speech_to_text(audio_file, model=self.asr_model, prompt=None, response_format="text"))
|
|
|
|
|
if resp.result_code == ComputeTaskResultCode.OK:
|
|
|
|
|
voice_content = resp.result_str
|
|
|
|
|
|
|
|
|
|
content = []
|
|
|
|
|
if video_prompt is not None:
|
|
|
|
|
content.append({"type": "text", "text": video_prompt})
|
|
|
|
|
if voice_content is not None and voice_content != "":
|
|
|
|
|
content.append({"type": "text", "text": f"Voice content in video:{voice_content}"})
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
content.extend([{"type": "image_url", "image_url": {"url": frame}} for frame in frames])
|
|
|
|
|
msg_prompt.messages = [{"role": "user", "content": content}]
|
2024-02-08 17:39:46 +08:00
|
|
|
if self.mutil_model:
|
|
|
|
|
self.is_mutil_model = True
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"mutil_model is not set!")
|
2023-12-09 18:39:42 -08:00
|
|
|
elif msg.is_audio_msg():
|
2024-02-08 17:39:46 +08:00
|
|
|
if self.enable_media2text:
|
|
|
|
|
logger.error(f"enable_media2text is not supported yet")
|
2023-12-09 18:39:42 -08:00
|
|
|
else:
|
2024-02-17 13:37:03 +08:00
|
|
|
prompt, audio_file = msg.get_audio_body()
|
2024-02-08 17:39:46 +08:00
|
|
|
resp = await (ComputeKernel.get_instance().do_speech_to_text(audio_file, model=self.asr_model, prompt=None, response_format="text"))
|
|
|
|
|
if resp.result_code != ComputeTaskResultCode.OK:
|
|
|
|
|
error_resp = msg.create_error_resp(resp.error_str)
|
|
|
|
|
return error_resp
|
|
|
|
|
else:
|
2024-02-17 13:37:03 +08:00
|
|
|
if prompt == "":
|
|
|
|
|
msg.body = resp.result_str
|
|
|
|
|
msg_prompt.messages = [{"role":"user","content":resp.result_str}]
|
|
|
|
|
else:
|
|
|
|
|
msg.body = f"{prompt}\nVoice content:{resp.result_str}"
|
|
|
|
|
msg_prompt.messages = [{"role":"user","content": prompt}, {"role": "user", "content": f"Voice content:{resp.result_str}"}]
|
2023-12-09 18:39:42 -08:00
|
|
|
else:
|
|
|
|
|
msg_prompt.messages = [{"role":"user","content":msg.body}]
|
|
|
|
|
|
|
|
|
|
return msg_prompt
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
async def sender_info(self,msg:AgentMsg)->str:
|
|
|
|
|
sender_id = msg.sender
|
|
|
|
|
#TODO Is sender an agent?
|
|
|
|
|
return await self.memory.get_contact_summary(sender_id)
|
|
|
|
|
|
2024-03-20 18:47:58 -07:00
|
|
|
async def load_chatlogs(self,msg:AgentMsg,max_length_by_token:int)->str:
|
2023-12-09 18:39:42 -08:00
|
|
|
## like
|
|
|
|
|
#sender,[2023-11-1 12:00:00]
|
|
|
|
|
#content
|
2024-03-20 18:47:58 -07:00
|
|
|
return await self.memory.load_chatlogs(msg,max_length_by_token)
|
|
|
|
|
|
|
|
|
|
async def get_chat_summary(self,msg:AgentMsg)->str:
|
|
|
|
|
return await self.memory.get_chat_summary(msg)
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
|
|
|
|
|
async def get_extend_known_info(self,msg:AgentMsg,prompt:LLMPrompt)->str:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
|
|
|
|
|
prompt = LLMPrompt()
|
2024-02-08 17:39:46 +08:00
|
|
|
# User Prompt
|
2023-12-09 18:39:42 -08:00
|
|
|
## Input Msg
|
|
|
|
|
msg : AgentMsg = input.get("msg")
|
2024-01-07 12:44:47 -08:00
|
|
|
context_info = input.get("context_info")
|
2023-12-09 18:39:42 -08:00
|
|
|
if msg is None:
|
|
|
|
|
logger.error(f"LLMAgeMessageProcess prepare_prompt failed! input msg not found")
|
|
|
|
|
return None
|
|
|
|
|
msg_prompt = await self.get_prompt_from_msg(msg)
|
|
|
|
|
if msg_prompt is None:
|
|
|
|
|
logger.error(f"LLMAgeMessageProcess prepare_prompt failed! get_prompt_from_msg return None")
|
|
|
|
|
return None
|
|
|
|
|
prompt.append(msg_prompt)
|
|
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
## 通用的角色相关的系统提示词
|
|
|
|
|
system_prompt_dict = self.prepare_role_system_prompt(context_info)
|
2024-02-08 17:39:46 +08:00
|
|
|
|
|
|
|
|
## 已知信息
|
2023-12-09 18:39:42 -08:00
|
|
|
known_info = {}
|
|
|
|
|
#prompt.append_system_message(self.known_info_tips)
|
|
|
|
|
### 信息发送者资料
|
|
|
|
|
known_info["sender_info"] = await self.sender_info(msg)
|
|
|
|
|
#prompt.append_system_message(await self.sender_info(self,msg))
|
2024-03-20 18:47:58 -07:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
system_prompt_dict["known_info"] = known_info
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-01-05 20:02:58 -08:00
|
|
|
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
|
2023-12-10 21:42:23 -08:00
|
|
|
if self.workspace:
|
2023-12-17 18:23:40 -08:00
|
|
|
#TODO eanble workspace functions?
|
|
|
|
|
logger.info(f"workspace is not none,enable workspace functions")
|
2023-12-10 21:42:23 -08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-03-20 18:47:58 -07:00
|
|
|
### 根据Token Limit加载聊天记录
|
|
|
|
|
remain_token = self.get_remain_prompt_length(prompt,json.dumps(system_prompt_dict,ensure_ascii=False))
|
|
|
|
|
chat_record,is_all = await self.load_chatlogs(msg,remain_token - self.chat_summary_token_len)
|
|
|
|
|
if chat_record:
|
|
|
|
|
if len(chat_record) > 4:
|
|
|
|
|
known_info["chat_record"] = chat_record
|
|
|
|
|
|
|
|
|
|
if not is_all :
|
|
|
|
|
### 如果出触发了Token Limit,则删除几条信息后,加载summary (summary的长度基本是固定的)
|
|
|
|
|
summary = await self.get_chat_summary(msg)
|
|
|
|
|
if summary:
|
|
|
|
|
if len(summary) > 4:
|
|
|
|
|
known_info["chat_summary"] = summary
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-03-20 18:47:58 -07:00
|
|
|
# TODO: extend known info
|
|
|
|
|
#prompt.append_system_message(await self.get_extend_known_info(msg,prompt))
|
|
|
|
|
|
|
|
|
|
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
|
2023-12-09 18:39:42 -08:00
|
|
|
return prompt
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
|
|
|
|
|
msg:AgentMsg = input.get("msg")
|
2023-12-09 18:39:42 -08:00
|
|
|
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
|
|
|
|
|
resp_msg = msg.create_group_resp_msg(self.memory.agent_id,llm_result.resp)
|
|
|
|
|
else:
|
|
|
|
|
resp_msg = msg.create_resp_msg(llm_result.resp)
|
2024-02-08 17:39:46 +08:00
|
|
|
|
2024-02-17 13:37:03 +08:00
|
|
|
if llm_result.raw_result is not None:
|
|
|
|
|
llm_result.raw_result["_resp_msg"] = resp_msg
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
action_params = {}
|
|
|
|
|
action_params["_input"] = input
|
|
|
|
|
action_params["_memory"] = self.memory
|
|
|
|
|
action_params["_workspace"] = self.workspace
|
2024-02-08 17:39:46 +08:00
|
|
|
action_params["_resp_msg"] = resp_msg
|
2024-01-07 12:44:47 -08:00
|
|
|
action_params["_llm_result"] = llm_result
|
|
|
|
|
action_params["_agentid"] = self.memory.agent_id
|
|
|
|
|
action_params["_start_at"] = datetime.now()
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
await self._execute_actions(actions,action_params)
|
2023-12-17 18:23:40 -08:00
|
|
|
|
|
|
|
|
chatsession = self.memory.get_session_from_msg(msg)
|
|
|
|
|
chatsession.append(msg)
|
2024-02-08 17:39:46 +08:00
|
|
|
chatsession.append(resp_msg)
|
2023-12-17 18:23:40 -08:00
|
|
|
|
|
|
|
|
return True
|
2024-01-05 20:02:58 -08:00
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
class AgentSelfThinking(LLMAgentBaseProcess):
|
2023-12-09 18:39:42 -08:00
|
|
|
def __init__(self) -> None:
|
|
|
|
|
super().__init__()
|
2024-03-20 18:47:58 -07:00
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
|
|
|
|
|
async def load_from_config(self, config: dict) -> Coroutine[Any, Any, bool]:
|
|
|
|
|
if await super().load_from_config(config) is False:
|
|
|
|
|
return False
|
|
|
|
|
|
2024-03-20 18:47:58 -07:00
|
|
|
async def _load_chat_history(self,token_limit:int):
|
|
|
|
|
chat_history = {}
|
|
|
|
|
session_list = AIChatSession.list_session(self.memory.agent_id ,self.memory.memory_db)
|
|
|
|
|
total_read_msg = 0
|
|
|
|
|
for session_id in session_list:
|
|
|
|
|
chatsession = AIChatSession.get_session_by_id(session_id,self.memory.memory_db)
|
|
|
|
|
session_history = {}
|
|
|
|
|
session_history["summary"] = chatsession.summary
|
|
|
|
|
session_history["id"] = chatsession.session_id
|
|
|
|
|
token_limit -= ComputeKernel.llm_num_tokens_from_text(chatsession.summary,self.model_name)
|
|
|
|
|
read_history_msg = 0
|
|
|
|
|
|
|
|
|
|
if token_limit > 8:
|
|
|
|
|
# load session chat history
|
|
|
|
|
cur_pos = chatsession.summarize_pos
|
|
|
|
|
messages = chatsession.read_history(0,cur_pos,"natural") # read
|
|
|
|
|
history_str = ""
|
|
|
|
|
for msg in messages:
|
|
|
|
|
read_history_msg += 1
|
|
|
|
|
total_read_msg += 1
|
|
|
|
|
cur_pos += 1
|
|
|
|
|
dt = datetime.fromtimestamp(float(msg.create_time))
|
|
|
|
|
formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
|
|
|
|
|
record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
|
|
|
|
|
token_limit -= ComputeKernel.llm_num_tokens_from_text(record_str,self.model_name)
|
|
|
|
|
if token_limit < 8:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
history_str = history_str + record_str
|
|
|
|
|
|
2024-04-23 04:56:22 -07:00
|
|
|
if ComputeKernel.llm_num_tokens_from_text(history_str,self.model_name) > self.chat_summary_token_len:
|
2024-03-20 18:47:58 -07:00
|
|
|
session_history["history"] = history_str
|
|
|
|
|
chat_history[session_id] = session_history
|
|
|
|
|
chatsession.summarize_pos = cur_pos
|
2023-12-17 18:23:40 -08:00
|
|
|
|
|
|
|
|
else:
|
2024-03-20 18:47:58 -07:00
|
|
|
logger.info(f"load_chat_history reach token limit,load {total_read_msg} history messages.")
|
|
|
|
|
return chat_history
|
|
|
|
|
|
|
|
|
|
if total_read_msg < 2:
|
|
|
|
|
logger.info(f"load_chat_history: no history messages,return NONE")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return chat_history
|
|
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
|
|
|
|
|
prompt = LLMPrompt()
|
|
|
|
|
|
|
|
|
|
context_info = input.get("context_info")
|
2024-03-20 18:47:58 -07:00
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
system_prompt_dict = self.prepare_role_system_prompt(context_info)
|
|
|
|
|
|
|
|
|
|
# Known_info is the SESSION summary of the existence, the current task work record summary,
|
2024-03-20 18:47:58 -07:00
|
|
|
token_remain = self.get_remain_prompt_length(prompt,json.dumps(system_prompt_dict,ensure_ascii=False))
|
|
|
|
|
chat_history = await self._load_chat_history(token_remain)
|
|
|
|
|
if chat_history is None:
|
|
|
|
|
logger.info(f"prepare_prompt: no history messages,return NONE")
|
|
|
|
|
return None
|
|
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
|
2024-03-20 18:47:58 -07:00
|
|
|
|
2024-02-04 17:28:25 -08:00
|
|
|
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
|
2024-03-20 18:47:58 -07:00
|
|
|
prompt.append_user_message(json.dumps(chat_history,ensure_ascii=False))
|
|
|
|
|
return prompt
|
2024-02-04 17:28:25 -08:00
|
|
|
|
|
|
|
|
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
|
|
|
|
|
action_params = {}
|
|
|
|
|
action_params["_input"] = input
|
|
|
|
|
action_params["_memory"] = self.memory
|
|
|
|
|
action_params["_workspace"] = self.workspace
|
|
|
|
|
action_params["_llm_result"] = llm_result
|
|
|
|
|
action_params["_agentid"] = self.memory.agent_id
|
|
|
|
|
action_params["_start_at"] = datetime.now()
|
|
|
|
|
try:
|
|
|
|
|
if await self._execute_actions(actions,action_params) is False:
|
|
|
|
|
result_str = "execute action failed!"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"execute action failed! {e}")
|
|
|
|
|
result_str = "execute action failed!,error:" + str(e)
|
|
|
|
|
|
|
|
|
|
class AgentSelfLearning(BaseLLMProcess):
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
async def load_from_config(self, config: dict) -> Coroutine[Any, Any, bool]:
|
|
|
|
|
if await super().load_from_config(config) is False:
|
|
|
|
|
return False
|
|
|
|
|
|
2023-12-09 18:39:42 -08:00
|
|
|
async def prepare_prompt(self) -> LLMPrompt:
|
|
|
|
|
prompt = LLMPrompt()
|
2024-02-08 17:39:46 +08:00
|
|
|
pass
|
2023-12-09 18:39:42 -08:00
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
|
2023-12-09 18:39:42 -08:00
|
|
|
pass
|
|
|
|
|
|
2023-12-17 18:23:40 -08:00
|
|
|
async def post_llm_process(self,actions:List[ActionNode]) -> bool:
|
2023-12-09 18:39:42 -08:00
|
|
|
pass
|
2023-12-06 13:31:05 -08:00
|
|
|
|
2024-01-07 12:44:47 -08:00
|
|
|
class AgentSelfImprove(BaseLLMProcess):
|
|
|
|
|
def __init__(self) -> None:
|
2024-02-08 17:39:46 +08:00
|
|
|
super().__init__()
|
2023-12-06 13:31:05 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|