shell knowledge commands
This commit is contained in:
@@ -4,3 +4,108 @@
|
|||||||
|
|
||||||
# 2023-09-19 16:05:27.714815
|
# 2023-09-19 16:05:27.714815
|
||||||
+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
|
+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
|
||||||
|
|
||||||
|
# 2023-09-20 14:48:06.181582
|
||||||
|
+/connect
|
||||||
|
|
||||||
|
# 2023-09-20 14:48:22.678945
|
||||||
|
+/connect Jarvis email
|
||||||
|
|
||||||
|
# 2023-09-20 14:54:35.101026
|
||||||
|
+/connect Jarvis telegram
|
||||||
|
|
||||||
|
# 2023-09-20 14:54:55.229801
|
||||||
|
+token abcd
|
||||||
|
|
||||||
|
# 2023-09-20 16:22:34.229084
|
||||||
|
+/knowledge add email
|
||||||
|
|
||||||
|
# 2023-09-20 16:22:46.797842
|
||||||
|
+puotosssa@live.com
|
||||||
|
|
||||||
|
# 2023-09-20 16:22:53.622015
|
||||||
|
+p19870626
|
||||||
|
|
||||||
|
# 2023-09-20 16:22:59.186145
|
||||||
|
+imap@live.com
|
||||||
|
|
||||||
|
# 2023-09-20 16:23:02.334411
|
||||||
|
+993
|
||||||
|
|
||||||
|
# 2023-09-20 16:40:29.996329
|
||||||
|
+/knowledge add email
|
||||||
|
|
||||||
|
# 2023-09-20 16:40:52.860700
|
||||||
|
+puotosssa@live.com
|
||||||
|
|
||||||
|
# 2023-09-20 16:40:55.324994
|
||||||
|
+fdafda
|
||||||
|
|
||||||
|
# 2023-09-20 16:40:58.167154
|
||||||
|
+fdafdaf
|
||||||
|
|
||||||
|
# 2023-09-20 16:40:59.757363
|
||||||
|
+993
|
||||||
|
|
||||||
|
# 2023-09-20 16:42:55.288680
|
||||||
|
+/knowledge add email
|
||||||
|
|
||||||
|
# 2023-09-20 16:42:56.916408
|
||||||
|
+fdfa
|
||||||
|
|
||||||
|
# 2023-09-20 16:42:57.965232
|
||||||
|
+fdfds
|
||||||
|
|
||||||
|
# 2023-09-20 16:42:59.149516
|
||||||
|
+fdafd
|
||||||
|
|
||||||
|
# 2023-09-20 16:43:00.949104
|
||||||
|
+993
|
||||||
|
|
||||||
|
# 2023-09-21 14:11:33.500467
|
||||||
|
+/knowledge add c:/users/tsukasa/myai/photos
|
||||||
|
|
||||||
|
# 2023-09-21 14:14:31.488794
|
||||||
|
+/knowledge add dir
|
||||||
|
|
||||||
|
# 2023-09-21 14:14:49.298635
|
||||||
|
+c:/users/tsukasa/myai/photos
|
||||||
|
|
||||||
|
# 2023-09-21 15:27:58.550658
|
||||||
|
+/knowledge add dir
|
||||||
|
|
||||||
|
# 2023-09-21 15:28:10.190467
|
||||||
|
+c:/users/tsukasa/myai/photos
|
||||||
|
|
||||||
|
# 2023-09-21 15:32:32.431305
|
||||||
|
+/knowledge add dir
|
||||||
|
|
||||||
|
# 2023-09-21 15:32:46.695566
|
||||||
|
+c:/users/tsukasa/myai/photos
|
||||||
|
|
||||||
|
# 2023-09-21 15:33:36.391465
|
||||||
|
+/knowledge add dir
|
||||||
|
|
||||||
|
# 2023-09-21 15:33:47.574853
|
||||||
|
+c:/users/tsukasa/myai/photos
|
||||||
|
|
||||||
|
# 2023-09-21 17:44:26.308692
|
||||||
|
+/knowledge journals
|
||||||
|
|
||||||
|
# 2023-09-21 17:45:42.925359
|
||||||
|
+/knowledge journal
|
||||||
|
|
||||||
|
# 2023-09-21 18:13:08.720054
|
||||||
|
+/knowledge query what a see is
|
||||||
|
|
||||||
|
# 2023-09-21 18:20:09.275556
|
||||||
|
+/knowledge journal
|
||||||
|
|
||||||
|
# 2023-09-21 18:20:10.746056
|
||||||
|
+/knowledge query what a see is
|
||||||
|
|
||||||
|
# 2023-09-21 18:29:48.391223
|
||||||
|
+/knowledge journal
|
||||||
|
|
||||||
|
# 2023-09-21 18:29:51.406532
|
||||||
|
+/knowledge query what a see is
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from .compute_kernel import ComputeKernel,ComputeTask
|
|||||||
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
|
||||||
from .knowledge_pipeline import EmailSpider
|
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
|
||||||
from .bus import AIBus
|
from .bus import AIBus
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# define a knowledge base class
|
# define a knowledge base class
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from . import AgentPrompt, ComputeKernel, AIStorage
|
from .agent import AgentPrompt
|
||||||
|
from .compute_kernel import ComputeKernel
|
||||||
|
from .storage import AIStorage
|
||||||
from knowledge import *
|
from knowledge import *
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ class KnowledgeBase:
|
|||||||
|
|
||||||
def __singleton_init__(self) -> None:
|
def __singleton_init__(self) -> None:
|
||||||
self.store = KnowledgeStore()
|
self.store = KnowledgeStore()
|
||||||
self.compute_kernel = ComputeKernel()
|
self.compute_kernel = ComputeKernel.get_instance()
|
||||||
|
|
||||||
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():
|
||||||
@@ -155,7 +157,7 @@ class KnowledgeBase:
|
|||||||
# pass
|
# pass
|
||||||
|
|
||||||
async def insert_object(self, object: KnowledgeObject):
|
async def insert_object(self, object: KnowledgeObject):
|
||||||
# self.__save_object(object)
|
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_prompt(self, prompt: AgentPrompt):
|
||||||
@@ -164,6 +166,7 @@ class KnowledgeBase:
|
|||||||
knowledge_prompt = self.prompt_from_objects(objects)
|
knowledge_prompt = self.prompt_from_objects(objects)
|
||||||
logging.info(f"prompt_from_objects result: {knowledge_prompt.as_str()}")
|
logging.info(f"prompt_from_objects result: {knowledge_prompt.as_str()}")
|
||||||
prompt.append(knowledge_prompt)
|
prompt.append(knowledge_prompt)
|
||||||
|
return prompt
|
||||||
|
|
||||||
async def query_objects(self, prompt: AgentPrompt) -> [ObjectID]:
|
async def query_objects(self, prompt: AgentPrompt) -> [ObjectID]:
|
||||||
results = []
|
results = []
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ An example of a local file is as follows:
|
|||||||
│ └── dd-login-service-min.png
|
│ └── dd-login-service-min.png
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import sqlite3
|
||||||
import imaplib
|
import imaplib
|
||||||
import os
|
|
||||||
import toml
|
|
||||||
import logging
|
import logging
|
||||||
import mailparser
|
import mailparser
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -26,63 +26,139 @@ import json
|
|||||||
import base64
|
import base64
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import requests
|
import requests
|
||||||
|
import os
|
||||||
|
import toml
|
||||||
|
from .storage import AIStorage, UserConfigItem
|
||||||
|
from .knowledge_base import KnowledgeBase, ImageObjectBuilder, ObjectID, ObjectType, DocumentObjectBuilder
|
||||||
|
|
||||||
class EmailSpider:
|
class KnowledgeJournal:
|
||||||
|
def __init__(self, source_type: str, source_id: str, item_id: str, object_id: str, timestamp=None):
|
||||||
|
# define a timestamp variable
|
||||||
|
self.timestamp = datetime.datetime.now() if timestamp is None else timestamp
|
||||||
|
self.object_id = object_id
|
||||||
|
self.source_type = source_type
|
||||||
|
self.source_id = source_id
|
||||||
|
self.item_id = item_id
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.source_type == "dir":
|
||||||
|
object_id = ObjectID.from_base58(self.object_id)
|
||||||
|
object_type = None
|
||||||
|
if object_id.get_object_type() == ObjectType.Image:
|
||||||
|
object_type = "image"
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
return f"Add {object_type} from {os.path.join(self.source_id, self.item_id)}"
|
||||||
|
if self.source_type == "email":
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# init sqlite3 client
|
||||||
|
class KnowledgeJournalClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# logger config
|
knowledge_dir = os.path.join(AIStorage.get_instance().get_myai_dir(), "knowledge")
|
||||||
self.logger = logging.getLogger('email spider')
|
if not os.path.exists(knowledge_dir):
|
||||||
self.logger.setLevel(logging.DEBUG)
|
os.makedirs(knowledge_dir)
|
||||||
ch = logging.StreamHandler()
|
self.journal_path = os.path.join(knowledge_dir, "journal.db")
|
||||||
formatter = logging.Formatter('%(asctime)s [%(name)s] [%(levelname)s] %(message)s')
|
|
||||||
ch.setFormatter(formatter)
|
conn = sqlite3.connect(self.journal_path)
|
||||||
self.logger.addHandler(ch)
|
conn.execute(
|
||||||
|
'''CREATE TABLE IF NOT EXISTS journal (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
source_type TEXT,
|
||||||
|
source_id TEXT,
|
||||||
|
item_id TEXT,
|
||||||
|
object_id TEXT)'''
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def insert(self, journal: KnowledgeJournal):
|
||||||
|
conn = sqlite3.connect(self.journal_path)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO journal (time, source_type, source_id, item_id, object_id) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
(journal.timestamp, journal.source_type, journal.source_id, journal.item_id, journal.object_id),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def latest_journal(self, source_id: str) -> KnowledgeJournal:
|
||||||
|
conn = sqlite3.connect(self.journal_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT * FROM journal WHERE source_id = ? ORDER BY id DESC LIMIT 1", (source_id,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
(_, timestamp, source_type, sorce_id, item_id, object_id) = result
|
||||||
|
return KnowledgeJournal(source_type, sorce_id, item_id, object_id, timestamp)
|
||||||
|
|
||||||
|
def latest_journals(self, topn) -> [KnowledgeJournal]:
|
||||||
|
conn = sqlite3.connect(self.journal_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT * FROM journal ORDER BY id DESC LIMIT ?", (topn,))
|
||||||
|
return [KnowledgeJournal(source_type, sorce_id, item_id, object_id, timestamp) for (_, timestamp, source_type, sorce_id, item_id, object_id) in cursor.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
|
class KnowledgeEmailSource:
|
||||||
|
def __init__(self, config:dict):
|
||||||
|
self.config = config
|
||||||
|
self.config["type"] = "email"
|
||||||
|
|
||||||
|
def id(self):
|
||||||
|
"::".join([self.config["imap_server"], self.config["address"]])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_config_items(cls):
|
||||||
|
return [("address", "email address"),
|
||||||
|
("password", "email password"),
|
||||||
|
("imap_server", "imap server"),
|
||||||
|
("imap_port", "imap port")
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def local_root(cls):
|
||||||
|
user_data_dir = AIStorage.get_instance().get_myai_dir()
|
||||||
|
return os.path.abspath(f"{user_data_dir}/email")
|
||||||
|
|
||||||
|
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)
|
||||||
self.config = toml.load('./rootfs/email/config.toml')
|
|
||||||
if os.path.exists('./rootfs/email/config.local.toml'):
|
|
||||||
self.config = toml.load('./rootfs/email/config.local.toml')
|
|
||||||
|
|
||||||
self.client = self.email_client()
|
self.client = self.email_client()
|
||||||
|
await self.read_emails()
|
||||||
|
|
||||||
def email_client(self) -> imaplib.IMAP4_SSL:
|
def email_client(self) -> imaplib.IMAP4_SSL:
|
||||||
self.logger.info(f"read email config from {self.config.get('EMAIL_IMAP_SERVER')}")
|
logging.info(f"read email config from {self.config.get('imap_server')}")
|
||||||
client = imaplib.IMAP4_SSL(
|
client = imaplib.IMAP4_SSL(
|
||||||
host=self.config.get('EMAIL_IMAP_SERVER'),
|
host=self.config.get('imap_server'),
|
||||||
port=self.config.get('EMAIL_IMAP_PORT')
|
port=self.config.get('imap_port')
|
||||||
)
|
)
|
||||||
client.login(self.config.get('EMAIL_ADDRESS'), self.config.get('EMAIL_PASSWORD'))
|
client.login(self.config.get('address'), self.config.get('password'))
|
||||||
return client
|
return client
|
||||||
|
|
||||||
def list_box(self):
|
async def read_emails(self, folder: str = 'INBOX', imap_keyword: str = "UNSEEN"):
|
||||||
_, mailbox_list = self.client.list()
|
|
||||||
for mailbox in mailbox_list:
|
|
||||||
print(mailbox.decode())
|
|
||||||
|
|
||||||
def read_emails(self, folder: str = 'INBOX', imap_keyword: str = "UNSEEN"):
|
|
||||||
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()
|
||||||
self.logger.info(f"got {len(email_list)} emails")
|
logging.info(f"got {len(email_list)} emails")
|
||||||
email_list.reverse()
|
email_list.reverse()
|
||||||
for uid in email_list:
|
for uid in email_list:
|
||||||
if self.check_email_saved(uid):
|
if self.check_email_saved(uid):
|
||||||
self.logger.info(f"email uid {uid} already saved")
|
logging.info(f"email uid {uid} already saved")
|
||||||
else:
|
else:
|
||||||
self.read_and_save_email(uid)
|
self.read_and_save_email(uid)
|
||||||
self.logger.info(f"email uid {uid} saved")
|
logging.info(f"email uid {uid} saved")
|
||||||
|
|
||||||
def read_and_save_email(self, uid: str):
|
def read_and_save_email(self, uid: 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])
|
||||||
self.logger.info(f"got email subject [{mail.subject}]")
|
logging.info(f"got email subject [{mail.subject}]")
|
||||||
self.save_email(mail)
|
self.save_email(mail)
|
||||||
|
|
||||||
def get_local_dir_name(self, mail: mailparser.MailParser) -> str:
|
def get_local_dir_name(self, mail: mailparser.MailParser) -> str:
|
||||||
dir = f"{self.config.get('LOCAL_DIR')}/{self.config.get('EMAIL_ADDRESS')}"
|
dir = f"{self.local_root()}/{self.config.get('address')}"
|
||||||
name = f"{mail.subject}__{mail.date}"
|
name = f"{mail.subject}__{mail.date}"
|
||||||
name = hashlib.md5(name.encode('utf-8')).hexdigest()
|
name = hashlib.md5(name.encode('utf-8')).hexdigest()
|
||||||
return f"{dir}/{name}"
|
return f"{dir}/{name}"
|
||||||
@@ -91,9 +167,9 @@ class EmailSpider:
|
|||||||
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])
|
||||||
self.logger.info(f"[{uid}]check email subject [{mail.subject}]")
|
logging.info(f"[{uid}]check email subject [{mail.subject}]")
|
||||||
dir = self.get_local_dir_name(mail)
|
dir = self.get_local_dir_name(mail)
|
||||||
self.logger.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 False
|
||||||
@@ -116,7 +192,7 @@ class EmailSpider:
|
|||||||
image_data = image_data.encode()
|
image_data = image_data.encode()
|
||||||
with open(filefullname, 'wb') as f:
|
with open(filefullname, 'wb') as f:
|
||||||
f.write(image_data)
|
f.write(image_data)
|
||||||
self.logger.info(f"save email image {filename} success")
|
logging.info(f"save email image {filename} success")
|
||||||
|
|
||||||
# save email body images(html content)
|
# save email body images(html content)
|
||||||
def save_body_images(self, html_content: str, email_dir: str):
|
def save_body_images(self, html_content: str, email_dir: str):
|
||||||
@@ -124,7 +200,7 @@ class EmailSpider:
|
|||||||
soup = BeautifulSoup(html_content, 'html.parser')
|
soup = BeautifulSoup(html_content, 'html.parser')
|
||||||
img_tags = soup.find_all('img')
|
img_tags = soup.find_all('img')
|
||||||
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]
|
||||||
self.logger.info(f'Found {len(img_urls)} images in email body')
|
logging.info(f'Found {len(img_urls)} images in email body')
|
||||||
|
|
||||||
if not os.path.exists(email_dir):
|
if not os.path.exists(email_dir):
|
||||||
os.makedirs(email_dir)
|
os.makedirs(email_dir)
|
||||||
@@ -138,17 +214,17 @@ class EmailSpider:
|
|||||||
with open(img_filename, 'wb') as img_file:
|
with open(img_filename, 'wb') as img_file:
|
||||||
for chunk in response.iter_content(1024):
|
for chunk in response.iter_content(1024):
|
||||||
img_file.write(chunk)
|
img_file.write(chunk)
|
||||||
self.logger.info(f'Downloaded {img_url} to {img_filename}')
|
logging.info(f'Downloaded {img_url} to {img_filename}')
|
||||||
else:
|
else:
|
||||||
self.logger.info(f'Failed to download {img_url}')
|
logging.info(f'Failed to download {img_url}')
|
||||||
|
|
||||||
# save email content to local dir
|
# save email content to local dir
|
||||||
def save_email(self, mail: mailparser.MailParser):
|
def save_email(self, mail: mailparser.MailParser):
|
||||||
dir = f"{self.config.get('LOCAL_DIR')}/{self.config.get('EMAIL_ADDRESS')}"
|
dir = f"{self.local_root()}/{self.config.get('address')}"
|
||||||
if not os.path.exists(dir):
|
if not os.path.exists(dir):
|
||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
email_dir = self.get_local_dir_name(mail)
|
email_dir = self.get_local_dir_name(mail)
|
||||||
self.logger.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") as f:
|
||||||
@@ -158,14 +234,58 @@ class EmailSpider:
|
|||||||
if 'body' in mail_dict:
|
if 'body' in mail_dict:
|
||||||
del mail_dict['body']
|
del mail_dict['body']
|
||||||
json.dump(mail_dict, f, ensure_ascii=False, indent=4)
|
json.dump(mail_dict, f, ensure_ascii=False, indent=4)
|
||||||
self.logger.info(f"save email meta info {f.name}")
|
logging.info(f"save email meta info {f.name}")
|
||||||
|
|
||||||
self.save_email_attachment(mail, email_dir)
|
self.save_email_attachment(mail, email_dir)
|
||||||
self.save_body_images(mail.body, f"{email_dir}/body_image")
|
self.save_body_images(mail.body, f"{email_dir}/body_image")
|
||||||
|
|
||||||
|
|
||||||
|
class KnowledgeDirSource:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
self.config["type"] = "dir"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_config_items(cls):
|
||||||
|
return [("path", "local dir path")]
|
||||||
|
|
||||||
|
def id(self):
|
||||||
|
return self.config["path"]
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
return self.config["path"]
|
||||||
|
|
||||||
|
async def run_once(self):
|
||||||
|
logging.debug(f"knowledge dir source {self.id()} run once")
|
||||||
|
journal_client = KnowledgeJournalClient()
|
||||||
|
latest_journal = journal_client.latest_journal(self.id())
|
||||||
|
if latest_journal is not None:
|
||||||
|
if os.path.getmtime(self.path()) <= latest_journal.timestamp:
|
||||||
|
logging.debug(f"knowledge dir source {self.id()} ingnored for no update")
|
||||||
|
return
|
||||||
|
file_pathes = sorted(os.listdir(self.path()), key=lambda x: os.path.getctime(os.path.join(self.path(), x)))
|
||||||
|
for rel_path in file_pathes:
|
||||||
|
file_path = os.path.join(self.path(), rel_path)
|
||||||
|
timestamp = os.path.getctime(file_path)
|
||||||
|
if latest_journal is not None:
|
||||||
|
if timestamp <= latest_journal.timestamp:
|
||||||
|
continue
|
||||||
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']:
|
||||||
|
logging.info(f"knowledge dir source {self.id()} found image file {file_path}")
|
||||||
|
image = ImageObjectBuilder({}, {}, file_path).build()
|
||||||
|
await KnowledgeBase().insert_object(image)
|
||||||
|
journal_client.insert(KnowledgeJournal("dir", self.id(), rel_path, str(image.calculate_id()), timestamp))
|
||||||
|
if ext in ['.txt']:
|
||||||
|
logging.info(f"knowledge dir source {self.id()} found text file {file_path}")
|
||||||
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
document = DocumentObjectBuilder({}, {}, text).build()
|
||||||
|
await KnowledgeBase().insert_object(document)
|
||||||
|
journal_client.insert(KnowledgeJournal("dir", self.id(), rel_path, str(document.calculate_id()), timestamp))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from . import AIStorage, KnowledgeBase
|
|
||||||
|
|
||||||
# define singleton class knowledge pipline
|
# define singleton class knowledge pipline
|
||||||
class KnowledgePipline:
|
class KnowledgePipline:
|
||||||
@@ -178,15 +298,71 @@ class KnowledgePipline:
|
|||||||
cls._instance.__singleton_init__()
|
cls._instance.__singleton_init__()
|
||||||
|
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
|
def initial(self):
|
||||||
|
config_path = self.__config_path()
|
||||||
|
logging.info(f"initial knowledge pipline from {config_path}")
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
config = toml.load(self.__config_path())
|
||||||
|
for source_config in config["sources"]:
|
||||||
|
if source_config['type'] == 'email':
|
||||||
|
self.add_email_source(KnowledgeEmailSource(source_config))
|
||||||
|
if source_config['type'] == 'dir':
|
||||||
|
self.add_dir_source(KnowledgeDirSource(source_config))
|
||||||
|
|
||||||
def __singleton_init__(self) -> None:
|
def __singleton_init__(self):
|
||||||
self.knowledge_base = KnowledgeBase()
|
self.knowledge_base = KnowledgeBase()
|
||||||
|
self.email_sources = dict()
|
||||||
|
self.dir_sources = dict()
|
||||||
|
self.source_queue = list()
|
||||||
|
self.run_lock = asyncio.Lock()
|
||||||
|
asyncio.create_task(self.run_loop())
|
||||||
|
|
||||||
|
|
||||||
|
def save_config(self):
|
||||||
|
config = dict()
|
||||||
|
config["sources"] = [source.config for source in self.source_queue]
|
||||||
|
with open(self.__config_path(), "w") as f:
|
||||||
|
toml.dump(config, f)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def declare_user_config(cls):
|
def __config_path(cls) -> str:
|
||||||
user_config = AIStorage.get_instance().get_user_config()
|
user_data_dir = AIStorage.get_instance().get_myai_dir()
|
||||||
user_config.add_user_config("email_spiders","email addresses to build knowledge base",True,None,"list")
|
return os.path.abspath(f"{user_data_dir}/etc/knowledge.cfg.toml")
|
||||||
user_config.add_user_config("personal_dirs", "personal directories to build knowledge base", True, None, "list")
|
|
||||||
|
|
||||||
def add_email_address(self, ) -> None:
|
|
||||||
pass
|
def add_email_source(self, source: KnowledgeEmailSource):
|
||||||
|
if self.email_sources.get(source.id()) is not None:
|
||||||
|
return "already exists"
|
||||||
|
self.email_sources[source.id()] = source
|
||||||
|
self.source_queue.append(source)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_dir_source(self, source: KnowledgeDirSource):
|
||||||
|
if self.dir_sources.get(source.id()) is not None:
|
||||||
|
logging.info(f"knowledge add source {source.id()} failed for already exists")
|
||||||
|
return "already exists"
|
||||||
|
logging.info(f"knowledge added source {source.id()}")
|
||||||
|
self.dir_sources[source.id()] = source
|
||||||
|
self.source_queue.append(source)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_latest_journals(self, topn) -> [KnowledgeJournal]:
|
||||||
|
return KnowledgeJournalClient().latest_journals(topn)
|
||||||
|
|
||||||
|
async def run_loop(self):
|
||||||
|
while True:
|
||||||
|
await self.run_once()
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
async def run_once(self):
|
||||||
|
logging.info(f"knowledge pipeline started")
|
||||||
|
# sources = list()
|
||||||
|
# async with self.run_lock:
|
||||||
|
# for source in self.source_queue:
|
||||||
|
# sources.append(source)
|
||||||
|
# for source in sources:
|
||||||
|
# await source.run_once()
|
||||||
|
for source in self.source_queue:
|
||||||
|
await source.run_once()
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class DocumentObjectBuilder:
|
|||||||
self.text = text
|
self.text = text
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def build(self, relation_store: ObjectRelationStore) -> 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,
|
1024 * 4,
|
||||||
@@ -60,6 +60,6 @@ class DocumentObjectBuilder:
|
|||||||
|
|
||||||
# Add relation to store
|
# Add relation to store
|
||||||
for chunk_id in chunk_list.chunk_list:
|
for chunk_id in chunk_list.chunk_list:
|
||||||
relation_store.add_relation(chunk_id, doc_id)
|
KnowledgeStore().get_relation_store().add_relation(chunk_id, doc_id)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class EmailObjectBuilder:
|
|||||||
with open(content_file, "r", encoding="utf-8") as f:
|
with open(content_file, "r", encoding="utf-8") as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
|
|
||||||
document = DocumentObjectBuilder({}, {}, text).build(relation_store=relation)
|
document = DocumentObjectBuilder({}, {}, text).build()
|
||||||
document_id = document.calculate_id()
|
document_id = document.calculate_id()
|
||||||
store.put_object(document_id, document.encode())
|
store.put_object(document_id, document.encode())
|
||||||
documents = {"email.txt": document_id}
|
documents = {"email.txt": document_id}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def get_exif_data(image_path: str):
|
|||||||
return {
|
return {
|
||||||
TAGS.get(key): exif_data[key]
|
TAGS.get(key): exif_data[key]
|
||||||
for key in exif_data.keys()
|
for key in exif_data.keys()
|
||||||
if key in TAGS and isinstance(exif_data[key], (bytes, str))
|
if key in TAGS and isinstance(exif_data[key], str)
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class ChunkListWriter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
file_hash = HashValue(hash_obj.digest())
|
file_hash = HashValue(hash_obj.digest())
|
||||||
print(f"calc file hash: {file_path}, {file_hash}")
|
# print(f"calc file hash: {file_path}, {file_hash}")
|
||||||
|
|
||||||
return ChunkList(chunk_list, file_hash)
|
return ChunkList(chunk_list, file_hash)
|
||||||
|
|
||||||
|
|||||||
+6
-10
@@ -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,16 +13,12 @@ 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)
|
||||||
directory = os.path.join(
|
knowledge_dir = aios_kernel.storage.AIStorage().get_myai_dir() / "knowledge"
|
||||||
os.path.dirname(__file__), "../../rootfs/data/"
|
|
||||||
)
|
|
||||||
directory = os.path.normpath(directory)
|
|
||||||
print(directory)
|
|
||||||
|
|
||||||
if not os.path.exists(directory):
|
if not os.path.exists(knowledge_dir):
|
||||||
os.makedirs(directory)
|
os.makedirs(knowledge_dir)
|
||||||
|
|
||||||
cls._instance.__singleton_init__(directory)
|
cls._instance.__singleton_init__(knowledge_dir)
|
||||||
|
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
@@ -64,5 +60,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]
|
||||||
|
|||||||
@@ -6,16 +6,14 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
class ChromaVectorStore(VectorBase):
|
class ChromaVectorStore(VectorBase):
|
||||||
def __init__(self, model_name: str) -> None:
|
def __init__(self, root_dir, model_name: str) -> None:
|
||||||
super().__init__(model_name)
|
super().__init__(model_name)
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
"will init chroma vector store, model={}".format(model_name)
|
"will init chroma vector store, model={}".format(model_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
directory = os.path.join(
|
directory = os.path.join(root_dir, "vector")
|
||||||
os.path.dirname(__file__), "../../../rootfs/data/vector"
|
|
||||||
)
|
|
||||||
logging.info("will use vector store: {}".format(directory))
|
logging.info("will use vector store: {}".format(directory))
|
||||||
|
|
||||||
client = chromadb.PersistentClient(
|
client = chromadb.PersistentClient(
|
||||||
|
|||||||
@@ -22,8 +22,11 @@ from prompt_toolkit.styles import Style
|
|||||||
directory = os.path.dirname(__file__)
|
directory = os.path.dirname(__file__)
|
||||||
sys.path.append(directory + '/../../')
|
sys.path.append(directory + '/../../')
|
||||||
|
|
||||||
from aios_kernel import AIOS_Version,UserConfigItem,AIStorage,Workflow,AIAgent,AgentMsg,AgentMsgStatus,ComputeKernel,OpenAI_ComputeNode,AIBus,AIChatSession,AgentTunnel,TelegramTunnel,CalenderEnvironment,Environment,EmailTunnel,LocalLlama_ComputeNode
|
|
||||||
import proxy
|
import proxy
|
||||||
|
from aios_kernel import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sys.path.append(directory + '/../../component/')
|
sys.path.append(directory + '/../../component/')
|
||||||
from agent_manager import AgentManager
|
from agent_manager import AgentManager
|
||||||
@@ -115,7 +118,8 @@ class AIOS_Shell:
|
|||||||
await AgentTunnel.load_all_tunnels_from_config(tunnel_config)
|
await AgentTunnel.load_all_tunnels_from_config(tunnel_config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"load tunnels config from {tunnels_config_path} failed!")
|
logger.warning(f"load tunnels config from {tunnels_config_path} failed!")
|
||||||
|
|
||||||
|
KnowledgePipline.get_instance().initial()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -163,8 +167,7 @@ class AIOS_Shell:
|
|||||||
|
|
||||||
tunnel_config[key] = user_input
|
tunnel_config[key] = user_input
|
||||||
|
|
||||||
return tunnel_config
|
return tunnel_config
|
||||||
|
|
||||||
|
|
||||||
async def append_tunnel_config(self,tunnel_config):
|
async def append_tunnel_config(self,tunnel_config):
|
||||||
user_data_dir = AIStorage.get_instance().get_myai_dir()
|
user_data_dir = AIStorage.get_instance().get_myai_dir()
|
||||||
@@ -179,6 +182,55 @@ class AIOS_Shell:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"load tunnels config from {tunnels_config_path} failed!")
|
logger.warning(f"load tunnels config from {tunnels_config_path} failed!")
|
||||||
|
|
||||||
|
async def handle_knowledge_commands(self, args):
|
||||||
|
show_text = FormattedText([("class:title", "sub command not support!\n"
|
||||||
|
"/knowledge add email | dir\n"
|
||||||
|
"/knowledge journal [$topn]\n"
|
||||||
|
"/knowledge query $query\n")])
|
||||||
|
if len(args) < 1:
|
||||||
|
return show_text
|
||||||
|
sub_cmd = args[0]
|
||||||
|
if sub_cmd == "add":
|
||||||
|
if len(args) < 2:
|
||||||
|
return show_text
|
||||||
|
if args[1] == "email":
|
||||||
|
config = dict()
|
||||||
|
for key, item in KnowledgeEmailSource.user_config_items():
|
||||||
|
user_input = await try_get_input(f"{key} : {item}")
|
||||||
|
if user_input is None:
|
||||||
|
return show_text
|
||||||
|
config[key] = user_input
|
||||||
|
error = KnowledgePipline.get_instance().add_email_source(KnowledgeEmailSource(config))
|
||||||
|
if error is not None:
|
||||||
|
return FormattedText([("class:title", f"/knowledge add email failed {error}\n")])
|
||||||
|
else:
|
||||||
|
KnowledgePipline.get_instance().save_config()
|
||||||
|
if args[1] == "dir":
|
||||||
|
config = dict()
|
||||||
|
for key, item in KnowledgeDirSource.user_config_items():
|
||||||
|
user_input = await try_get_input(f"{key} : {item}")
|
||||||
|
if user_input is None:
|
||||||
|
return show_text
|
||||||
|
config[key] = user_input
|
||||||
|
error = KnowledgePipline.get_instance().add_dir_source(KnowledgeDirSource(config))
|
||||||
|
if error is not None:
|
||||||
|
return FormattedText([("class:title", f"/knowledge add dir failed {error}\n")])
|
||||||
|
else:
|
||||||
|
KnowledgePipline.get_instance().save_config()
|
||||||
|
else:
|
||||||
|
return show_text
|
||||||
|
if sub_cmd == "journal":
|
||||||
|
topn = 10 if len(args) == 1 else int(args[1])
|
||||||
|
journals = [str(journal) for journal in KnowledgePipline.get_instance().get_latest_journals(topn)]
|
||||||
|
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:
|
||||||
case 'send':
|
case 'send':
|
||||||
@@ -221,6 +273,8 @@ class AIOS_Shell:
|
|||||||
show_text = FormattedText([("class:title", f"connect to {tunnel_target} success!")])
|
show_text = FormattedText([("class:title", f"connect to {tunnel_target} success!")])
|
||||||
|
|
||||||
return show_text
|
return show_text
|
||||||
|
case 'knowledge':
|
||||||
|
return await self.handle_knowledge_commands(args)
|
||||||
case 'open':
|
case 'open':
|
||||||
if len(args) >= 1:
|
if len(args) >= 1:
|
||||||
target_id = args[0]
|
target_id = args[0]
|
||||||
@@ -400,6 +454,9 @@ async def main():
|
|||||||
'/history $num $offset',
|
'/history $num $offset',
|
||||||
'/login $username',
|
'/login $username',
|
||||||
'/connect $target',
|
'/connect $target',
|
||||||
|
'/knowledge add email | dir',
|
||||||
|
'/knowledge journal [$topn]',
|
||||||
|
'/knowledge query $query'
|
||||||
'/set_config $key',
|
'/set_config $key',
|
||||||
'/list_config',
|
'/list_config',
|
||||||
'/show',
|
'/show',
|
||||||
|
|||||||
Reference in New Issue
Block a user