Merge pull request #68 from photosssa/MVP
Add local text/image embedding node; Add an agent Mia to use local knowledge
This commit is contained in:
@@ -2,34 +2,22 @@ instance_id = "Mia"
|
|||||||
fullname = "Mia"
|
fullname = "Mia"
|
||||||
llm_model_name = "gpt-3.5-turbo-16k-0613"
|
llm_model_name = "gpt-3.5-turbo-16k-0613"
|
||||||
max_token_size = 16000
|
max_token_size = 16000
|
||||||
enable_kb = "true"
|
#enable_function =["add_event"]
|
||||||
#enable_timestamp = "true"
|
#enable_kb = "true"
|
||||||
#owner_prompt = "我是你的主人{name}"
|
enable_timestamp = "true"
|
||||||
contact_prompt = "我是你的主人朋友{name},请回答主人允许回答的问题"
|
owner_prompt = "我是你的主人{name}"
|
||||||
guest_prompt = "我是你的的主人的客人{name},请不要回答我的任何问题"
|
contact_prompt = "我是你的朋友{name}"
|
||||||
owner_env = "calender"
|
owner_env = "knowledge"
|
||||||
|
|
||||||
[[prompt]]
|
[[prompt]]
|
||||||
role = "system"
|
role = "system"
|
||||||
content = """
|
content = """
|
||||||
你叫Jarvis,是我的超级私人助理。
|
你叫Mia,你可以访问我的个人知识库。
|
||||||
你领导一个团队为我服务,团队的成员有:
|
|
||||||
Tracy,私人英语老师
|
|
||||||
David,私人画家
|
|
||||||
|
|
||||||
***
|
***
|
||||||
你看到的信息里有的有时会带上时间标签,这是为了让你更好的理解时间。你回复的信息不用创建这个时间标签。
|
|
||||||
你在收到我的信息后,按如下规则处理
|
你在收到我的信息后,按如下规则处理
|
||||||
1. 如果你认为团队里有人更适合处理该信息,用下面方法转发消息给他们处理
|
1. 在第一次接受到一条信息时,优先尝试用合适的关键字查询去查询知识库。
|
||||||
```
|
2. 如果信息中包含一段知识库的查询结果,尝试用查询结果处理,如果还是不能处理,尝试递增index继续查询。
|
||||||
##/send_msg 成员名字
|
3. 如果知识库返回不了结果了,请尽力返回。
|
||||||
消息内容
|
|
||||||
```
|
|
||||||
2.你可以访问我的Calender,查看我的日程安排。如果你在处理信息的过程中需要修改我的日程安排,请直接用合适的方法修改。
|
|
||||||
3.不符合上述规则的信息,请尽力处理。
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
#3.你可以访问我的个人信息库,当你处理我的信息时,如果需要用到我的个人信息,请先用合适的方法进行查询,然后再基于查询的结果进行进一步处理后再将结果发给我。
|
|
||||||
#4.你能根据我的需要对系统进行配置,但在修改任何配置前,请先和我确认。
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from .agent import AIAgent,AIAgentTemplete,AgentPrompt
|
|||||||
from .compute_kernel import ComputeKernel,ComputeTask,ComputeTaskResult,ComputeTaskState,ComputeTaskType
|
from .compute_kernel import ComputeKernel,ComputeTask,ComputeTaskResult,ComputeTaskState,ComputeTaskType
|
||||||
from .compute_node import ComputeNode,LocalComputeNode
|
from .compute_node import ComputeNode,LocalComputeNode
|
||||||
from .open_ai_node import OpenAI_ComputeNode
|
from .open_ai_node import OpenAI_ComputeNode
|
||||||
from .knowledge_base import KnowledgeBase
|
from .knowledge_base import KnowledgeBase, KnowledgeEnvironment
|
||||||
from .knowledge_pipeline import KnowledgeEmailSource, KnowledgeDirSource, KnowledgePipline
|
from .knowledge_pipeline import KnowledgeEmailSource, KnowledgeDirSource, KnowledgePipline
|
||||||
from .role import AIRole,AIRoleGroup
|
from .role import AIRole,AIRoleGroup
|
||||||
from .workflow import Workflow
|
from .workflow import Workflow
|
||||||
@@ -23,6 +23,7 @@ from .text_to_speech_function import TextToSpeechFunction
|
|||||||
from .workspace_env import WorkspaceEnvironment
|
from .workspace_env import WorkspaceEnvironment
|
||||||
from .local_stability_node import Local_Stability_ComputeNode
|
from .local_stability_node import Local_Stability_ComputeNode
|
||||||
from .stability_node import Stability_ComputeNode
|
from .stability_node import Stability_ComputeNode
|
||||||
|
from .local_st_compute_node import LocalSentenceTransformer_Text_ComputeNode,LocalSentenceTransformer_Image_ComputeNode
|
||||||
|
|
||||||
AIOS_Version = "0.5.1, build 2023-9-27"
|
AIOS_Version = "0.5.1, build 2023-9-27"
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class ComputeKernel:
|
|||||||
task_result = await self._send_task(task_req)
|
task_result = await self._send_task(task_req)
|
||||||
|
|
||||||
if task_req.state == ComputeTaskState.DONE:
|
if task_req.state == ComputeTaskState.DONE:
|
||||||
return task_result.result
|
return task_result.result_str
|
||||||
|
|
||||||
return "error!"
|
return "error!"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
|
from typing import Union
|
||||||
|
from knowledge import ObjectID
|
||||||
|
|
||||||
class ComputeTaskResultCode(Enum):
|
class ComputeTaskResultCode(Enum):
|
||||||
OK = 0
|
OK = 0
|
||||||
@@ -25,6 +27,7 @@ class ComputeTaskType(Enum):
|
|||||||
VOICE_2_TEXT = "voice_2_text"
|
VOICE_2_TEXT = "voice_2_text"
|
||||||
TEXT_2_VOICE = "text_2_voice"
|
TEXT_2_VOICE = "text_2_voice"
|
||||||
TEXT_EMBEDDING ="text_embedding"
|
TEXT_EMBEDDING ="text_embedding"
|
||||||
|
IMAGE_EMBEDDING ="image_embedding"
|
||||||
|
|
||||||
|
|
||||||
class ComputeTask:
|
class ComputeTask:
|
||||||
@@ -60,7 +63,7 @@ class ComputeTask:
|
|||||||
if inner_functions is not None:
|
if inner_functions is not None:
|
||||||
self.params["inner_functions"] = inner_functions
|
self.params["inner_functions"] = inner_functions
|
||||||
|
|
||||||
def set_text_embedding_params(self, input, model_name=None, callchain_id = None):
|
def set_text_embedding_params(self, input: str, model_name=None, callchain_id = None):
|
||||||
self.task_type = ComputeTaskType.TEXT_EMBEDDING
|
self.task_type = ComputeTaskType.TEXT_EMBEDDING
|
||||||
self.create_time = time.time()
|
self.create_time = time.time()
|
||||||
self.task_id = uuid.uuid4().hex
|
self.task_id = uuid.uuid4().hex
|
||||||
@@ -70,6 +73,17 @@ class ComputeTask:
|
|||||||
else:
|
else:
|
||||||
self.params["model_name"] = "text-embedding-ada-002"
|
self.params["model_name"] = "text-embedding-ada-002"
|
||||||
self.params["input"] = input
|
self.params["input"] = input
|
||||||
|
|
||||||
|
def set_image_embedding_params(self, input = Union[ObjectID, bytes], model_name=None, callchain_id = None):
|
||||||
|
self.task_type = ComputeTaskType.IMAGE_EMBEDDING
|
||||||
|
self.create_time = time.time()
|
||||||
|
self.task_id = uuid.uuid4().hex
|
||||||
|
self.callchain_id = callchain_id
|
||||||
|
if model_name is not None:
|
||||||
|
self.params["model_name"] = model_name
|
||||||
|
else:
|
||||||
|
self.params["model_name"] = None
|
||||||
|
self.params["input"] = input
|
||||||
|
|
||||||
def set_text_2_image_params(self, prompt: str, model_name, negative_prompt="", callchain_id=None):
|
def set_text_2_image_params(self, prompt: str, model_name, negative_prompt="", callchain_id=None):
|
||||||
self.task_type = ComputeTaskType.TEXT_2_IMAGE
|
self.task_type = ComputeTaskType.TEXT_2_IMAGE
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import logging
|
|||||||
from .agent import AgentPrompt
|
from .agent import AgentPrompt
|
||||||
from .compute_kernel import ComputeKernel
|
from .compute_kernel import ComputeKernel
|
||||||
from .storage import AIStorage
|
from .storage import AIStorage
|
||||||
|
from .environment import Environment
|
||||||
|
from .ai_function import SimpleAIFunction
|
||||||
from knowledge import *
|
from knowledge import *
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +22,7 @@ class KnowledgeBase:
|
|||||||
def __singleton_init__(self) -> None:
|
def __singleton_init__(self) -> None:
|
||||||
self.store = KnowledgeStore()
|
self.store = KnowledgeStore()
|
||||||
self.compute_kernel = ComputeKernel.get_instance()
|
self.compute_kernel = ComputeKernel.get_instance()
|
||||||
|
self._default_text_model = "all-MiniLM-L6-v2"
|
||||||
|
|
||||||
async def __embedding_document(self, document: DocumentObject):
|
async def __embedding_document(self, document: DocumentObject):
|
||||||
for chunk_id in document.get_chunk_list():
|
for chunk_id in document.get_chunk_list():
|
||||||
@@ -28,8 +31,8 @@ class KnowledgeBase:
|
|||||||
raise ValueError(f"text chunk not found: {chunk_id}")
|
raise ValueError(f"text chunk not found: {chunk_id}")
|
||||||
|
|
||||||
text = chunk.read().decode("utf-8")
|
text = chunk.read().decode("utf-8")
|
||||||
vector = await self.compute_kernel.do_text_embedding(text)
|
vector = await self.compute_kernel.do_text_embedding(text, self._default_text_model)
|
||||||
await self.store.get_vector_store("default").insert(vector, chunk_id)
|
await self.store.get_vector_store(self._default_text_model).insert(vector, chunk_id)
|
||||||
|
|
||||||
async def __embedding_image(self, image: ImageObject):
|
async def __embedding_image(self, image: ImageObject):
|
||||||
desc = {}
|
desc = {}
|
||||||
@@ -39,8 +42,8 @@ class KnowledgeBase:
|
|||||||
desc["exif"] = image.get_exif()
|
desc["exif"] = image.get_exif()
|
||||||
if not not image.get_tags():
|
if not not image.get_tags():
|
||||||
desc["tags"] = image.get_tags()
|
desc["tags"] = image.get_tags()
|
||||||
vector = await self.compute_kernel.do_text_embedding(json.dumps(desc))
|
vector = await self.compute_kernel.do_text_embedding(json.dumps(desc), self._default_text_model)
|
||||||
await self.store.get_vector_store("default").insert(vector, image.calculate_id())
|
await self.store.get_vector_store(self._default_text_model).insert(vector, image.calculate_id())
|
||||||
|
|
||||||
async def __embedding_video(self, vedio: VideoObject):
|
async def __embedding_video(self, vedio: VideoObject):
|
||||||
desc = {}
|
desc = {}
|
||||||
@@ -50,8 +53,8 @@ class KnowledgeBase:
|
|||||||
desc["info"] = vedio.get_info()
|
desc["info"] = vedio.get_info()
|
||||||
if not not vedio.get_tags():
|
if not not vedio.get_tags():
|
||||||
desc["tags"] = vedio.get_tags()
|
desc["tags"] = vedio.get_tags()
|
||||||
vector = await self.compute_kernel.do_text_embedding(json.dumps(desc))
|
vector = await self.compute_kernel.do_text_embedding(json.dumps(desc), self._default_text_model)
|
||||||
await self.store.get_vector_store("default").insert(vector, vedio.calculate_id())
|
await self.store.get_vector_store(self._default_text_model).insert(vector, vedio.calculate_id())
|
||||||
|
|
||||||
async def __embedding_rich_text(self, rich_text: RichTextObject):
|
async def __embedding_rich_text(self, rich_text: RichTextObject):
|
||||||
for document_id in rich_text.get_documents().values():
|
for document_id in rich_text.get_documents().values():
|
||||||
@@ -68,8 +71,8 @@ class KnowledgeBase:
|
|||||||
await self.__embedding_rich_text(rich_text)
|
await self.__embedding_rich_text(rich_text)
|
||||||
|
|
||||||
async def __embedding_email(self, email: EmailObject):
|
async def __embedding_email(self, email: EmailObject):
|
||||||
vector = await self.compute_kernel.do_text_embedding(json.dumps(email.get_desc()))
|
vector = await self.compute_kernel.do_text_embedding(json.dumps(email.get_desc()), self._default_text_model)
|
||||||
await self.store.get_vector_store("default").insert(vector, email.calculate_id())
|
await self.store.get_vector_store(self._default_text_model).insert(vector, email.calculate_id())
|
||||||
await self.__embedding_rich_text(email.get_rich_text())
|
await self.__embedding_rich_text(email.get_rich_text())
|
||||||
|
|
||||||
|
|
||||||
@@ -159,23 +162,10 @@ class KnowledgeBase:
|
|||||||
async def insert_object(self, object: KnowledgeObject):
|
async def insert_object(self, object: KnowledgeObject):
|
||||||
self.store.get_object_store().put_object(object.calculate_id(), object.encode())
|
self.store.get_object_store().put_object(object.calculate_id(), object.encode())
|
||||||
await self.__do_embedding(object)
|
await self.__do_embedding(object)
|
||||||
|
|
||||||
async def query_prompt(self, prompt: AgentPrompt):
|
async def query_objects(self, tokens: str, topk: int) -> [ObjectID]:
|
||||||
logging.info(f"query_prompt: {prompt}")
|
vector = await self.compute_kernel.do_text_embedding(tokens, self._default_text_model)
|
||||||
objects = await self.query_objects(prompt)
|
return await self.store.get_vector_store(self._default_text_model).query(vector, topk)
|
||||||
knowledge_prompt = self.prompt_from_objects(objects)
|
|
||||||
logging.info(f"prompt_from_objects result: {knowledge_prompt.as_str()}")
|
|
||||||
|
|
||||||
return knowledge_prompt
|
|
||||||
|
|
||||||
async def query_objects(self, prompt: AgentPrompt) -> [ObjectID]:
|
|
||||||
results = []
|
|
||||||
for msg in prompt.messages:
|
|
||||||
if msg["role"] == "user":
|
|
||||||
vector = await self.compute_kernel.do_text_embedding(msg["content"])
|
|
||||||
object_ids = await self.store.get_vector_store("default").query(vector, 10)
|
|
||||||
results.extend(object_ids)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def __load_object(self, object_id: ObjectID) -> KnowledgeObject:
|
def __load_object(self, object_id: ObjectID) -> KnowledgeObject:
|
||||||
if object_id.get_object_type() == ObjectType.Document:
|
if object_id.get_object_type() == ObjectType.Document:
|
||||||
@@ -192,7 +182,7 @@ class KnowledgeBase:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def prompt_from_objects(self, object_ids: [ObjectID]) -> AgentPrompt:
|
def tokens_from_objects(self, object_ids: [ObjectID]) -> list[str]:
|
||||||
results = dict()
|
results = dict()
|
||||||
for object_id in object_ids:
|
for object_id in object_ids:
|
||||||
parents = self.store.get_relation_store().get_related_root_objects(object_id)
|
parents = self.store.get_relation_store().get_related_root_objects(object_id)
|
||||||
@@ -203,8 +193,7 @@ class KnowledgeBase:
|
|||||||
results[str(root_object_id)].append(object_id)
|
results[str(root_object_id)].append(object_id)
|
||||||
else:
|
else:
|
||||||
results[str(root_object_id)] = [root_object_id, object_id]
|
results[str(root_object_id)] = [root_object_id, object_id]
|
||||||
|
content = ""
|
||||||
content = "*** I have provided the following known information for your reference with json format:\n"
|
|
||||||
result_desc = []
|
result_desc = []
|
||||||
for result in results.values():
|
for result in results.values():
|
||||||
# first element in result is the root object
|
# first element in result is the root object
|
||||||
@@ -236,12 +225,28 @@ class KnowledgeBase:
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
content += json.dumps(result_desc)
|
content += json.dumps(result_desc)
|
||||||
content += ".\n"
|
content += ".\n"
|
||||||
|
|
||||||
prompt = AgentPrompt()
|
return content
|
||||||
prompt.messages.append({"role": "user", "content": content})
|
|
||||||
|
|
||||||
return prompt
|
|
||||||
|
|
||||||
|
|
||||||
|
class KnowledgeEnvironment(Environment):
|
||||||
|
def __init__(self, env_id: str) -> None:
|
||||||
|
super().__init__(env_id)
|
||||||
|
|
||||||
|
query_param = {
|
||||||
|
"tokens": "tokens to query",
|
||||||
|
"index": "index of query result"
|
||||||
|
}
|
||||||
|
self.add_ai_function(SimpleAIFunction("query_knowledge",
|
||||||
|
"vector query content from local knowledge base",
|
||||||
|
self._query,
|
||||||
|
query_param))
|
||||||
|
|
||||||
|
async def _query(self, tokens: str, index: int=0):
|
||||||
|
object_ids = await KnowledgeBase().query_objects(tokens, 4)
|
||||||
|
if len(object_ids) <= index:
|
||||||
|
return "*** I have no more information for your reference.\n"
|
||||||
|
else:
|
||||||
|
content = "*** I have provided the following known information for your reference with json format:\n"
|
||||||
|
return content + KnowledgeBase().tokens_from_objects(object_ids[index:index + 1])
|
||||||
@@ -32,7 +32,7 @@ import requests
|
|||||||
import os
|
import os
|
||||||
import toml
|
import toml
|
||||||
from .storage import AIStorage, UserConfigItem
|
from .storage import AIStorage, UserConfigItem
|
||||||
from .knowledge_base import KnowledgeBase, ImageObjectBuilder, ObjectID, ObjectType, DocumentObjectBuilder
|
from .knowledge_base import KnowledgeBase, ImageObjectBuilder, ObjectID, ObjectType, DocumentObjectBuilder, EmailObjectBuilder, EmailObject
|
||||||
|
|
||||||
class KnowledgeJournal:
|
class KnowledgeJournal:
|
||||||
def __init__(self, source_type: str, source_id: str, item_id: str, object_id: str, timestamp=None):
|
def __init__(self, source_type: str, source_id: str, item_id: str, object_id: str, timestamp=None):
|
||||||
@@ -53,7 +53,10 @@ class KnowledgeJournal:
|
|||||||
pass
|
pass
|
||||||
return f"Add {object_type} from {os.path.join(self.source_id, self.item_id)}"
|
return f"Add {object_type} from {os.path.join(self.source_id, self.item_id)}"
|
||||||
if self.source_type == "email":
|
if self.source_type == "email":
|
||||||
pass
|
object_id = ObjectID.from_base58(self.object_id)
|
||||||
|
email = EmailObject.decode(KnowledgeBase().store.get_object_store().get_object(object_id))
|
||||||
|
meta = email.get_meta()
|
||||||
|
return f'Add email from {os.path.join(self.source_id)} subject {meta["subject"]}'
|
||||||
|
|
||||||
|
|
||||||
# init sqlite3 client
|
# init sqlite3 client
|
||||||
@@ -108,7 +111,7 @@ class KnowledgeEmailSource:
|
|||||||
self.config["type"] = "email"
|
self.config["type"] = "email"
|
||||||
|
|
||||||
def id(self):
|
def id(self):
|
||||||
"::".join([self.config["imap_server"], self.config["address"]])
|
return self.config["address"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def user_config_items(cls):
|
def user_config_items(cls):
|
||||||
@@ -121,13 +124,15 @@ class KnowledgeEmailSource:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def local_root(cls):
|
def local_root(cls):
|
||||||
user_data_dir = AIStorage.get_instance().get_myai_dir()
|
user_data_dir = AIStorage.get_instance().get_myai_dir()
|
||||||
return os.path.abspath(f"{user_data_dir}/email")
|
return os.path.abspath(f"{user_data_dir}/knowledge/email")
|
||||||
|
|
||||||
async def run_once(self):
|
async def run_once(self):
|
||||||
# read config from toml file
|
# read config from toml file
|
||||||
# and read from config config.local.toml if exists (config.local.toml is ignored by git)
|
# and read from config config.local.toml if exists (config.local.toml is ignored by git)
|
||||||
|
logging.debug(f"knowledge email source {self.id()} run once")
|
||||||
|
filter = "ALL"
|
||||||
self.client = self.email_client()
|
self.client = self.email_client()
|
||||||
await self.read_emails()
|
await self.read_emails(imap_keyword=filter)
|
||||||
|
|
||||||
def email_client(self) -> imaplib.IMAP4_SSL:
|
def email_client(self) -> imaplib.IMAP4_SSL:
|
||||||
logging.info(f"read email config from {self.config.get('imap_server')}")
|
logging.info(f"read email config from {self.config.get('imap_server')}")
|
||||||
@@ -139,26 +144,37 @@ class KnowledgeEmailSource:
|
|||||||
return client
|
return client
|
||||||
|
|
||||||
async def read_emails(self, folder: str = 'INBOX', imap_keyword: str = "UNSEEN"):
|
async def read_emails(self, folder: str = 'INBOX', imap_keyword: str = "UNSEEN"):
|
||||||
|
journal_client = KnowledgeJournalClient()
|
||||||
|
latest_journal = journal_client.latest_journal(self.id())
|
||||||
|
latest_uid = 0 if latest_journal is None else int(latest_journal.item_id)
|
||||||
self.client.select(folder)
|
self.client.select(folder)
|
||||||
_, data = self.client.uid('search', None, imap_keyword)
|
_, data = self.client.uid('search', None, imap_keyword)
|
||||||
|
|
||||||
# get email uid list
|
# get email uid list
|
||||||
email_list = data[0].split()
|
email_list = data[0].split()
|
||||||
logging.info(f"got {len(email_list)} emails")
|
logging.info(f"got {len(email_list)} emails")
|
||||||
email_list.reverse()
|
journal_client = KnowledgeJournalClient()
|
||||||
for uid in email_list:
|
for uid in email_list:
|
||||||
if self.check_email_saved(uid):
|
_uid = int.from_bytes(uid)
|
||||||
logging.info(f"email uid {uid} already saved")
|
if _uid > latest_uid:
|
||||||
else:
|
email_dir = self.check_email_saved(uid)
|
||||||
self.read_and_save_email(uid)
|
if email_dir is not None:
|
||||||
logging.info(f"email uid {uid} saved")
|
logging.info(f"email uid {uid} already saved")
|
||||||
|
else:
|
||||||
|
email_dir = self.read_and_save_email(uid)
|
||||||
|
logging.info(f"email uid {uid} saved")
|
||||||
|
email_object = EmailObjectBuilder({}, email_dir).build()
|
||||||
|
await KnowledgeBase().insert_object(email_object)
|
||||||
|
journal_client.insert(KnowledgeJournal("email", self.id(), str(int.from_bytes(uid)), str(email_object.calculate_id())))
|
||||||
|
|
||||||
def read_and_save_email(self, uid: str):
|
|
||||||
|
def read_and_save_email(self, uid: str) -> str:
|
||||||
message_parts = "(BODY.PEEK[])"
|
message_parts = "(BODY.PEEK[])"
|
||||||
_, email_data = self.client.uid('fetch', uid, message_parts)
|
_, email_data = self.client.uid('fetch', uid, message_parts)
|
||||||
mail = mailparser.parse_from_bytes(email_data[0][1])
|
mail = mailparser.parse_from_bytes(email_data[0][1])
|
||||||
logging.info(f"got email subject [{mail.subject}]")
|
logging.info(f"got email subject [{mail.subject}]")
|
||||||
self.save_email(mail)
|
self.save_email(mail)
|
||||||
|
return self.get_local_dir_name(mail)
|
||||||
|
|
||||||
def get_local_dir_name(self, mail: mailparser.MailParser) -> str:
|
def get_local_dir_name(self, mail: mailparser.MailParser) -> str:
|
||||||
dir = f"{self.local_root()}/{self.config.get('address')}"
|
dir = f"{self.local_root()}/{self.config.get('address')}"
|
||||||
@@ -166,7 +182,7 @@ class KnowledgeEmailSource:
|
|||||||
name = hashlib.md5(name.encode('utf-8')).hexdigest()
|
name = hashlib.md5(name.encode('utf-8')).hexdigest()
|
||||||
return f"{dir}/{name}"
|
return f"{dir}/{name}"
|
||||||
|
|
||||||
def check_email_saved(self, uid: str):
|
def check_email_saved(self, uid: str) -> str:
|
||||||
message_parts = "(BODY[HEADER])"
|
message_parts = "(BODY[HEADER])"
|
||||||
_, email_data = self.client.uid('fetch', uid, message_parts)
|
_, email_data = self.client.uid('fetch', uid, message_parts)
|
||||||
mail = mailparser.parse_from_bytes(email_data[0][1])
|
mail = mailparser.parse_from_bytes(email_data[0][1])
|
||||||
@@ -175,8 +191,8 @@ class KnowledgeEmailSource:
|
|||||||
logging.info(f"check email saved {dir}")
|
logging.info(f"check email saved {dir}")
|
||||||
file = f"{dir}/email.txt"
|
file = f"{dir}/email.txt"
|
||||||
if os.path.exists(file):
|
if os.path.exists(file):
|
||||||
return False
|
return dir
|
||||||
return False
|
return None
|
||||||
|
|
||||||
# save email attachment(images)
|
# save email attachment(images)
|
||||||
def save_email_attachment(self, mail: mailparser.MailParser, email_dir: str):
|
def save_email_attachment(self, mail: mailparser.MailParser, email_dir: str):
|
||||||
@@ -205,12 +221,16 @@ class KnowledgeEmailSource:
|
|||||||
img_urls = [img['src'] for img in img_tags if 'src' in img.attrs]
|
img_urls = [img['src'] for img in img_tags if 'src' in img.attrs]
|
||||||
logging.info(f'Found {len(img_urls)} images in email body')
|
logging.info(f'Found {len(img_urls)} images in email body')
|
||||||
|
|
||||||
|
name_count = 0
|
||||||
|
|
||||||
if not os.path.exists(email_dir):
|
if not os.path.exists(email_dir):
|
||||||
os.makedirs(email_dir)
|
os.makedirs(email_dir)
|
||||||
|
|
||||||
for img_url in img_urls:
|
for img_url in img_urls:
|
||||||
# keep the original image filename(last of url)
|
# keep the original image filename(last of url)
|
||||||
img_filename = os.path.join(email_dir, img_url.split('/')[-1])
|
ext = img_url.split('/')[-1].split('.')[-1]
|
||||||
|
img_filename = os.path.join(email_dir, f"{name_count}.{ext}")
|
||||||
|
name_count += 1
|
||||||
# download image
|
# download image
|
||||||
response = requests.get(img_url, stream=True)
|
response = requests.get(img_url, stream=True)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
@@ -230,7 +250,8 @@ class KnowledgeEmailSource:
|
|||||||
logging.info(f"save email to {email_dir}")
|
logging.info(f"save email to {email_dir}")
|
||||||
if not os.path.exists(email_dir):
|
if not os.path.exists(email_dir):
|
||||||
os.makedirs(email_dir)
|
os.makedirs(email_dir)
|
||||||
with open(f"{email_dir}/email.txt", "w") as f:
|
with open(f"{email_dir}/email.txt", "w", encoding='utf-8') as f:
|
||||||
|
# soup = BeautifulSoup(mail.body, 'html.parser')
|
||||||
f.write(mail.body)
|
f.write(mail.body)
|
||||||
with open(f"{email_dir}/meta.json", "w", encoding='utf-8') as f:
|
with open(f"{email_dir}/meta.json", "w", encoding='utf-8') as f:
|
||||||
mail_dict = json.loads(mail.mail_json)
|
mail_dict = json.loads(mail.mail_json)
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from typing import Optional, List
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Union
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
from .compute_task import ComputeTask, ComputeTaskState, ComputeTaskType
|
||||||
|
from .queue_compute_node import Queue_ComputeNode
|
||||||
|
from knowledge import ObjectID
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LocalSentenceTransformer_Text_ComputeNode(Queue_ComputeNode):
|
||||||
|
# For valid pretrained models, see https://www.sbert.net/docs/pretrained_models.html
|
||||||
|
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.node_id = "local_sentence_transformer_text_embedding_node"
|
||||||
|
self.model_name = model_name
|
||||||
|
self.model = None
|
||||||
|
|
||||||
|
def initial(self) -> bool:
|
||||||
|
logger.info(
|
||||||
|
f"LocalSentenceTransformer_Text_ComputeNode init, model_name: {self.model_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.model_name is not None
|
||||||
|
assert self.model is None
|
||||||
|
try:
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
|
||||||
|
self.model = SentenceTransformer(self.model_name)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"load model {self.model} failed: {err}")
|
||||||
|
return False
|
||||||
|
self.start()
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def execute_task(
|
||||||
|
self, task: ComputeTask
|
||||||
|
) -> {
|
||||||
|
"task_type": str,
|
||||||
|
"content": str,
|
||||||
|
"message": str,
|
||||||
|
"state": ComputeTaskState,
|
||||||
|
"error": {
|
||||||
|
"code": int,
|
||||||
|
"message": str,
|
||||||
|
},
|
||||||
|
}:
|
||||||
|
try:
|
||||||
|
# logger.debug(f"LocalSentenceTransformer_Text_ComputeNode task: {task}")
|
||||||
|
if task.task_type == ComputeTaskType.TEXT_EMBEDDING:
|
||||||
|
input = task.params["input"]
|
||||||
|
logger.debug(
|
||||||
|
f"LocalSentenceTransformer_Text_ComputeNode task input: {input}"
|
||||||
|
)
|
||||||
|
sentence_embeddings = self.model.encode(input, show_progress_bar=False).tolist()
|
||||||
|
# logger.debug(f"LocalSentenceTransformer_Text_ComputeNode task sentence_embeddings: {sentence_embeddings}")
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.DONE,
|
||||||
|
"content": sentence_embeddings,
|
||||||
|
"message": None,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.ERROR,
|
||||||
|
"error": {"code": -1, "message": "unsupport embedding task type"},
|
||||||
|
}
|
||||||
|
except Exception as err:
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
logger.error(f"{traceback.format_exc()}, error: {err}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.ERROR,
|
||||||
|
"error": {"code": -1, "message": "unknown exception: " + str(err)},
|
||||||
|
}
|
||||||
|
|
||||||
|
def display(self) -> str:
|
||||||
|
return f"LocalSentenceTransformer_Text_ComputeNode: {self.node_id}, {self.model_name}"
|
||||||
|
|
||||||
|
def get_capacity(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_support(self, task: ComputeTask) -> bool:
|
||||||
|
return task.task_type == ComputeTaskType.TEXT_EMBEDDING and task.params["model_name"] == "all-MiniLM-L6-v2"
|
||||||
|
|
||||||
|
def is_local(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class LocalSentenceTransformer_Image_ComputeNode(Queue_ComputeNode):
|
||||||
|
# For valid pretrained models, see https://www.sbert.net/docs/pretrained_models.html
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model_name: str = "clip-ViT-B-32",
|
||||||
|
multi_model_name: str = "clip-ViT-B-32-multilingual-v1",
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.node_id = "local_sentence_transformer_image_embedding_node"
|
||||||
|
self.model_name = model_name
|
||||||
|
self.multi_model_name = multi_model_name
|
||||||
|
self.model = None
|
||||||
|
self.multi_model = None
|
||||||
|
|
||||||
|
def initial(self) -> bool:
|
||||||
|
logger.info(
|
||||||
|
f"LocalSentenceTransformer_Image_ComputeNode init, model_name: {self.model_name} {self.multi_model_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.model_name is not None
|
||||||
|
assert self.multi_model_name is not None
|
||||||
|
assert self.model is None
|
||||||
|
assert self.multi_model is None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
|
||||||
|
self.model = SentenceTransformer(self.model_name)
|
||||||
|
self.multi_model = SentenceTransformer(self.multi_model_name)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"load model {self.model} failed: {err}")
|
||||||
|
return False
|
||||||
|
self.start()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _load_image(self, source: Union[ObjectID, bytes]) -> Optional[Image]:
|
||||||
|
image_data = None
|
||||||
|
if isinstance(source, ObjectID):
|
||||||
|
from knowledge import KnowledgeStore, ImageObject
|
||||||
|
|
||||||
|
buf = KnowledgeStore().get_object_store().get_object(source)
|
||||||
|
if buf is None:
|
||||||
|
logger.error(f"load image object but not found! {source}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_obj = ImageObject.decode(buf)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"decode ImageObject from buffer failed: {source}, {err}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
file_size = image_obj.get_file_size()
|
||||||
|
print(f"got image object: {source.to_base58()}, size: {file_size}")
|
||||||
|
|
||||||
|
image_data = (
|
||||||
|
KnowledgeStore()
|
||||||
|
.get_chunk_reader()
|
||||||
|
.read_chunk_list_to_single_bytes(image_obj.get_chunk_list())
|
||||||
|
)
|
||||||
|
|
||||||
|
elif isinstance(source, bytes):
|
||||||
|
image_data = source
|
||||||
|
else:
|
||||||
|
logger.error(f"unsupport image source type: {type(source)}, {source}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
img = Image.open(io.BytesIO(image_data))
|
||||||
|
|
||||||
|
return img
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"load image from buffer failed: {source}, {err}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def execute_task(
|
||||||
|
self, task: ComputeTask
|
||||||
|
) -> {
|
||||||
|
"task_type": str,
|
||||||
|
"content": str,
|
||||||
|
"message": str,
|
||||||
|
"state": ComputeTaskState,
|
||||||
|
"error": {
|
||||||
|
"code": int,
|
||||||
|
"message": str,
|
||||||
|
},
|
||||||
|
}:
|
||||||
|
try:
|
||||||
|
# logger.debug(f"LocalSentenceTransformer_Text_ComputeNode task: {task}")
|
||||||
|
if task.task_type == ComputeTaskType.TEXT_EMBEDDING:
|
||||||
|
input = task.params["input"]
|
||||||
|
logger.debug(
|
||||||
|
f"LocalSentenceTransformer_Text_ComputeNode task text input: {input}"
|
||||||
|
)
|
||||||
|
sentence_embeddings = self.multi_model.encode(input, show_progress_bar=False).tolist()
|
||||||
|
# logger.debug(f"LocalSentenceTransformer_Text_ComputeNode task sentence_embeddings: {sentence_embeddings}")
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.DONE,
|
||||||
|
"content": sentence_embeddings,
|
||||||
|
"message": None,
|
||||||
|
}
|
||||||
|
elif task.task_type == ComputeTaskType.IMAGE_EMBEDDING:
|
||||||
|
input = task.params["input"]
|
||||||
|
logger.debug(
|
||||||
|
f"LocalSentenceTransformer_Image_ComputeNode task image input: {input}"
|
||||||
|
)
|
||||||
|
|
||||||
|
img = self._load_image(input)
|
||||||
|
if img is None:
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.ERROR,
|
||||||
|
"error": {"code": -1, "message": "load image failed"},
|
||||||
|
}
|
||||||
|
|
||||||
|
sentence_embeddings = self.model.encode(img)
|
||||||
|
|
||||||
|
# logger.debug(f"LocalSentenceTransformer_Text_ComputeNode task sentence_embeddings: {sentence_embeddings}")
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.DONE,
|
||||||
|
"content": sentence_embeddings,
|
||||||
|
"message": None,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.ERROR,
|
||||||
|
"error": {"code": -1, "message": "unsupport embedding task type"},
|
||||||
|
}
|
||||||
|
except Exception as err:
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
logger.error(f"{traceback.format_exc()}, error: {err}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"state": ComputeTaskState.ERROR,
|
||||||
|
"error": {"code": -1, "message": "unknown exception: " + str(err)},
|
||||||
|
}
|
||||||
|
|
||||||
|
def display(self) -> str:
|
||||||
|
return f"LocalSentenceTransformer_Image_ComputeNode: {self.node_id}, {self.model_name}"
|
||||||
|
|
||||||
|
def get_capacity(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_support(self, task: ComputeTask) -> bool:
|
||||||
|
return (
|
||||||
|
(task.task_type == ComputeTaskType.TEXT_EMBEDDING and task.params["model_name"] == "clip-ViT-B-32")
|
||||||
|
or task.task_type == ComputeTaskType.IMAGE_EMBEDDING
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_local(self) -> bool:
|
||||||
|
return True
|
||||||
@@ -98,7 +98,7 @@ class OpenAI_ComputeNode(ComputeNode):
|
|||||||
task.state = ComputeTaskState.DONE
|
task.state = ComputeTaskState.DONE
|
||||||
result.result_code = ComputeTaskResultCode.OK
|
result.result_code = ComputeTaskResultCode.OK
|
||||||
result.worker_id = self.node_id
|
result.worker_id = self.node_id
|
||||||
result.result = resp["data"][0]["embedding"]
|
result.result_str = resp["data"][0]["embedding"]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
case ComputeTaskType.LLM_COMPLETION:
|
case ComputeTaskType.LLM_COMPLETION:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from asyncio import Queue
|
|||||||
import logging
|
import logging
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
|
||||||
from .compute_task import ComputeTask, ComputeTaskResult, ComputeTaskState, ComputeTaskType
|
from .compute_task import ComputeTask, ComputeTaskResult, ComputeTaskResultCode, ComputeTaskState, ComputeTaskType
|
||||||
from .compute_node import ComputeNode
|
from .compute_node import ComputeNode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -13,6 +13,7 @@ class Queue_ComputeNode(ComputeNode):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.task_queue = Queue()
|
self.task_queue = Queue()
|
||||||
|
self.is_start = False
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def execute_task(self, task: ComputeTask) -> {
|
async def execute_task(self, task: ComputeTask) -> {
|
||||||
@@ -39,28 +40,32 @@ class Queue_ComputeNode(ComputeNode):
|
|||||||
resp = await self.execute_task(task)
|
resp = await self.execute_task(task)
|
||||||
|
|
||||||
result = ComputeTaskResult()
|
result = ComputeTaskResult()
|
||||||
result.set_from_task(task)
|
|
||||||
|
|
||||||
task.state = resp["state"]
|
|
||||||
|
|
||||||
if task.state == ComputeTaskState.ERROR:
|
|
||||||
task.error_str = resp["error"]["message"]
|
|
||||||
|
|
||||||
|
|
||||||
result.worker_id = self.node_id
|
result.worker_id = self.node_id
|
||||||
result.result_str = resp["content"]
|
task.state = resp["state"]
|
||||||
result.result_message = resp["message"]
|
|
||||||
|
if task.state == ComputeTaskState.ERROR:
|
||||||
|
result.result_code = ComputeTaskResultCode.ERROR
|
||||||
|
task.error_str = resp["error"]["message"]
|
||||||
|
else:
|
||||||
|
result.result_code = ComputeTaskResultCode.OK
|
||||||
|
result.result_str = resp["content"]
|
||||||
|
result.result_message = resp["message"]
|
||||||
|
|
||||||
|
result.set_from_task(task)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def start(self):
|
def start(self):
|
||||||
|
if self.is_start is True:
|
||||||
|
return
|
||||||
|
self.is_start = True
|
||||||
|
|
||||||
async def _run_task_loop():
|
async def _run_task_loop():
|
||||||
while True:
|
while True:
|
||||||
task = await self.task_queue.get()
|
task = await self.task_queue.get()
|
||||||
logger.info(f"{self.display()} get task: {task.display()}")
|
logger.info(f"openai_node get task: {task.display()}")
|
||||||
result = await self._run_task(task)
|
await self._run_task(task)
|
||||||
if result is not None:
|
|
||||||
task.result = result
|
|
||||||
|
|
||||||
asyncio.create_task(_run_task_loop())
|
asyncio.create_task(_run_task_loop())
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ class DocumentObjectBuilder:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def build(self) -> DocumentObject:
|
def build(self) -> DocumentObject:
|
||||||
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_text(
|
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_text(self.text)
|
||||||
self.text,
|
|
||||||
1024 * 4,
|
|
||||||
".?!\n"
|
|
||||||
)
|
|
||||||
doc = DocumentObject(self.meta, self.tags, chunk_list)
|
doc = DocumentObject(self.meta, self.tags, chunk_list)
|
||||||
doc_id = doc.calculate_id()
|
doc_id = doc.calculate_id()
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ class EmailObject(KnowledgeObject):
|
|||||||
|
|
||||||
super().__init__(ObjectType.Email, desc, body)
|
super().__init__(ObjectType.Email, desc, body)
|
||||||
|
|
||||||
def get_meta(self):
|
def get_meta(self) -> dict:
|
||||||
return self.desc["meta"]
|
return self.desc["meta"]
|
||||||
|
|
||||||
def get_tags(self):
|
def get_tags(self) -> dict:
|
||||||
return self.desc["tags"]
|
return self.desc["tags"]
|
||||||
|
|
||||||
def get_rich_text(self):
|
def get_rich_text(self) -> RichTextObject:
|
||||||
return self.body["content"]
|
return self.body["content"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from ..object import KnowledgeObject
|
|||||||
from ..data import ChunkList, ChunkListWriter
|
from ..data import ChunkList, ChunkListWriter
|
||||||
from ..object import ObjectType
|
from ..object import ObjectType
|
||||||
from .. import KnowledgeStore
|
from .. import KnowledgeStore
|
||||||
|
import os
|
||||||
|
|
||||||
# desc
|
# desc
|
||||||
# meta
|
# meta
|
||||||
@@ -13,30 +14,34 @@ from .. import KnowledgeStore
|
|||||||
|
|
||||||
|
|
||||||
class ImageObject(KnowledgeObject):
|
class ImageObject(KnowledgeObject):
|
||||||
def __init__(self, meta: dict, tags: dict, exif: dict, chunk_list: ChunkList):
|
def __init__(self, meta: dict, tags: dict, exif: dict, file_size: int, chunk_list: ChunkList):
|
||||||
desc = dict()
|
desc = dict()
|
||||||
body = dict()
|
body = dict()
|
||||||
desc["meta"] = meta
|
desc["meta"] = meta
|
||||||
desc["exif"] = exif
|
desc["exif"] = exif
|
||||||
desc["tags"] = tags
|
desc["tags"] = tags
|
||||||
desc["hash"] = chunk_list.hash.to_base58()
|
desc["hash"] = chunk_list.hash.to_base58()
|
||||||
|
desc["file_size"] = file_size
|
||||||
body["chunk_list"] = chunk_list.chunk_list
|
body["chunk_list"] = chunk_list.chunk_list
|
||||||
|
|
||||||
super().__init__(ObjectType.Image, desc, body)
|
super().__init__(ObjectType.Image, desc, body)
|
||||||
|
|
||||||
def get_meta(self):
|
def get_meta(self) -> dict:
|
||||||
return self.desc["meta"]
|
return self.desc["meta"]
|
||||||
|
|
||||||
def get_exif(self):
|
def get_exif(self) -> dict:
|
||||||
return self.desc["exif"]
|
return self.desc["exif"]
|
||||||
|
|
||||||
def get_tags(self):
|
def get_tags(self) -> dict:
|
||||||
return self.desc["tags"]
|
return self.desc["tags"]
|
||||||
|
|
||||||
def get_hash(self):
|
def get_hash(self) -> str:
|
||||||
return self.desc["hash"]
|
return self.desc["hash"]
|
||||||
|
|
||||||
def get_chunk_list(self):
|
def get_file_size(self) -> int:
|
||||||
|
return self.desc["file_size"]
|
||||||
|
|
||||||
|
def get_chunk_list(self) -> ChunkList:
|
||||||
return self.body["chunk_list"]
|
return self.body["chunk_list"]
|
||||||
|
|
||||||
|
|
||||||
@@ -82,8 +87,10 @@ class ImageObjectBuilder:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def build(self) -> ImageObject:
|
def build(self) -> ImageObject:
|
||||||
|
|
||||||
|
file_size = os.path.getsize(self.image_file)
|
||||||
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_file(
|
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_file(
|
||||||
self.image_file, 1024 * 1024 * 4, self.restore_file
|
self.image_file, 1024 * 1024 * 4, self.restore_file
|
||||||
)
|
)
|
||||||
exif = get_exif_data(self.image_file)
|
exif = get_exif_data(self.image_file)
|
||||||
return ImageObject(self.meta, self.tags, exif, chunk_list)
|
return ImageObject(self.meta, self.tags, exif, file_size, chunk_list)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Chunk:
|
|||||||
self.range_start = range_start
|
self.range_start = range_start
|
||||||
self.size = size
|
self.size = size
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> bytes:
|
||||||
with open(self.file_path, 'rb') as f:
|
with open(self.file_path, 'rb') as f:
|
||||||
f.seek(self.range_start)
|
f.seek(self.range_start)
|
||||||
return f.read(self.size)
|
return f.read(self.size)
|
||||||
@@ -26,6 +26,8 @@ class ChunkReader:
|
|||||||
|
|
||||||
def get_chunk(self, chunk_id: ChunkID) -> Chunk:
|
def get_chunk(self, chunk_id: ChunkID) -> Chunk:
|
||||||
positions = self.chunk_tracker.get_position(chunk_id)
|
positions = self.chunk_tracker.get_position(chunk_id)
|
||||||
|
logging.info(f"chunk positions: {chunk_id}, {positions}")
|
||||||
|
|
||||||
if positions is None:
|
if positions is None:
|
||||||
logging.warning(f"chunk not found: {chunk_id}")
|
logging.warning(f"chunk not found: {chunk_id}")
|
||||||
return None
|
return None
|
||||||
@@ -54,15 +56,23 @@ class ChunkReader:
|
|||||||
def get_chunk_list(self, chunk_list: List[ChunkID]) -> List[Chunk]:
|
def get_chunk_list(self, chunk_list: List[ChunkID]) -> List[Chunk]:
|
||||||
return [self.get_chunk(chunk_id) for chunk_id in chunk_list]
|
return [self.get_chunk(chunk_id) for chunk_id in chunk_list]
|
||||||
|
|
||||||
def read_chunk_list(self, chunk_ids: List[ChunkID]):
|
def read_chunk_list(self, chunk_ids: List[ChunkID]) -> bytes:
|
||||||
for chunk_id in chunk_ids:
|
for chunk_id in chunk_ids:
|
||||||
chunk = self.get_chunk(chunk_id)
|
chunk = self.get_chunk(chunk_id)
|
||||||
if chunk is None:
|
if chunk is None:
|
||||||
raise ValueError(f"chunk not found: {chunk_id}")
|
raise ValueError(f"chunk not found: {chunk_id}")
|
||||||
|
|
||||||
yield from chunk.read()
|
yield chunk.read()
|
||||||
|
|
||||||
def read_text_chunk_list(self, chunk_ids: List[ChunkID]):
|
def read_chunk_list_to_single_bytes(self, chunk_ids: List[ChunkID]) -> bytes:
|
||||||
|
chunks = []
|
||||||
|
for chunk in self.read_chunk_list(chunk_ids):
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
image_data = b''.join(chunks)
|
||||||
|
return image_data
|
||||||
|
|
||||||
|
def read_text_chunk_list(self, chunk_ids: List[ChunkID]) -> str:
|
||||||
for chunk_id in chunk_ids:
|
for chunk_id in chunk_ids:
|
||||||
chunk = self.get_chunk(chunk_id)
|
chunk = self.get_chunk(chunk_id)
|
||||||
if chunk is None:
|
if chunk is None:
|
||||||
|
|||||||
+149
-28
@@ -1,13 +1,139 @@
|
|||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
from typing import Tuple, List
|
import tiktoken
|
||||||
|
import logging
|
||||||
|
from typing import Callable, Iterable, Optional, Tuple, List
|
||||||
from .chunk_store import ChunkStore
|
from .chunk_store import ChunkStore
|
||||||
from .chunk import ChunkID, PositionFileRange, PositionType
|
from .chunk import ChunkID, PositionFileRange, PositionType
|
||||||
from ..object import HashValue
|
from ..object import HashValue
|
||||||
from .tracker import ChunkTracker
|
from .tracker import ChunkTracker
|
||||||
from .chunk_list import ChunkList
|
from .chunk_list import ChunkList
|
||||||
|
|
||||||
|
def _join_docs(docs: List[str], separator: str) -> Optional[str]:
|
||||||
|
text = separator.join(docs)
|
||||||
|
text = text.strip()
|
||||||
|
if text == "":
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _merge_splits(
|
||||||
|
splits: Iterable[str],
|
||||||
|
separator: str,
|
||||||
|
chunk_size: int,
|
||||||
|
chunk_overlap: int,
|
||||||
|
length_function: Callable[[str], int]
|
||||||
|
) -> List[str]:
|
||||||
|
# We now want to combine these smaller pieces into medium size
|
||||||
|
# chunks to send to the LLM.
|
||||||
|
separator_len = length_function(separator)
|
||||||
|
|
||||||
|
docs = []
|
||||||
|
current_doc: List[str] = []
|
||||||
|
total = 0
|
||||||
|
for d in splits:
|
||||||
|
_len = length_function(d)
|
||||||
|
if (
|
||||||
|
total + _len + (separator_len if len(current_doc) > 0 else 0)
|
||||||
|
> chunk_size
|
||||||
|
):
|
||||||
|
if total > chunk_size:
|
||||||
|
logging.warning(
|
||||||
|
f"Created a chunk of size {total}, "
|
||||||
|
f"which is longer than the specified {self._chunk_size}"
|
||||||
|
)
|
||||||
|
if len(current_doc) > 0:
|
||||||
|
doc = _join_docs(current_doc, separator)
|
||||||
|
if doc is not None:
|
||||||
|
docs.append(doc)
|
||||||
|
# Keep on popping if:
|
||||||
|
# - we have a larger chunk than in the chunk overlap
|
||||||
|
# - or if we still have any chunks and the length is long
|
||||||
|
while total > chunk_overlap or (
|
||||||
|
total + _len + (separator_len if len(current_doc) > 0 else 0)
|
||||||
|
> chunk_size
|
||||||
|
and total > 0
|
||||||
|
):
|
||||||
|
total -= length_function(current_doc[0]) + (
|
||||||
|
separator_len if len(current_doc) > 1 else 0
|
||||||
|
)
|
||||||
|
current_doc = current_doc[1:]
|
||||||
|
current_doc.append(d)
|
||||||
|
total += _len + (separator_len if len(current_doc) > 1 else 0)
|
||||||
|
doc = _join_docs(current_doc, separator)
|
||||||
|
if doc is not None:
|
||||||
|
docs.append(doc)
|
||||||
|
return docs
|
||||||
|
|
||||||
|
|
||||||
|
def _split_text_with_regex(
|
||||||
|
text: str, separator: str, keep_separator: bool
|
||||||
|
) -> List[str]:
|
||||||
|
# Now that we have the separator, split the text
|
||||||
|
if separator:
|
||||||
|
if keep_separator:
|
||||||
|
# The parentheses in the pattern keep the delimiters in the result.
|
||||||
|
_splits = re.split(f"({separator})", text)
|
||||||
|
splits = [_splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2)]
|
||||||
|
if len(_splits) % 2 == 0:
|
||||||
|
splits += _splits[-1:]
|
||||||
|
splits = [_splits[0]] + splits
|
||||||
|
else:
|
||||||
|
splits = re.split(separator, text)
|
||||||
|
else:
|
||||||
|
splits = list(text)
|
||||||
|
return [s for s in splits if s != ""]
|
||||||
|
|
||||||
|
|
||||||
|
def _split_text(
|
||||||
|
text: str,
|
||||||
|
separators: List[str],
|
||||||
|
chunk_size: int,
|
||||||
|
chunk_overlap: int,
|
||||||
|
length_function: Callable[[str], int]
|
||||||
|
) -> List[str]:
|
||||||
|
|
||||||
|
"""Split incoming text and return chunks."""
|
||||||
|
final_chunks = []
|
||||||
|
# Get appropriate separator to use
|
||||||
|
separator = separators[-1]
|
||||||
|
new_separators = []
|
||||||
|
for i, _s in enumerate(separators):
|
||||||
|
_separator = re.escape(_s)
|
||||||
|
if _s == "":
|
||||||
|
separator = _s
|
||||||
|
break
|
||||||
|
if re.search(_separator, text):
|
||||||
|
separator = _s
|
||||||
|
new_separators = separators[i + 1 :]
|
||||||
|
break
|
||||||
|
|
||||||
|
keep_separator = True
|
||||||
|
_separator = re.escape(separator)
|
||||||
|
splits = _split_text_with_regex(text, _separator, keep_separator)
|
||||||
|
|
||||||
|
# Now go merging things, recursively splitting longer texts.
|
||||||
|
_good_splits = []
|
||||||
|
_separator = "" if keep_separator else separator
|
||||||
|
for s in splits:
|
||||||
|
if length_function(s) < chunk_size:
|
||||||
|
_good_splits.append(s)
|
||||||
|
else:
|
||||||
|
if _good_splits:
|
||||||
|
merged_text = _merge_splits(_good_splits, _separator, chunk_size, chunk_overlap, length_function)
|
||||||
|
final_chunks.extend(merged_text)
|
||||||
|
_good_splits = []
|
||||||
|
if not new_separators:
|
||||||
|
final_chunks.append(s)
|
||||||
|
else:
|
||||||
|
other_info = _split_text(s, new_separators, chunk_size, chunk_overlap, length_function)
|
||||||
|
final_chunks.extend(other_info)
|
||||||
|
if _good_splits:
|
||||||
|
merged_text = _merge_splits(_good_splits, _separator, chunk_size, chunk_overlap, length_function)
|
||||||
|
final_chunks.extend(merged_text)
|
||||||
|
return final_chunks
|
||||||
|
|
||||||
class ChunkListWriter:
|
class ChunkListWriter:
|
||||||
def __init__(self, chunk_store: ChunkStore, chunk_tracker: ChunkTracker):
|
def __init__(self, chunk_store: ChunkStore, chunk_tracker: ChunkTracker):
|
||||||
self.chunk_store = chunk_store
|
self.chunk_store = chunk_store
|
||||||
@@ -27,6 +153,8 @@ class ChunkListWriter:
|
|||||||
chunk = file.read(chunk_size)
|
chunk = file.read(chunk_size)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
chunk_len = len(chunk)
|
||||||
chunk_id = ChunkID.hash_data(chunk)
|
chunk_id = ChunkID.hash_data(chunk)
|
||||||
chunk_list.append(chunk_id)
|
chunk_list.append(chunk_id)
|
||||||
|
|
||||||
@@ -38,8 +166,9 @@ class ChunkListWriter:
|
|||||||
)
|
)
|
||||||
self.chunk_store.put_chunk(chunk_id, chunk)
|
self.chunk_store.put_chunk(chunk_id, chunk)
|
||||||
else:
|
else:
|
||||||
|
pos = file.tell()
|
||||||
file_range = PositionFileRange(
|
file_range = PositionFileRange(
|
||||||
file_path, file.tell() - chunk_size, chunk_size
|
file_path, pos - chunk_len, pos
|
||||||
)
|
)
|
||||||
self.chunk_tracker.add_position(
|
self.chunk_tracker.add_position(
|
||||||
chunk_id, str(file_range), PositionType.FileRange
|
chunk_id, str(file_range), PositionType.FileRange
|
||||||
@@ -51,9 +180,24 @@ class ChunkListWriter:
|
|||||||
return ChunkList(chunk_list, file_hash)
|
return ChunkList(chunk_list, file_hash)
|
||||||
|
|
||||||
def create_chunk_list_from_text(
|
def create_chunk_list_from_text(
|
||||||
self, text: str, chunk_max_words: int, separator_chars: str = ".,"
|
self,
|
||||||
|
text: str,
|
||||||
|
chunk_size: int = 4000,
|
||||||
|
chunk_overlap: int = 200,
|
||||||
|
separators: str = ["\n\n", "\n", " ", ""]
|
||||||
) -> ChunkList:
|
) -> ChunkList:
|
||||||
text_list = self._split_text_list(text, chunk_max_words, separator_chars)
|
enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
|
||||||
|
|
||||||
|
def length_function(text: str) -> int:
|
||||||
|
return len(
|
||||||
|
enc.encode(
|
||||||
|
text,
|
||||||
|
allowed_special=set(),
|
||||||
|
disallowed_special="all",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
text_list = _split_text(text, separators, chunk_size, chunk_overlap, length_function)
|
||||||
chunk_list = []
|
chunk_list = []
|
||||||
hash_obj = hashlib.sha256()
|
hash_obj = hashlib.sha256()
|
||||||
|
|
||||||
@@ -67,27 +211,4 @@ class ChunkListWriter:
|
|||||||
self.chunk_store.put_chunk(chunk_id, chunk_bytes)
|
self.chunk_store.put_chunk(chunk_id, chunk_bytes)
|
||||||
|
|
||||||
hash = HashValue(hash_obj.digest())
|
hash = HashValue(hash_obj.digest())
|
||||||
return ChunkList(chunk_list, hash)
|
return ChunkList(chunk_list, hash)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _split_text_list(
|
|
||||||
text: str, chunk_max_words: int, separator_chars: str = ".,"
|
|
||||||
) -> List[str]:
|
|
||||||
sentences = re.split(f"[{separator_chars}]", text)
|
|
||||||
# chunk_list = []
|
|
||||||
# chunk = []
|
|
||||||
# word_count = 0
|
|
||||||
# for sentence in sentences:
|
|
||||||
# words = sentence.split()
|
|
||||||
# for word in words:
|
|
||||||
# if word_count < chunk_max_words:
|
|
||||||
# chunk.append(word)
|
|
||||||
# word_count += 1
|
|
||||||
# else:
|
|
||||||
# chunk_list.append(" ".join(chunk))
|
|
||||||
# chunk = [word]
|
|
||||||
# word_count = 1
|
|
||||||
# if chunk:
|
|
||||||
# chunk_list.append(" ".join(chunk))
|
|
||||||
# return chunk_list
|
|
||||||
return sentences
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
|
|
||||||
# define a object type enum
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
class ObjectType(Enum):
|
|
||||||
TextChunk = 1
|
|
||||||
Image = 2
|
|
||||||
Email = 101
|
|
||||||
|
|
||||||
|
|
||||||
# define a object ID class to identify a object
|
|
||||||
class ObjectID: # pylint: disable=too-few-public-methods
|
|
||||||
def __init__(self, object_type, digist):
|
|
||||||
self.object_type = object_type
|
|
||||||
self.digist = digist
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.object_type.name}:{self.digist}"
|
|
||||||
|
|
||||||
|
|
||||||
# define a object class
|
|
||||||
class KnowledgeObject(ABC): # pylint: disable=too-few-public-methods
|
|
||||||
def __init__(self, object_type: ObjectType):
|
|
||||||
self.object_type = object_type
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_id(self) -> ObjectID:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# define a to binary method to convert object to binary
|
|
||||||
@abstractmethod
|
|
||||||
def to_binary(self) -> bytes:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# define a from binary method to convert binary to object
|
|
||||||
@abstractmethod
|
|
||||||
def from_binary(self, binary: bytes):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# define a text chunk class
|
|
||||||
class TextChunkObject(KnowledgeObject): # pylint: disable=too-few-public-methods
|
|
||||||
def __init__(self, text: str):
|
|
||||||
super().__init__(ObjectType.TextChunk)
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
|
|
||||||
# define a image class
|
|
||||||
class ImageObject(KnowledgeObject): # pylint: disable=too-few-public-methods
|
|
||||||
def __init__(self, meta, path):
|
|
||||||
super().__init__(ObjectType.Image)
|
|
||||||
self.meta = meta
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
|
|
||||||
# define a email class
|
|
||||||
class EmailObject(KnowledgeObject): # pylint: disable=too-few-public-methods
|
|
||||||
def __init__(self, meta):
|
|
||||||
super().__init__(ObjectType.Email)
|
|
||||||
self.meta = meta
|
|
||||||
self.text = [ObjectID]
|
|
||||||
self.images = [ObjectID]
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from .object import ObjectID
|
from .object import ObjectID
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FileBlobStorage:
|
class FileBlobStorage:
|
||||||
@@ -38,16 +40,23 @@ class FileBlobStorage:
|
|||||||
|
|
||||||
def put(self, object_id: ObjectID, contents: bytes):
|
def put(self, object_id: ObjectID, contents: bytes):
|
||||||
full_path = self.get_full_path(object_id)
|
full_path = self.get_full_path(object_id)
|
||||||
|
if os.path.exists(full_path):
|
||||||
|
logger.warning(f"will replace object: {object_id}")
|
||||||
|
|
||||||
self.write_sync(full_path, contents)
|
self.write_sync(full_path, contents)
|
||||||
|
|
||||||
def get(self, object_id: ObjectID) -> bytes:
|
def get(self, object_id: ObjectID) -> bytes:
|
||||||
full_path = self.get_full_path(object_id)
|
full_path = self.get_full_path(object_id)
|
||||||
|
if not os.path.exists(full_path):
|
||||||
|
return None
|
||||||
|
|
||||||
with open(full_path, "rb") as f:
|
with open(full_path, "rb") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
def delete(self, object_id: ObjectID):
|
def delete(self, object_id: ObjectID):
|
||||||
full_path = self.get_full_path(object_id)
|
full_path = self.get_full_path(object_id)
|
||||||
os.remove(full_path)
|
if os.path.exists(full_path):
|
||||||
|
os.remove(full_path)
|
||||||
|
|
||||||
def exists(self, object_id: ObjectID) -> bool:
|
def exists(self, object_id: ObjectID) -> bool:
|
||||||
full_path = self.get_full_path(object_id)
|
full_path = self.get_full_path(object_id)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# define a object type enum
|
# define a object type enum
|
||||||
|
from __future__ import annotations
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from .object_id import ObjectID, ObjectType
|
from .object_id import ObjectID, ObjectType
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
@@ -61,5 +63,5 @@ class KnowledgeObject(ABC):
|
|||||||
return pickle.dumps(self)
|
return pickle.dumps(self)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode(data: bytes):
|
def decode(data: bytes) -> "ImageObject":
|
||||||
return pickle.loads(data)
|
return pickle.loads(data)
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
# import RDB LargeBinary
|
|
||||||
from sqlalchemy import Column, String, LargeBinary, create_engine, sessionmaker, pickle
|
|
||||||
from .object import KnowledgeObject
|
|
||||||
|
|
||||||
# implement object storage with RDB
|
|
||||||
# define object storage table
|
|
||||||
class ObjectStorageTable(Base):
|
|
||||||
__tablename__ = 'object_storage'
|
|
||||||
id = Column(String, primary_key=True)
|
|
||||||
parent = Column(String, nullable=True)
|
|
||||||
object = Column(LargeBinary, nullable=False)
|
|
||||||
|
|
||||||
def __init__(self, id, parent, object): # pylint: disable=redefined-builtin
|
|
||||||
self.id = id
|
|
||||||
self.parent = parent
|
|
||||||
self.object = object
|
|
||||||
|
|
||||||
# define object storage class
|
|
||||||
class ObjectStorage:
|
|
||||||
async def __init__(self, db_url):
|
|
||||||
self.engine = create_engine(db_url)
|
|
||||||
self.session = sessionmaker(bind=self.engine)() # pylint: disable=not-callable
|
|
||||||
|
|
||||||
async def get(self, id) -> [KnowledgeObject, KnowledgeObject]:
|
|
||||||
obj = self.session.query(ObjectStorageTable).filter(ObjectStorageTable.id == id).first()
|
|
||||||
if obj is None:
|
|
||||||
return None
|
|
||||||
return pickle.loads(obj.object)
|
|
||||||
|
|
||||||
# define insert method
|
|
||||||
async def insert(self, object, parent): # pylint: disable=redefined-builtin
|
|
||||||
obj = ObjectStorageTable(id, parent, pickle.dumps(object))
|
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ from .object import ObjectStore, ObjectRelationStore
|
|||||||
from .data import ChunkStore, ChunkTracker, ChunkListWriter, ChunkReader
|
from .data import ChunkStore, ChunkTracker, ChunkListWriter, ChunkReader
|
||||||
from .vector import ChromaVectorStore, VectorBase
|
from .vector import ChromaVectorStore, VectorBase
|
||||||
import logging
|
import logging
|
||||||
import aios_kernel
|
|
||||||
|
|
||||||
# KnowledgeStore class, which aggregates ChunkStore, ChunkTracker, and ObjectStore, and is a global singleton that makes it easy to use these three built-in store examples
|
# KnowledgeStore class, which aggregates ChunkStore, ChunkTracker, and ObjectStore, and is a global singleton that makes it easy to use these three built-in store examples
|
||||||
class KnowledgeStore:
|
class KnowledgeStore:
|
||||||
@@ -13,6 +13,8 @@ class KnowledgeStore:
|
|||||||
def __new__(cls):
|
def __new__(cls):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
|
|
||||||
|
import aios_kernel
|
||||||
knowledge_dir = aios_kernel.storage.AIStorage().get_myai_dir() / "knowledge"
|
knowledge_dir = aios_kernel.storage.AIStorage().get_myai_dir() / "knowledge"
|
||||||
|
|
||||||
if not os.path.exists(knowledge_dir):
|
if not os.path.exists(knowledge_dir):
|
||||||
@@ -60,5 +62,5 @@ class KnowledgeStore:
|
|||||||
|
|
||||||
def get_vector_store(self, model_name: str) -> VectorBase:
|
def get_vector_store(self, model_name: str) -> VectorBase:
|
||||||
if model_name not in self.vector_store:
|
if model_name not in self.vector_store:
|
||||||
self.vector_store[model_name] = ChromaVectorStore(model_name)
|
self.vector_store[model_name] = ChromaVectorStore(self.root, model_name)
|
||||||
return self.vector_store[model_name]
|
return self.vector_store[model_name]
|
||||||
|
|||||||
+52
-1
@@ -80,10 +80,61 @@ toml>=0.10.0
|
|||||||
protobuf
|
protobuf
|
||||||
grpcio
|
grpcio
|
||||||
grpcio-status
|
grpcio-status
|
||||||
|
h11==0.14.0
|
||||||
|
httpcore==0.17.3
|
||||||
|
httptools==0.6.0
|
||||||
|
httpx==0.24.1
|
||||||
|
huggingface-hub==0.16.4
|
||||||
|
humanfriendly==10.0
|
||||||
|
idna==3.4
|
||||||
|
imageio==2.31.3
|
||||||
|
imageio-ffmpeg==0.4.8
|
||||||
|
importlib-resources==6.0.1
|
||||||
|
mail-parser==3.15.0
|
||||||
|
monotonic==1.6
|
||||||
|
moviepy==1.0.0
|
||||||
|
mpmath==1.3.0
|
||||||
|
multidict==6.0.4
|
||||||
|
numpy==1.25.2
|
||||||
|
onnxruntime==1.15.1
|
||||||
|
openai==0.28.0
|
||||||
|
overrides==7.4.0
|
||||||
|
packaging==23.1
|
||||||
|
pandas==2.1.0
|
||||||
|
Pillow==10.0.0
|
||||||
|
posthog==3.0.2
|
||||||
|
proglog==0.1.10
|
||||||
|
prompt-toolkit==3.0.39
|
||||||
|
proto-plus==1.22.3
|
||||||
|
protobuf
|
||||||
|
pulsar-client==3.3.0
|
||||||
|
pyasn1==0.5.0
|
||||||
|
pyasn1-modules==0.3.0
|
||||||
|
pydantic==1.10.12
|
||||||
|
PyPika==0.48.9
|
||||||
|
pyreadline3==3.4.1
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
python-telegram-bot==20.5
|
||||||
|
pytz==2023.3.post1
|
||||||
|
PyYAML==6.0.1
|
||||||
|
requests==2.31.0
|
||||||
|
rsa==4.9
|
||||||
|
simplejson==3.19.1
|
||||||
|
six==1.16.0
|
||||||
|
sniffio==1.3.0
|
||||||
|
soupsieve==2.5
|
||||||
|
starlette==0.27.0
|
||||||
|
sympy==1.12
|
||||||
|
telegram==0.0.1
|
||||||
|
tokenizers==0.14.0
|
||||||
|
toml==0.10.0
|
||||||
pysocks
|
pysocks
|
||||||
chardet
|
chardet
|
||||||
pydub
|
pydub
|
||||||
aiosqlite
|
aiosqlite
|
||||||
python-telegram-bot
|
python-telegram-bot
|
||||||
pydub
|
pydub
|
||||||
stability_sdk
|
stability_sdk
|
||||||
|
sentence-transformers==2.2.2
|
||||||
|
tiktoken
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ directory = os.path.dirname(__file__)
|
|||||||
sys.path.append(directory + '/../../')
|
sys.path.append(directory + '/../../')
|
||||||
|
|
||||||
|
|
||||||
from aios_kernel import AIOS_Version,AgentMsgType,UserConfigItem,AIStorage,Workflow,AIAgent,AgentMsg,AgentMsgStatus,ComputeKernel,OpenAI_ComputeNode,AIBus,AIChatSession,AgentTunnel,TelegramTunnel,CalenderEnvironment,Environment,EmailTunnel,LocalLlama_ComputeNode,Local_Stability_ComputeNode,Stability_ComputeNode,PaintEnvironment
|
|
||||||
from aios_kernel import ContactManager,Contact
|
|
||||||
import proxy
|
import proxy
|
||||||
from aios_kernel import *
|
from aios_kernel import *
|
||||||
|
|
||||||
@@ -114,6 +113,10 @@ class AIOS_Shell:
|
|||||||
|
|
||||||
cm.add_family_member(self.username,owenr)
|
cm.add_family_member(self.username,owenr)
|
||||||
|
|
||||||
|
knowledge_env = KnowledgeEnvironment("knowledge")
|
||||||
|
Environment.set_env_by_id("knowledge",knowledge_env)
|
||||||
|
|
||||||
|
|
||||||
cal_env = CalenderEnvironment("calender")
|
cal_env = CalenderEnvironment("calender")
|
||||||
await cal_env.start()
|
await cal_env.start()
|
||||||
Environment.set_env_by_id("calender",cal_env)
|
Environment.set_env_by_id("calender",cal_env)
|
||||||
@@ -137,6 +140,7 @@ class AIOS_Shell:
|
|||||||
return False
|
return False
|
||||||
ComputeKernel.get_instance().add_compute_node(open_ai_node)
|
ComputeKernel.get_instance().add_compute_node(open_ai_node)
|
||||||
|
|
||||||
|
|
||||||
if await AIStorage.get_instance().is_feature_enable("llama"):
|
if await AIStorage.get_instance().is_feature_enable("llama"):
|
||||||
llama_ai_node = LocalLlama_ComputeNode()
|
llama_ai_node = LocalLlama_ComputeNode()
|
||||||
if await llama_ai_node.initial() is True:
|
if await llama_ai_node.initial() is True:
|
||||||
@@ -146,6 +150,7 @@ class AIOS_Shell:
|
|||||||
logger.error("llama node initial failed!")
|
logger.error("llama node initial failed!")
|
||||||
await AIStorage.get_instance().set_feature_init_result("llama",False)
|
await AIStorage.get_instance().set_feature_init_result("llama",False)
|
||||||
|
|
||||||
|
|
||||||
if await AIStorage.get_instance().is_feature_enable("aigc"):
|
if await AIStorage.get_instance().is_feature_enable("aigc"):
|
||||||
try:
|
try:
|
||||||
google_text_to_speech_node = GoogleTextToSpeechNode.get_instance()
|
google_text_to_speech_node = GoogleTextToSpeechNode.get_instance()
|
||||||
@@ -160,13 +165,20 @@ class AIOS_Shell:
|
|||||||
# logger.error("stability api node initial failed!")
|
# logger.error("stability api node initial failed!")
|
||||||
# ComputeKernel.get_instance().add_compute_node(stability_api_node)
|
# ComputeKernel.get_instance().add_compute_node(stability_api_node)
|
||||||
|
|
||||||
local_sd_node = Local_Stability_ComputeNode.get_instance()
|
|
||||||
if await local_sd_node.initial() is True:
|
|
||||||
ComputeKernel.get_instance().add_compute_node(local_sd_node)
|
local_st_text_compute_node = LocalSentenceTransformer_Text_ComputeNode()
|
||||||
else:
|
if local_st_text_compute_node.initial() is not True:
|
||||||
logger.error("local stability node initial failed!")
|
logger.error("local sentence transformer text embedding node initial failed!")
|
||||||
await AIStorage.get_instance.set_feature_init_result("aigc",False)
|
else:
|
||||||
|
ComputeKernel.get_instance().add_compute_node(local_st_text_compute_node)
|
||||||
|
|
||||||
|
local_st_image_compute_node = LocalSentenceTransformer_Image_ComputeNode()
|
||||||
|
if local_st_image_compute_node.initial() is not True:
|
||||||
|
logger.error("local sentence transformer image embedding node initial failed!")
|
||||||
|
else:
|
||||||
|
ComputeKernel.get_instance().add_compute_node(local_st_image_compute_node)
|
||||||
|
|
||||||
|
|
||||||
await ComputeKernel.get_instance().start()
|
await ComputeKernel.get_instance().start()
|
||||||
|
|
||||||
@@ -308,8 +320,7 @@ class AIOS_Shell:
|
|||||||
async def handle_knowledge_commands(self, args):
|
async def handle_knowledge_commands(self, args):
|
||||||
show_text = FormattedText([("class:title", "sub command not support!\n"
|
show_text = FormattedText([("class:title", "sub command not support!\n"
|
||||||
"/knowledge add email | dir\n"
|
"/knowledge add email | dir\n"
|
||||||
"/knowledge journal [$topn]\n"
|
"/knowledge journal [$topn]\n")])
|
||||||
"/knowledge query $query\n")])
|
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
return show_text
|
return show_text
|
||||||
sub_cmd = args[0]
|
sub_cmd = args[0]
|
||||||
@@ -346,13 +357,6 @@ class AIOS_Shell:
|
|||||||
topn = 10 if len(args) == 1 else int(args[1])
|
topn = 10 if len(args) == 1 else int(args[1])
|
||||||
journals = [str(journal) for journal in KnowledgePipline.get_instance().get_latest_journals(topn)]
|
journals = [str(journal) for journal in KnowledgePipline.get_instance().get_latest_journals(topn)]
|
||||||
print_formatted_text("\r\n".join(journals))
|
print_formatted_text("\r\n".join(journals))
|
||||||
if sub_cmd == "query":
|
|
||||||
if len(args) < 2:
|
|
||||||
return show_text
|
|
||||||
prompt = AgentPrompt()
|
|
||||||
prompt.messages.append({"role": "user", "content":" ".join(args[1:])})
|
|
||||||
result = await KnowledgeBase().query_prompt(prompt)
|
|
||||||
print_formatted_text(result.as_str())
|
|
||||||
|
|
||||||
async def call_func(self,func_name, args):
|
async def call_func(self,func_name, args):
|
||||||
match func_name:
|
match func_name:
|
||||||
@@ -662,8 +666,7 @@ async def main():
|
|||||||
'/connect $target',
|
'/connect $target',
|
||||||
'/contact $name',
|
'/contact $name',
|
||||||
'/knowledge add email | dir',
|
'/knowledge add email | dir',
|
||||||
'/knowledge journal [$topn]',
|
'/knowledge journal [$topn]',
|
||||||
'/knowledge query $query'
|
|
||||||
'/set_config $key',
|
'/set_config $key',
|
||||||
'/enable $feature',
|
'/enable $feature',
|
||||||
'/disable $feature',
|
'/disable $feature',
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ class TestChunk(unittest.TestCase):
|
|||||||
with open(text_file, "r", encoding="utf-8") as file:
|
with open(text_file, "r", encoding="utf-8") as file:
|
||||||
text = file.read()
|
text = file.read()
|
||||||
|
|
||||||
gen.create_chunk_list_from_text(text, 1024)
|
gen.create_chunk_list_from_text(text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from sentence_transformers import SentenceTransformer, util
|
||||||
|
|
||||||
|
|
||||||
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
print(dir_path)
|
||||||
|
|
||||||
|
sys.path.append("{}/../src/".format(dir_path))
|
||||||
|
print(sys.path)
|
||||||
|
|
||||||
|
root = logging.getLogger()
|
||||||
|
root.setLevel(logging.DEBUG)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
root.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
def test_st():
|
||||||
|
image_model = SentenceTransformer('clip-ViT-B-32-multilingual-v1')
|
||||||
|
model = SentenceTransformer("all-MiniLM-L6-v2")
|
||||||
|
|
||||||
|
# Our sentences we like to encode
|
||||||
|
sentences = [
|
||||||
|
"This framework generates embeddings for each input sentence",
|
||||||
|
"Sentences are passed as a list of string.",
|
||||||
|
"The quick brown fox jumps over the lazy dog.",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Sentences are encoded by calling model.encode()
|
||||||
|
sentence_embeddings = model.encode(sentences)
|
||||||
|
|
||||||
|
# Print the embeddings
|
||||||
|
for sentence, embedding in zip(sentences, sentence_embeddings):
|
||||||
|
print("Sentence:", sentence)
|
||||||
|
print("Embedding:", embedding)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Single list of sentences
|
||||||
|
|
||||||
|
"""
|
||||||
|
sentences = [
|
||||||
|
"The cat sits outside",
|
||||||
|
"A man is playing guitar",
|
||||||
|
"I love pasta",
|
||||||
|
"The new movie is awesome",
|
||||||
|
"The cat plays in the garden",
|
||||||
|
"A woman watches TV",
|
||||||
|
"The new movie is so great",
|
||||||
|
"Do you like pizza?",
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
sentences = [
|
||||||
|
"猫坐在外面",
|
||||||
|
"狗坐在上面",
|
||||||
|
"狗坐在里面",
|
||||||
|
"一个男人在弹吉他",
|
||||||
|
"我爱意大利面",
|
||||||
|
"新电影太精彩了",
|
||||||
|
"猫在花园里玩耍",
|
||||||
|
"一个女人在看电视",
|
||||||
|
"新电影太棒了",
|
||||||
|
"你喜欢披萨吗?",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Compute embeddings
|
||||||
|
#embeddings = model.encode(sentences, convert_to_tensor=True)
|
||||||
|
embeddings = model.encode(sentences)
|
||||||
|
print("embeddings as follows: ")
|
||||||
|
print(embeddings)
|
||||||
|
|
||||||
|
|
||||||
|
# Compute cosine-similarities for each sentence with each other sentence
|
||||||
|
cosine_scores = util.cos_sim(embeddings, embeddings)
|
||||||
|
|
||||||
|
# Find the pairs with the highest cosine similarity scores
|
||||||
|
pairs = []
|
||||||
|
for i in range(len(cosine_scores) - 1):
|
||||||
|
for j in range(i + 1, len(cosine_scores)):
|
||||||
|
pairs.append({"index": [i, j], "score": cosine_scores[i][j]})
|
||||||
|
|
||||||
|
# Sort scores in decreasing order
|
||||||
|
pairs = sorted(pairs, key=lambda x: x["score"], reverse=True)
|
||||||
|
|
||||||
|
for pair in pairs[0:10]:
|
||||||
|
i, j = pair["index"]
|
||||||
|
print(
|
||||||
|
"{} \t\t {} \t\t Score: {:.4f}".format(
|
||||||
|
sentences[i], sentences[j], pair["score"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_st()
|
||||||
+39
-2
@@ -24,13 +24,15 @@ from knowledge import (
|
|||||||
ObjectRelationStore,
|
ObjectRelationStore,
|
||||||
KnowledgeStore,
|
KnowledgeStore,
|
||||||
EmailObject,
|
EmailObject,
|
||||||
|
ImageObject,
|
||||||
)
|
)
|
||||||
|
from aios_kernel import LocalSentenceTransformer_Image_ComputeNode, ComputeTask
|
||||||
import asyncio
|
import asyncio
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
class TestVectorSTorage(unittest.TestCase):
|
class TestVectorSTorage(unittest.IsolatedAsyncioTestCase):
|
||||||
def test_object(self):
|
async def test_object(self):
|
||||||
data = HashValue.hash_data("1233".encode("utf-8"))
|
data = HashValue.hash_data("1233".encode("utf-8"))
|
||||||
print(data.to_base58())
|
print(data.to_base58())
|
||||||
print(data.to_base36())
|
print(data.to_base36())
|
||||||
@@ -57,6 +59,41 @@ class TestVectorSTorage(unittest.TestCase):
|
|||||||
ret2 = obj.encode()
|
ret2 = obj.encode()
|
||||||
self.assertEqual(ret, ret2)
|
self.assertEqual(ret, ret2)
|
||||||
|
|
||||||
|
images = email_object.get_rich_text().get_images()
|
||||||
|
image_keys = list(images.keys())
|
||||||
|
print("got image list: ", image_keys)
|
||||||
|
|
||||||
|
image_id = images[image_keys[1]]
|
||||||
|
print(f"got image object: {image_keys[1]} {image_id.to_base58()}")
|
||||||
|
|
||||||
|
node = LocalSentenceTransformer_Image_ComputeNode();
|
||||||
|
ret = node.initial()
|
||||||
|
self.assertEqual(ret, True)
|
||||||
|
|
||||||
|
task = ComputeTask()
|
||||||
|
task.set_image_embedding_params(image_id)
|
||||||
|
ret = await node.execute_task(task)
|
||||||
|
print(ret)
|
||||||
|
'''
|
||||||
|
buf = KnowledgeStore().get_object_store().get_object(image_id)
|
||||||
|
image_obj= ImageObject.decode(buf)
|
||||||
|
file_size = image_obj.get_file_size()
|
||||||
|
print(f"got image object: {image_id.to_base58()}, size: {file_size}")
|
||||||
|
|
||||||
|
|
||||||
|
image_data = KnowledgeStore().get_chunk_reader().read_chunk_list_to_single_bytes(image_obj.get_chunk_list())
|
||||||
|
self.assertEqual(file_size, len(image_data))
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
image = Image.open(io.BytesIO(image_data))
|
||||||
|
image.show()
|
||||||
|
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
#model = SentenceTransformer('clip-ViT-B-32-multilingual-v1')
|
||||||
|
model = SentenceTransformer('clip-ViT-B-32')
|
||||||
|
model.encode(image, convert_to_tensor=True)
|
||||||
|
'''
|
||||||
|
|
||||||
def test_relation(self):
|
def test_relation(self):
|
||||||
obj1 = ObjectID.hash_data("12345".encode("utf-8"))
|
obj1 = ObjectID.hash_data("12345".encode("utf-8"))
|
||||||
|
|||||||
Reference in New Issue
Block a user