Merge branch 'MVP'
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
# agent memory
|
||||||
|
|
||||||
|
## memory的基本形式
|
||||||
|
memory的基本形式上是 topic+内容
|
||||||
|
topic用一个有意义的路径表示 /xxx/xxx/xxx (有点类似脑图的逻辑,可以通过逐级展开遍历浏览所有的memory)
|
||||||
|
同一个memory可以被多个路径指向
|
||||||
|
内容则是一个json文件
|
||||||
|
|
||||||
|
|
||||||
|
## Agent 使用memory的
|
||||||
|
1. 根据当前会话的主题,尝试在known_info中加载必要的memory
|
||||||
|
2. 提供memory的 list/查询 函数, 允许agent在必要的时候 list / 查询memory
|
||||||
|
该使用逻辑的本质和kb查询逻辑很像
|
||||||
|
|
||||||
|
## Agent 更新/创建memory
|
||||||
|
1. 在任何llm process的过程中,agent都可以用写文件的形式创建memory
|
||||||
|
2. 更新memory通常是一个专门的 self-think过程,agent此时会用某种模式整理自己所有的logs和memory,并对memory进行更新、创建、删除
|
||||||
|
该更新逻辑与Agent 与KB的Self-learning逻辑很像。但根据log->summary的过程基本上是 self-think独有的
|
||||||
|
|
||||||
|
## 实现逻辑
|
||||||
|
基本思路:
|
||||||
|
1. 核心API是一组通用的文件操作API(有些场景可以是只读的) + 一组特化的对象查询API
|
||||||
|
路径->Object,Object中包含ObjectId等信息
|
||||||
|
ObjectId->Object
|
||||||
|
Object一定是一个json,里面包含可以打开原始文件的路径(fileId)
|
||||||
|
2. 通过一组文件系统描述来引导Agent操作特定文件
|
||||||
|
3. 通过一组搜索API来引导Agent操作特定文件
|
||||||
|
|
||||||
|
对象查询API,基本思路是
|
||||||
|
|
||||||
|
ObjectId->Object
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ Let's start by introducing the two important processes.
|
|||||||
Note that the dependency check during installation allows for the missing packages to be installed into the current environment.
|
Note that the dependency check during installation allows for the missing packages to be installed into the current environment.
|
||||||
|
|
||||||
# Some Basic Concepts
|
# Some Basic Concepts
|
||||||
|
|
||||||
- ***env***:A target environment consisting of a series of configuration files, where packages can be loaded/installed.
|
- ***env***:A target environment consisting of a series of configuration files, where packages can be loaded/installed.
|
||||||
- ***pkg***:A Package(pkg) is either a folder or a file that serves the same purpose as a folder (such as zip, iso, etc.).
|
- ***pkg***:A Package(pkg) is either a folder or a file that serves the same purpose as a folder (such as zip, iso, etc.).
|
||||||
- ***pkg_name***:A unique string used to label a package. It's usually a readable package name, but can also include the version number or even the ContentId.
|
- ***pkg_name***:A unique string used to label a package. It's usually a readable package name, but can also include the version number or even the ContentId.
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# prompts
|
||||||
|
|
||||||
|
四个循环
|
||||||
|
1. 立刻处理消息+深度思考整理循环
|
||||||
|
Process <-> Self Thinking
|
||||||
|
|
||||||
|
2. 任务迭代完整循环致力于完成所有的待完成任务(不一定是成功完成)
|
||||||
|
Task -> Todo -> Check
|
||||||
|
|
||||||
|
3. 知识库整理
|
||||||
|
New Knowledge -> Self-Learning
|
||||||
|
使用知识库的时机?是否有quick process和deep thing的区别?
|
||||||
|
|
||||||
|
4. Self-Improve
|
||||||
|
根据四元组:输入,提示词,输出,上级意见 (可选),对提示词进行改进
|
||||||
@@ -12,6 +12,28 @@ Your name is Jarvis, the super personal assistant to the Principal. Help the Pri
|
|||||||
Only clearly specifying the task you completed can be completed independently.
|
Only clearly specifying the task you completed can be completed independently.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
kb_query_desc = """
|
||||||
|
$ introduce (have default config)
|
||||||
|
$ dir descriptions
|
||||||
|
$ dir1
|
||||||
|
$ dir2
|
||||||
|
$ dir3
|
||||||
|
$ support actions(if enable)
|
||||||
|
$ support funcitons(if enable)
|
||||||
|
|
||||||
|
现有信息以知识图谱的形式保存在存储系统中。
|
||||||
|
1. 介绍知识图谱的结构
|
||||||
|
2. 不同部分的规则说明(可选)创建知识图谱的指导思路(目前不允许AI自创结构)
|
||||||
|
|
||||||
|
|
||||||
|
# read (function)
|
||||||
|
access_knowledge_graph($op_name,$params)
|
||||||
|
|
||||||
|
# write (action)
|
||||||
|
update_knowledge_graph($op_name,$params)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
[behavior.on_message]
|
[behavior.on_message]
|
||||||
type="AgentMessageProcess"
|
type="AgentMessageProcess"
|
||||||
mutil_model="gpt-4-vision-preview"
|
mutil_model="gpt-4-vision-preview"
|
||||||
@@ -45,8 +67,8 @@ known_info_tips = """
|
|||||||
|
|
||||||
tools_tips = """
|
tools_tips = """
|
||||||
"""
|
"""
|
||||||
llm_context.actions.enable = ["agent.workspace.create_task","agent.workspace.cancel_task"]
|
llm_context.actions.enable = ["agent.workspace.create_task","agent.workspace.cancel_task","knowledge_base.knowledge_graph_update"]
|
||||||
llm_context.functions.enable = ["agent.workspace.list_task"]
|
llm_context.functions.enable = ["agent.workspace.list_task","knowledge_base.knowledge_graph_read"]
|
||||||
|
|
||||||
|
|
||||||
[behavior.triage_tasks]
|
[behavior.triage_tasks]
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from .ai_functions.image_2_text_function import Image2TextFunction
|
|||||||
from .environment.workspace_env import WorkspaceEnvironment
|
from .environment.workspace_env import WorkspaceEnvironment
|
||||||
|
|
||||||
from .storage.storage import ResourceLocation,AIStorage,UserConfig,UserConfigItem
|
from .storage.storage import ResourceLocation,AIStorage,UserConfig,UserConfigItem
|
||||||
|
from .storage.objfs import ObjFS
|
||||||
|
|
||||||
from .net import *
|
from .net import *
|
||||||
from .knowledge import *
|
from .knowledge import *
|
||||||
@@ -36,4 +37,4 @@ from .package_manager import *
|
|||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
|
|
||||||
AIOS_Version = "0.5.2, build 2023-12-15"
|
AIOS_Version = "0.5.2, build 2024-3-31"
|
||||||
|
|||||||
+119
-98
@@ -9,10 +9,11 @@ import sqlite3
|
|||||||
import aiofiles
|
import aiofiles
|
||||||
|
|
||||||
from ..storage.storage import AIStorage
|
from ..storage.storage import AIStorage
|
||||||
|
from ..knowledge.knowledge_base import BaseKnowledgeGraph,ObjFSKnowledgeGrpah
|
||||||
from ..frame.compute_kernel import ComputeKernel
|
from ..frame.compute_kernel import ComputeKernel
|
||||||
from ..frame.contact_manager import ContactManager
|
from ..frame.contact_manager import ContactManager
|
||||||
from ..frame.contact import Contact
|
from ..frame.contact import Contact
|
||||||
from ..proto.ai_function import ParameterDefine, SimpleAIAction, SimpleAIFunction
|
from ..proto.ai_function import ParameterDefine, SimpleAIFunction
|
||||||
from ..proto.agent_msg import AgentMsg, AgentMsgType
|
from ..proto.agent_msg import AgentMsg, AgentMsgType
|
||||||
from ..proto.agent_task import AgentWorkLog
|
from ..proto.agent_task import AgentWorkLog
|
||||||
|
|
||||||
@@ -35,20 +36,34 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class AgentMemory:
|
class AgentMemory:
|
||||||
def __init__(self,agent_id:str,base_dir:str) -> None:
|
def __init__(self,agent_id:str,base_dir:str,enable_knowledge_graph = True) -> None:
|
||||||
self.agent_memory_base_dir = base_dir
|
self.agent_memory_base_dir = base_dir
|
||||||
self.agent_id:str= agent_id
|
self.agent_id:str= agent_id
|
||||||
|
|
||||||
AIStorage.get_instance().ensure_directory_exists(self.agent_memory_base_dir)
|
AIStorage.get_instance().ensure_directory_exists(self.agent_memory_base_dir)
|
||||||
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/experience")
|
#AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/experience")
|
||||||
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/contacts")
|
#AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/contacts")
|
||||||
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/relations")
|
#AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/relations")
|
||||||
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/summary")
|
#AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/summary")
|
||||||
|
|
||||||
self.memory_db:str = f"{self.agent_memory_base_dir}/memory.db"
|
self.memory_db:str = f"{self.agent_memory_base_dir}/memory.db"
|
||||||
self.model_name:str = "gp4-1106-preview"
|
|
||||||
self.threshold_hours = 72
|
self.threshold_hours = 72
|
||||||
self.last_think_time : float = 0.0
|
self.last_think_time : float = 0.0
|
||||||
|
self.enable_knowledge_graph : bool = enable_knowledge_graph
|
||||||
|
if self.enable_knowledge_graph:
|
||||||
|
kb_desc = """The Knowledgegraph is used to store important information obtained by Agent in the conversation.Use the following ways to store information:
|
||||||
|
/contacts/$name:Related information of the contact
|
||||||
|
/relations/$obj1/$obj2:The relationship between obj2 and obj1
|
||||||
|
/summary/$topic:Based on topic summary
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.knowledge_graph = ObjFSKnowledgeGrpah(f"{self.agent_id}.memory",self.memory_db,kb_desc)
|
||||||
|
BaseKnowledgeGraph.add_kb(self.knowledge_graph)
|
||||||
|
self.simple_memory_sentences = None
|
||||||
|
else:
|
||||||
|
self.knowledge_graph = None
|
||||||
|
self.simple_memory_sentences : List[str] = []
|
||||||
|
|
||||||
self.load_memory_meta()
|
self.load_memory_meta()
|
||||||
|
|
||||||
@@ -84,7 +99,7 @@ class AgentMemory:
|
|||||||
return chatsession
|
return chatsession
|
||||||
|
|
||||||
# return last record time
|
# return last record time
|
||||||
async def load_records(self,starttime,tokenlimit=8000)->float:
|
async def load_records(self,starttime,tokenlimit=8000,model_name=None)->float:
|
||||||
# 专用思路:做聊天记录/工作经验的整理
|
# 专用思路:做聊天记录/工作经验的整理
|
||||||
# 通用思路:没有具体的目的,让Agent根据提示词自己工作(可能效果很差也可能很好)
|
# 通用思路:没有具体的目的,让Agent根据提示词自己工作(可能效果很差也可能很好)
|
||||||
# 先实现通用思路
|
# 先实现通用思路
|
||||||
@@ -92,7 +107,7 @@ class AgentMemory:
|
|||||||
work_records = self.load_worklogs(self.agent_id,token_limit=tokenlimit)
|
work_records = self.load_worklogs(self.agent_id,token_limit=tokenlimit)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def load_chatlogs(self,msg:AgentMsg,token_limit=800):
|
async def load_chatlogs(self,msg:AgentMsg,token_limit=800,model_name=""):
|
||||||
chatsession = self.get_session_from_msg(msg)
|
chatsession = self.get_session_from_msg(msg)
|
||||||
# Must load n (n> = 2), and hope to load the M
|
# Must load n (n> = 2), and hope to load the M
|
||||||
# The information in the # M is gradually added, knowing that it is less than 72 hours from the current time, and consumes enough tokens
|
# The information in the # M is gradually added, knowing that it is less than 72 hours from the current time, and consumes enough tokens
|
||||||
@@ -105,7 +120,7 @@ class AgentMemory:
|
|||||||
dt = datetime.fromtimestamp(float(msg.create_time))
|
dt = datetime.fromtimestamp(float(msg.create_time))
|
||||||
formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
|
formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
|
||||||
record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
|
record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
|
||||||
token_limit -= ComputeKernel.llm_num_tokens_from_text(record_str,self.model_name)
|
token_limit -= ComputeKernel.llm_num_tokens_from_text(record_str)
|
||||||
if token_limit <= 32:
|
if token_limit <= 32:
|
||||||
is_all = False
|
is_all = False
|
||||||
break
|
break
|
||||||
@@ -156,7 +171,7 @@ class AgentMemory:
|
|||||||
rows = c.fetchall()
|
rows = c.fetchall()
|
||||||
|
|
||||||
|
|
||||||
return [self.from_db_row(row) for row in rows]
|
return [self.worklog_from_db_row(row) for row in rows]
|
||||||
|
|
||||||
def _create_table(self,conn):
|
def _create_table(self,conn):
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@@ -176,7 +191,7 @@ class AgentMemory:
|
|||||||
#conn.close()
|
#conn.close()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_db_row(self,row):
|
def worklog_from_db_row(self,row):
|
||||||
log = AgentWorkLog()
|
log = AgentWorkLog()
|
||||||
# 这里高度依赖表结构的顺序
|
# 这里高度依赖表结构的顺序
|
||||||
log.logid, log.owner_id, log.work_type, log.timestamp, log.content, log.result, meta_str, log.operator = row
|
log.logid, log.owner_id, log.work_type, log.timestamp, log.content, log.result, meta_str, log.operator = row
|
||||||
@@ -202,6 +217,7 @@ class AgentMemory:
|
|||||||
|
|
||||||
def load_meta(self,Dict):
|
def load_meta(self,Dict):
|
||||||
self.last_think_time = Dict.get("last_think_time",0.0)
|
self.last_think_time = Dict.get("last_think_time",0.0)
|
||||||
|
self.simple_memory_sentences = Dict.get("simple_memory_sentences",[])
|
||||||
|
|
||||||
def load_memory_meta(self):
|
def load_memory_meta(self):
|
||||||
meta_file_path = f"{self.agent_memory_base_dir}/meta.json"
|
meta_file_path = f"{self.agent_memory_base_dir}/meta.json"
|
||||||
@@ -230,7 +246,12 @@ class AgentMemory:
|
|||||||
self.last_think_time = last_time
|
self.last_think_time = last_time
|
||||||
self.save_memory_meta()
|
self.save_memory_meta()
|
||||||
|
|
||||||
|
|
||||||
async def get_contact_summary(self,contact_id:str) -> str:
|
async def get_contact_summary(self,contact_id:str) -> str:
|
||||||
|
# There is two part of contact summary
|
||||||
|
# Part 1. user defined summary (set by owner or by contac) , global , imutable
|
||||||
|
# Part 2. auto generated summary, local in agent memory , mutable
|
||||||
|
|
||||||
if contact_id is None:
|
if contact_id is None:
|
||||||
return "Contact id is None"
|
return "Contact id is None"
|
||||||
|
|
||||||
@@ -241,107 +262,107 @@ class AgentMemory:
|
|||||||
result["relation"] = contact_info.relationship
|
result["relation"] = contact_info.relationship
|
||||||
result["notes"] = contact_info.notes
|
result["notes"] = contact_info.notes
|
||||||
|
|
||||||
summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
|
# summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(summary_path, mode='r') as file:
|
# async with aiofiles.open(summary_path, mode='r') as file:
|
||||||
result["summary"] = await file.read()
|
# result["summary"] = await file.read()
|
||||||
|
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"read contact summary failed: {e}")
|
# logger.error(f"read contact summary failed: {e}")
|
||||||
|
|
||||||
return json.dumps(result,ensure_ascii=False)
|
return json.dumps(result,ensure_ascii=False)
|
||||||
|
|
||||||
async def update_contact_summary(self,contact_id:str,summary:str):
|
# async def update_contact_summary(self,contact_id:str,summary:str):
|
||||||
summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
|
# summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(summary_path, mode='w') as file:
|
# async with aiofiles.open(summary_path, mode='w') as file:
|
||||||
await file.write(summary)
|
# await file.write(summary)
|
||||||
return "OK"
|
# return "OK"
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"write contact summary failed: {e}")
|
# logger.error(f"write contact summary failed: {e}")
|
||||||
return "write contact summary failed: {e}"
|
# return "write contact summary failed: {e}"
|
||||||
|
|
||||||
async def get_summary(self,object_name:str) -> str:
|
# async def get_summary(self,object_name:str) -> str:
|
||||||
summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
|
# summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(summary_path, mode='r') as file:
|
# async with aiofiles.open(summary_path, mode='r') as file:
|
||||||
return await file.read()
|
# return await file.read()
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"read summary failed: {e}")
|
# logger.error(f"read summary failed: {e}")
|
||||||
return f"read summary failed: {e}"
|
# return f"read summary failed: {e}"
|
||||||
|
|
||||||
async def update_summary(self,object_name:str,summary:str) -> str:
|
# async def update_summary(self,object_name:str,summary:str) -> str:
|
||||||
summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
|
# summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(summary_path, mode='w') as file:
|
# async with aiofiles.open(summary_path, mode='w') as file:
|
||||||
await file.write(summary)
|
# await file.write(summary)
|
||||||
return "OK"
|
# return "OK"
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"write summary failed: {e}")
|
# logger.error(f"write summary failed: {e}")
|
||||||
return f"write summary failed: {e}"
|
# return f"write summary failed: {e}"
|
||||||
|
|
||||||
async def list_summary_object_names(self) -> List[str]:
|
# async def list_summary_object_names(self) -> List[str]:
|
||||||
# list dir
|
# # list dir
|
||||||
try:
|
# try:
|
||||||
contents = os.listdir(self.agent_memory_base_dir)
|
# contents = os.listdir(self.agent_memory_base_dir)
|
||||||
return [x for x in contents if x.endswith(".summary")]
|
# return [x for x in contents if x.endswith(".summary")]
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"list summary object names failed: {e}")
|
# logger.error(f"list summary object names failed: {e}")
|
||||||
return []
|
# return []
|
||||||
|
|
||||||
# means object1 feel object2 is ...
|
# means object1 feel object2 is ...
|
||||||
async def get_relation_summary(self,object_name1:str,object_name2:str) -> str:
|
# async def get_relation_summary(self,object_name1:str,object_name2:str) -> str:
|
||||||
summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
|
# summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(summary_path, mode='r') as file:
|
# async with aiofiles.open(summary_path, mode='r') as file:
|
||||||
await file.read()
|
# await file.read()
|
||||||
except FileNotFoundError:
|
# except FileNotFoundError:
|
||||||
return "no summary"
|
# return "no summary"
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"read relation summary failed: {e}")
|
# logger.error(f"read relation summary failed: {e}")
|
||||||
return f"read relation summary failed: {e}"
|
# return f"read relation summary failed: {e}"
|
||||||
|
|
||||||
|
|
||||||
async def update_relation_summary(self,object_name1:str,object_name2:str,summary:Dict):
|
# async def update_relation_summary(self,object_name1:str,object_name2:str,summary:Dict):
|
||||||
summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
|
# summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(summary_path, mode='w') as file:
|
# async with aiofiles.open(summary_path, mode='w') as file:
|
||||||
await file.write(json.dumps(summary))
|
# await file.write(json.dumps(summary))
|
||||||
return "OK"
|
# return "OK"
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"write relation summary failed: {e}")
|
# logger.error(f"write relation summary failed: {e}")
|
||||||
return "write relation summary failed: {e}"
|
# return "write relation summary failed: {e}"
|
||||||
|
|
||||||
async def get_experience(self,topic_name:str) -> str:
|
# async def get_experience(self,topic_name:str) -> str:
|
||||||
experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
|
# experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(experience_path, mode='r') as file:
|
# async with aiofiles.open(experience_path, mode='r') as file:
|
||||||
await file.read()
|
# await file.read()
|
||||||
except FileNotFoundError:
|
# except FileNotFoundError:
|
||||||
return "no experience"
|
# return "no experience"
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"read experience failed: {e}")
|
# logger.error(f"read experience failed: {e}")
|
||||||
return f"read experience failed: {e}"
|
# return f"read experience failed: {e}"
|
||||||
|
|
||||||
|
|
||||||
async def set_experience(self,topic_name:str,summary:str) -> str:
|
# async def set_experience(self,topic_name:str,summary:str) -> str:
|
||||||
experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
|
# experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
|
||||||
try:
|
# try:
|
||||||
async with aiofiles.open(experience_path, mode='w') as file:
|
# async with aiofiles.open(experience_path, mode='w') as file:
|
||||||
await file.write(summary)
|
# await file.write(summary)
|
||||||
return "OK"
|
# return "OK"
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"write experience failed: {e}")
|
# logger.error(f"write experience failed: {e}")
|
||||||
return "write experience failed: {e}"
|
# return "write experience failed: {e}"
|
||||||
|
|
||||||
async def list_experience(self) -> List[str]:
|
# async def list_experience(self) -> List[str]:
|
||||||
dir_path = f"{self.agent_memory_base_dir}/experience"
|
# dir_path = f"{self.agent_memory_base_dir}/experience"
|
||||||
try:
|
# try:
|
||||||
contents = os.listdir(dir_path)
|
# contents = os.listdir(dir_path)
|
||||||
return [x for x in contents if x.endswith(".experience")]
|
# return [x for x in contents if x.endswith(".experience")]
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logger.error(f"list experience failed: {e}")
|
# logger.error(f"list experience failed: {e}")
|
||||||
return []
|
# return []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def register_ai_functions():
|
def register_ai_functions():
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from .workspace import AgentWorkspace
|
|||||||
from .llm_context import LLMProcessContext,GlobaToolsLibrary, SimpleLLMContext
|
from .llm_context import LLMProcessContext,GlobaToolsLibrary, SimpleLLMContext
|
||||||
|
|
||||||
from ..frame.compute_kernel import ComputeKernel
|
from ..frame.compute_kernel import ComputeKernel
|
||||||
|
from ..knowledge.knowledge_base import BaseKnowledgeGraph
|
||||||
|
|
||||||
from abc import ABC,abstractmethod
|
from abc import ABC,abstractmethod
|
||||||
import copy
|
import copy
|
||||||
@@ -229,8 +230,7 @@ class LLMAgentBaseProcess(BaseLLMProcess):
|
|||||||
|
|
||||||
self.workspace : AgentWorkspace = None # If Workspace is not none , enable Agent Tasklist
|
self.workspace : AgentWorkspace = None # If Workspace is not none , enable Agent Tasklist
|
||||||
self.memory : AgentMemory = None
|
self.memory : AgentMemory = None
|
||||||
self.enable_kb : bool = False
|
self.enable_kb_list : List[str] = None
|
||||||
self.kb = None
|
|
||||||
|
|
||||||
async def initial(self,params:Dict = None) -> bool:
|
async def initial(self,params:Dict = None) -> bool:
|
||||||
self.memory = params.get("memory")
|
self.memory = params.get("memory")
|
||||||
@@ -265,26 +265,49 @@ class LLMAgentBaseProcess(BaseLLMProcess):
|
|||||||
if config.get("context"):
|
if config.get("context"):
|
||||||
self.context = config.get("context")
|
self.context = config.get("context")
|
||||||
|
|
||||||
|
if config.get("knowledge_grpah_introduce"):
|
||||||
|
self.knowledge_grpah_introduce = config.get("knowledge_grpah_introduce")
|
||||||
|
|
||||||
self.llm_context = SimpleLLMContext()
|
self.llm_context = SimpleLLMContext()
|
||||||
if config.get("llm_context"):
|
if config.get("llm_context"):
|
||||||
self.llm_context.load_from_config(config.get("llm_context"))
|
self.llm_context.load_from_config(config.get("llm_context"))
|
||||||
|
|
||||||
if config.get("enable_kb"):
|
def prepare_knowledge_grpah_prompt(self) -> Dict:
|
||||||
self.enable_kb = config.get("enable_kb") == "true"
|
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
|
||||||
|
|
||||||
|
|
||||||
def prepare_role_system_prompt(self,context_info:Dict) -> Dict:
|
def prepare_role_system_prompt(self,context_info:Dict) -> Dict:
|
||||||
system_prompt_dict = {}
|
system_prompt_dict = {}
|
||||||
# System Prompt
|
|
||||||
## LLM的身份说明
|
|
||||||
system_prompt_dict["role_description"] = self.role_description
|
|
||||||
#prompt.append_system_message(self.role_description)
|
|
||||||
|
|
||||||
## 处理信息的流程说明
|
system_prompt_dict["role_description"] = self.role_description
|
||||||
system_prompt_dict["process_rule"] = self.process_description
|
system_prompt_dict["process_rule"] = self.process_description
|
||||||
#prompt.append_system_message(self.process_description)
|
|
||||||
### 回复的格式
|
|
||||||
system_prompt_dict["reply_format"] = self.reply_format
|
system_prompt_dict["reply_format"] = self.reply_format
|
||||||
#prompt.append_system_message(self.reply_format)
|
|
||||||
|
kb_prompt = self.prepare_knowledge_grpah_prompt()
|
||||||
|
if kb_prompt:
|
||||||
|
system_prompt_dict["knowledge_graph"] = kb_prompt
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
if self.context:
|
if self.context:
|
||||||
@@ -301,9 +324,13 @@ class LLMAgentBaseProcess(BaseLLMProcess):
|
|||||||
|
|
||||||
def get_action_desc(self) -> Dict:
|
def get_action_desc(self) -> Dict:
|
||||||
result = {}
|
result = {}
|
||||||
actions_list = self.llm_context.get_all_ai_action()
|
actions_list = []
|
||||||
|
|
||||||
|
actions_list.extend(self.llm_context.get_all_ai_action())
|
||||||
|
|
||||||
for action in actions_list:
|
for action in actions_list:
|
||||||
result[action.get_name()] = action.get_description()
|
result[action.get_name()] = action.get_description()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
|
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
|
||||||
@@ -483,10 +510,6 @@ class AgentMessageProcess(LLMAgentBaseProcess):
|
|||||||
#TODO eanble workspace functions?
|
#TODO eanble workspace functions?
|
||||||
logger.info(f"workspace is not none,enable workspace functions")
|
logger.info(f"workspace is not none,enable workspace functions")
|
||||||
|
|
||||||
## 给予查询KB的权限
|
|
||||||
if self.enable_kb:
|
|
||||||
logger.info(f"enable kb")
|
|
||||||
|
|
||||||
|
|
||||||
### 根据Token Limit加载聊天记录
|
### 根据Token Limit加载聊天记录
|
||||||
remain_token = self.get_remain_prompt_length(prompt,json.dumps(system_prompt_dict,ensure_ascii=False))
|
remain_token = self.get_remain_prompt_length(prompt,json.dumps(system_prompt_dict,ensure_ascii=False))
|
||||||
@@ -575,7 +598,7 @@ class AgentSelfThinking(LLMAgentBaseProcess):
|
|||||||
|
|
||||||
history_str = history_str + record_str
|
history_str = history_str + record_str
|
||||||
|
|
||||||
if read_history_msg >= 2:
|
if ComputeKernel.llm_num_tokens_from_text(history_str,self.model_name) > self.chat_summary_token_len:
|
||||||
session_history["history"] = history_str
|
session_history["history"] = history_str
|
||||||
chat_history[session_id] = session_history
|
chat_history[session_id] = session_history
|
||||||
chatsession.summarize_pos = cur_pos
|
chatsession.summarize_pos = cur_pos
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class ComputeKernel:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def llm_num_tokens_from_text(text:str,model:str) -> int:
|
def llm_num_tokens_from_text(text:str,model:str = None) -> int:
|
||||||
if model is None:
|
if model is None:
|
||||||
model = "gpt-4-turbo-preview"
|
model = "gpt-4-turbo-preview"
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ from .data import *
|
|||||||
from .store import KnowledgeStore
|
from .store import KnowledgeStore
|
||||||
from .core_object import *
|
from .core_object import *
|
||||||
from .pipeline import *
|
from .pipeline import *
|
||||||
|
from .knowledge_base import *
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from ..proto.ai_function import ParameterDefine, SimpleAIAction, SimpleAIFunction
|
||||||
|
from ..agent.llm_context import GlobaToolsLibrary
|
||||||
|
from ..storage.objfs import ObjFS
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class BaseKnowledgeGraph(ABC):
|
||||||
|
_all_knowledge_bases = {}
|
||||||
|
_default_kb = None
|
||||||
|
@classmethod
|
||||||
|
def get_kb(cls, kb_id:str):
|
||||||
|
if kb_id is None:
|
||||||
|
return cls._default_kb
|
||||||
|
|
||||||
|
return cls._all_knowledge_bases.get(kb_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_kb(cls,kb:'BaseKnowledgeGraph',is_default=False):
|
||||||
|
cls._all_knowledge_bases[kb.kb_id] = kb
|
||||||
|
if is_default:
|
||||||
|
cls._default_kb = kb
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_kb(cls,kb_id:str):
|
||||||
|
if cls._default_kb is not None and cls._default_kb.kb_id == kb_id:
|
||||||
|
cls._default_kb = None
|
||||||
|
|
||||||
|
if cls._all_knowledge_bases.get(kb_id) is not None:
|
||||||
|
del cls._all_knowledge_bases[kb_id]
|
||||||
|
|
||||||
|
def __init__(self, kb_id: str,kb_desc:str=None):
|
||||||
|
self.kb_id = kb_id
|
||||||
|
if kb_desc is None:
|
||||||
|
self.kb_desc = """
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
self.kb_desc = kb_desc
|
||||||
|
|
||||||
|
def get_description(self)->str:
|
||||||
|
return self.kb_desc
|
||||||
|
|
||||||
|
# 读接口: 查询,浏览
|
||||||
|
@abstractmethod
|
||||||
|
async def serach(self, query: str,query_type:str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_obj_by_path(self,path)->str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_obj_by_id(self,obj_id)->str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def list_by_path(self,base_path)->List[str]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def list_source(self) -> List[str]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def add_obj(self,obj_id,obj_name,obj_content,paths) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def remove(self,remove_path) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def remove_obj(self,objid):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def link(self,obj_id,paths) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def unlink(self,paths) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def update_obj(self,obj_id,new_content) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_kb_default_desc_str():
|
||||||
|
return """The basic design of the Knowledge Graph is
|
||||||
|
1. Each object can be described in JSON, and have a unique obj_id.
|
||||||
|
2. The object can be accessed through the PATH, and multiple paths can point to the same object.
|
||||||
|
3. Carefully understand the semantics of the path, and follow the description of the knowledge graph.You can list all the sub-paths of a path through the LIST operation
|
||||||
|
All Knowledge Graph APIs return are json format string."""
|
||||||
|
|
||||||
|
|
||||||
|
# 写接口:通常由KnowledgePipeline调用
|
||||||
|
@staticmethod
|
||||||
|
def register_ai_functions():
|
||||||
|
|
||||||
|
async def knowledge_graph_access(parameters):
|
||||||
|
kb_id = parameters['kb_id']
|
||||||
|
op_name = parameters['op']
|
||||||
|
param = parameters['param']
|
||||||
|
|
||||||
|
|
||||||
|
if op_name is None:
|
||||||
|
logger.error("Operation type is not specified")
|
||||||
|
return "Operation type is not specified"
|
||||||
|
if param is None:
|
||||||
|
logger.error("Operation parameters is not specified")
|
||||||
|
return "Error! Operation parameters is not specified"
|
||||||
|
param = json.loads(param)
|
||||||
|
|
||||||
|
kb = BaseKnowledgeGraph.get_kb(kb_id)
|
||||||
|
if kb is None:
|
||||||
|
logger.error(f"Knowledge base is not found id:{kb_id}")
|
||||||
|
return "Error! Knowledge base is not found"
|
||||||
|
|
||||||
|
if op_name == "list":
|
||||||
|
root_path = param.get("path")
|
||||||
|
if root_path is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
return "Error! Path is not specified"
|
||||||
|
|
||||||
|
return json.dumps(await kb.list_by_path(root_path), ensure_ascii=False)
|
||||||
|
|
||||||
|
if op_name == "tree":
|
||||||
|
root_path = param.get("path")
|
||||||
|
if root_path is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
return "Error! Path is not specified"
|
||||||
|
|
||||||
|
depth = param.get("depth")
|
||||||
|
if depth is None:
|
||||||
|
depth = 3
|
||||||
|
return json.dumps(await kb.tree(root_path,depth), ensure_ascii=False)
|
||||||
|
|
||||||
|
if op_name == "read":
|
||||||
|
obj_path = param.get("path")
|
||||||
|
if obj_path is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
return "Error! Path is not specified"
|
||||||
|
return json.dumps(await kb.get_obj_by_path(obj_path), ensure_ascii=False)
|
||||||
|
|
||||||
|
if op_name == "get_obj":
|
||||||
|
obj_id = param.get("obj_id")
|
||||||
|
if obj_id is None:
|
||||||
|
logger.error("Object ID is not specified")
|
||||||
|
return "Error! Object ID is not specified"
|
||||||
|
return json.dumps(await kb.get_obj_by_id(obj_id), ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
return "Error! Operation type is not supported"
|
||||||
|
|
||||||
|
# search is not supported currently
|
||||||
|
func_desc = "Read knowledge graph, op_param format is as follows: list:{'path':$path}, read:{'path':$path}, get_obj:{'obj_id':$obj_id}, tree:{'path':$path,'depth':$depth}"
|
||||||
|
parameters = ParameterDefine.create_parameters({
|
||||||
|
"kb_id": "Knowledge Base ID",
|
||||||
|
"op": "Operation Type,could be [list, read, get_obj]",
|
||||||
|
"op_param": "Operation Param, must be a json string"
|
||||||
|
})
|
||||||
|
|
||||||
|
knowledge_graph_access_func = SimpleAIFunction("knowledge_base.knowledge_graph_read",
|
||||||
|
func_desc,
|
||||||
|
knowledge_graph_access,
|
||||||
|
parameters)
|
||||||
|
GlobaToolsLibrary.get_instance().register_tool_function(knowledge_graph_access_func)
|
||||||
|
|
||||||
|
async def knwoledge_graph_update(parameters):
|
||||||
|
kb_id = parameters['kb_id']
|
||||||
|
op_name = parameters['op']
|
||||||
|
param = parameters['param']
|
||||||
|
result = {}
|
||||||
|
if op_name is None:
|
||||||
|
logger.error("Operation type is not specified")
|
||||||
|
result["result"] = "Error! Operation type is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
if param is None:
|
||||||
|
logger.error("Operation parameters is not specified")
|
||||||
|
result["result"] = "Error! Operation parameters is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
param = json.loads(param)
|
||||||
|
|
||||||
|
kb = BaseKnowledgeGraph.get_kb(kb_id)
|
||||||
|
if kb is None:
|
||||||
|
logger.error(f"Knowledge base is not found id:{kb_id}")
|
||||||
|
result["result"] = "Error! Knowledge base is not found"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
if op_name == "write":
|
||||||
|
write_path = param.get("path")
|
||||||
|
if write_path is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
result["result"] = "Error! Path is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
obj_content = param.get("obj_json")
|
||||||
|
if obj_content is None:
|
||||||
|
logger.error("Object content is not specified")
|
||||||
|
result["result"] = "Error! Object content is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
objid = uuid.uuid4()
|
||||||
|
objname = os.path.basename(write_path)
|
||||||
|
paths = []
|
||||||
|
paths.append(write_path)
|
||||||
|
if await kb.add_obj(objid,objname,obj_content['content'],paths):
|
||||||
|
result["result"] = "OK"
|
||||||
|
result['obj_id'] = objid
|
||||||
|
else:
|
||||||
|
result["result"] = "Error! Add object failed"
|
||||||
|
|
||||||
|
if op_name == "remove":
|
||||||
|
remove_path = param.get("path")
|
||||||
|
if remove_path is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
result["result"] = "Error! Path is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
if await kb.remove(remove_path):
|
||||||
|
result["result"] = "OK"
|
||||||
|
else:
|
||||||
|
result["result"] = "Error! Remove path failed"
|
||||||
|
|
||||||
|
if op_name == "remove_obj":
|
||||||
|
obj_id = param.get("obj_id")
|
||||||
|
if obj_id is None:
|
||||||
|
logger.error("Object ID is not specified")
|
||||||
|
result["result"] = "Error! Object ID is not specified"
|
||||||
|
return result
|
||||||
|
|
||||||
|
obj = await kb.get_obj_by_id(obj_id)
|
||||||
|
if obj is None:
|
||||||
|
logger.error(f"Object is not found id:{obj_id}")
|
||||||
|
result["result"] = "Error! Object is not found"
|
||||||
|
return result
|
||||||
|
|
||||||
|
await kb.remove_obj(obj_id)
|
||||||
|
result["result"] = "OK"
|
||||||
|
|
||||||
|
if op_name == "set_obj":
|
||||||
|
obj_id = param.get("obj_id")
|
||||||
|
if obj_id is None:
|
||||||
|
logger.error("Object ID is not specified")
|
||||||
|
result["result"] = "Error! Object ID is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
obj = await kb.get_obj_by_id(obj_id)
|
||||||
|
if obj is None:
|
||||||
|
logger.error(f"Object is not found id:{obj_id}")
|
||||||
|
result["result"] = "Error! Object is not found"
|
||||||
|
return result
|
||||||
|
|
||||||
|
obj_content = param.get("obj_json")
|
||||||
|
if obj_content is None:
|
||||||
|
logger.error("new object is not specified")
|
||||||
|
result["result"] = "Error! new object is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
await kb.update_obj(obj_id,obj_content)
|
||||||
|
result["result"] = "OK"
|
||||||
|
|
||||||
|
if op_name == "link":
|
||||||
|
path_from = param.get("path")
|
||||||
|
path_to = param.get("target")
|
||||||
|
if path_from is None or path_to is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
result["result"] = "Error! Path is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
objid = await kb.get_obj_by_path(path_to)
|
||||||
|
if objid is None:
|
||||||
|
logger.error(f"Object is not found path:{path_to}")
|
||||||
|
result["result"] = "Error!Target Object is not found"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
await kb.link(objid,[path_from])
|
||||||
|
result["result"] = "OK"
|
||||||
|
|
||||||
|
if op_name == "unlink":
|
||||||
|
path_will_remove = param.get("path")
|
||||||
|
if path_will_remove is None:
|
||||||
|
logger.error("Path is not specified")
|
||||||
|
result["result"] = "Error! Path is not specified"
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
await kb.unlink([path_will_remove])
|
||||||
|
result["result"] = "OK"
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
OperationParames = """Parameters is a json string, the format is as follows:
|
||||||
|
write:{'path':$path,'obj_json':$obj_json},
|
||||||
|
remove:{'path':$path},
|
||||||
|
remove_obj:{'obj_id':$obj_id},
|
||||||
|
set_obj:{'obj_id':$obj_id,'obj_json':$new_obj_json},
|
||||||
|
link:{'path':$path,'target':$target_obj_path},
|
||||||
|
unlink:{'path':$path}
|
||||||
|
"""
|
||||||
|
parameters = ParameterDefine.create_parameters({
|
||||||
|
"kb_id": "Knowledge Base ID",
|
||||||
|
"op": "Operation Type,could be [write, remove, remove_obj, set_obj, link, unlink",
|
||||||
|
"param": OperationParames
|
||||||
|
})
|
||||||
|
|
||||||
|
knowledge_graph_update_func = SimpleAIFunction("knowledge_base.knowledge_graph_update",
|
||||||
|
"Update Knowledge Graph APIs",
|
||||||
|
knwoledge_graph_update,
|
||||||
|
parameters)
|
||||||
|
GlobaToolsLibrary.get_instance().register_tool_function(knowledge_graph_update_func)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjFSKnowledgeGrpah(BaseKnowledgeGraph):
|
||||||
|
def __init__(self, kb_id:str,db_path:str,kb_desc:str=None):
|
||||||
|
super().__init__(kb_id,kb_desc)
|
||||||
|
self.db_path = db_path
|
||||||
|
self.obj_storage : ObjFS = ObjFS(db_path)
|
||||||
|
|
||||||
|
async def serach(self, query: str,query_type:str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def list_source(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_obj_by_path(self,path)->str:
|
||||||
|
return self.obj_storage.get_obj_by_path(path)
|
||||||
|
|
||||||
|
async def get_obj_by_id(self,obj_id)->str:
|
||||||
|
return self.obj_storage.get_obj_by_id(obj_id)
|
||||||
|
|
||||||
|
async def list_by_path(self,base_path)->List[str]:
|
||||||
|
return self.obj_storage.list_paths(base_path)
|
||||||
|
|
||||||
|
async def tree(self,base_path,depth:int)->str:
|
||||||
|
return self.obj_storage.tree(base_path,depth)
|
||||||
|
|
||||||
|
async def add_obj(self,obj_id,obj_name,obj_content,paths)->bool:
|
||||||
|
self.obj_storage.add_obj(obj_id,obj_name,obj_content,paths)
|
||||||
|
|
||||||
|
#todo 更新默认是做dict的merge
|
||||||
|
async def update_obj(self, obj_id, new_content)->bool:
|
||||||
|
return self.obj_storage.update_obj(obj_id,new_content)
|
||||||
|
|
||||||
|
async def remove(self,remove_path)->bool:
|
||||||
|
self.obj_storage.remove_path(remove_path)
|
||||||
|
|
||||||
|
async def remove_obj(self,objid)->bool:
|
||||||
|
self.obj_storage.remove_obj(objid)
|
||||||
|
|
||||||
|
async def link(self,from_path,target_path)->bool:
|
||||||
|
objid = self.obj_storage.get_obj_by_path(target_path)
|
||||||
|
if objid is None:
|
||||||
|
return False
|
||||||
|
self.obj_storage.add_path(objid,from_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def unlink(self,paths)->bool:
|
||||||
|
self.obj_storage.remove_path(paths)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
class NamedObjectStorage:
|
||||||
|
def __init__(self, storage, name: str):
|
||||||
|
self.storage = storage
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
async def get(self, key: str) -> bytes:
|
||||||
|
return await self.storage.get(self.name, key)
|
||||||
|
|
||||||
|
async def put(self, key: str, data: bytes):
|
||||||
|
await self.storage.put(self.name, key, data)
|
||||||
|
|
||||||
|
async def delete(self, key: str):
|
||||||
|
await self.storage.delete(self.name, key)
|
||||||
|
|
||||||
|
async def list(self) -> List[str]:
|
||||||
|
return await self.storage.list(self.name)
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
import sqlite3
|
||||||
|
from sqlite3 import Error
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ObjFSReader(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def get_obj_by_path(self,path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_obj_by_id(self,obj_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def list_paths(self,base_path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
#ObjFS provides structured data storage similar to brain-like, as an object storage layer of Agent Friendly
|
||||||
|
class ObjFS(ObjFSReader):
|
||||||
|
def __init__(self, db_file):
|
||||||
|
""" initialize db connection """
|
||||||
|
self.db_file = db_file
|
||||||
|
self._get_conn()
|
||||||
|
|
||||||
|
def _get_conn(self):
|
||||||
|
""" get db connection """
|
||||||
|
local = threading.local()
|
||||||
|
if not hasattr(local, 'conn'):
|
||||||
|
local.conn = self._create_connection(self.db_file)
|
||||||
|
return local.conn
|
||||||
|
|
||||||
|
def _create_connection(self, db_file):
|
||||||
|
""" create a database connection to a SQLite database """
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_file)
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
logger.error("Error occurred while connecting to database: %s", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if conn:
|
||||||
|
self._create_table(conn)
|
||||||
|
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def _create_table(self, conn):
|
||||||
|
try:
|
||||||
|
conn.execute('''CREATE TABLE IF NOT EXISTS objects
|
||||||
|
(id TEXT PRIMARY KEY, name TEXT, content TEXT, created_at REAL, modified_at REAL, size INTEGER)''')
|
||||||
|
|
||||||
|
conn.execute('''CREATE TABLE IF NOT EXISTS paths
|
||||||
|
(id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT UNIQUE, obj_id TEXT, FOREIGN KEY(obj_id) REFERENCES objects(id))''')
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
logger.error("Error occurred while creating tables: %s", e)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
local = threading.local()
|
||||||
|
if not hasattr(local, 'conn'):
|
||||||
|
return
|
||||||
|
local.conn.close()
|
||||||
|
|
||||||
|
def add_obj(self,obj_uuid, name, content, paths) -> bool:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
#obj id是guid,由外部生成
|
||||||
|
|
||||||
|
# 获取当前时间戳
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# 计算内容大小
|
||||||
|
content_size = len(content.encode('utf-8'))
|
||||||
|
try:
|
||||||
|
# 插入对象
|
||||||
|
c.execute("INSERT INTO objects (id, name, content, created_at, modified_at, size) VALUES (?, ?, ?, ?, ?, ?)", (obj_uuid, name, content, current_time, current_time, content_size))
|
||||||
|
|
||||||
|
# 插入路径
|
||||||
|
for path in paths:
|
||||||
|
c.execute("INSERT OR IGNORE INTO paths (path, obj_id) VALUES (?, ?)", (path, obj_uuid))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while adding object: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_obj(self,obj_id, new_content) -> bool:
|
||||||
|
#UPDATE orders
|
||||||
|
#SET data = json_set(
|
||||||
|
# data,
|
||||||
|
# '$.items[1].price',
|
||||||
|
# 0.35
|
||||||
|
#)
|
||||||
|
#WHERE id = 1;
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
# 获取当前时间戳
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# 计算新内容大小
|
||||||
|
|
||||||
|
new_content_size = len(new_content.encode('utf-8'))
|
||||||
|
|
||||||
|
c.execute("UPDATE objects SET content = ?, modified_at = ?, size = ? WHERE id = ?", (new_content, current_time, new_content_size, obj_id))
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while updating object: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_path(self,obj_id, new_path) -> bool:
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("INSERT OR IGNORE INTO paths (path, obj_id) VALUES (?, ?)", (new_path, obj_id))
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while adding path: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def remove_path(self,path) -> bool:
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
#TODO
|
||||||
|
c.execute("DELETE FROM paths WHERE path = ?", (path,))
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while removing path: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def remove_obj(self,obj_id) -> bool:
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("DELETE FROM objects WHERE id = ?", (obj_id,))
|
||||||
|
|
||||||
|
# 删除所有与该对象相关的路径
|
||||||
|
c.execute("DELETE FROM paths WHERE obj_id = ?", (obj_id,))
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while removing object: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_obj_by_path(self,path) -> str:
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT objects.id, objects.name, objects.content FROM objects JOIN paths ON objects.id = paths.obj_id WHERE paths.path = ?", (path,))
|
||||||
|
obj_row = c.fetchone()
|
||||||
|
if obj_row:
|
||||||
|
return obj_row[2]
|
||||||
|
return None
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while getting object by path: %s", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_obj_by_id(self,obj_id) -> str:
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT id, name, content FROM objects WHERE id = ?", (obj_id,))
|
||||||
|
obj_row = c.fetchone()
|
||||||
|
if obj_row:
|
||||||
|
return obj_row[2]
|
||||||
|
return None
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while getting object by id: %s", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_paths(self,base_path)->List[str]:
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT path FROM paths WHERE path LIKE ? ESCAPE '/'", (base_path + "/%",))
|
||||||
|
return [row[0] for row in c.fetchall()]
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while listing paths: %s", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def tree(self, base_path,max_depth=3):
|
||||||
|
try:
|
||||||
|
conn = self._get_conn()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT path FROM paths WHERE path LIKE ? ESCAPE '/'", (base_path + "/%",))
|
||||||
|
paths = [row[0] for row in c.fetchall()]
|
||||||
|
tree = {}
|
||||||
|
for path in paths:
|
||||||
|
parts = path.split("/")
|
||||||
|
node = tree
|
||||||
|
for part in parts:
|
||||||
|
if part not in node:
|
||||||
|
node[part] = {}
|
||||||
|
node = node[part]
|
||||||
|
return tree
|
||||||
|
except Error as e:
|
||||||
|
logger.warning("Error occurred while listing paths: %s", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class OpenAI_ComputeNode(ComputeNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def declare_user_config(cls):
|
def declare_user_config(cls):
|
||||||
if os.getenv("OPENAI_API_KEY_") is None:
|
if os.getenv("OPENAI_API_KEY") is None:
|
||||||
user_config = AIStorage.get_instance().get_user_config()
|
user_config = AIStorage.get_instance().get_user_config()
|
||||||
user_config.add_user_config("openai_api_key","openai api key",False,None)
|
user_config.add_user_config("openai_api_key","openai api key",False,None)
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ class AIOS_Shell:
|
|||||||
#AgentManager.get_instance().register_environment("knowledge", LocalKnowledgeBase)
|
#AgentManager.get_instance().register_environment("knowledge", LocalKnowledgeBase)
|
||||||
AgentWorkspace.register_ai_functions()
|
AgentWorkspace.register_ai_functions()
|
||||||
AgentMemory.register_ai_functions()
|
AgentMemory.register_ai_functions()
|
||||||
|
BaseKnowledgeGraph.register_ai_functions()
|
||||||
ShellEnvironment.register_ai_functions()
|
ShellEnvironment.register_ai_functions()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user