From a0a45b89988ca8f3b60c4211a2b91d52f0f5f761 Mon Sep 17 00:00:00 2001 From: tsukasa Date: Thu, 21 Sep 2023 18:32:17 +0800 Subject: [PATCH] shell knowledge commands --- aios_shell_history.txt | 105 ++++++++ src/aios_kernel/__init__.py | 2 +- src/aios_kernel/knowledge_base.py | 9 +- src/aios_kernel/knowledge_pipeline.py | 270 +++++++++++++++---- src/knowledge/core_object/document_object.py | 4 +- src/knowledge/core_object/email_object.py | 2 +- src/knowledge/core_object/image_object.py | 2 +- src/knowledge/data/writer.py | 2 +- src/knowledge/store.py | 16 +- src/knowledge/vector/chroma_store.py | 6 +- src/service/aios_shell/aios_shell.py | 65 ++++- 11 files changed, 409 insertions(+), 74 deletions(-) diff --git a/aios_shell_history.txt b/aios_shell_history.txt index b0a8786..34732fd 100644 --- a/aios_shell_history.txt +++ b/aios_shell_history.txt @@ -4,3 +4,108 @@ # 2023-09-19 16:05:27.714815 +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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 diff --git a/src/aios_kernel/__init__.py b/src/aios_kernel/__init__.py index 6e6e000..7240b92 100644 --- a/src/aios_kernel/__init__.py +++ b/src/aios_kernel/__init__.py @@ -6,7 +6,7 @@ from .compute_kernel import ComputeKernel,ComputeTask from .compute_node import ComputeNode,LocalComputeNode from .open_ai_node import OpenAI_ComputeNode from .knowledge_base import KnowledgeBase -from .knowledge_pipeline import EmailSpider +from .knowledge_pipeline import KnowledgeEmailSource, KnowledgeDirSource, KnowledgePipline from .role import AIRole,AIRoleGroup from .workflow import Workflow from .bus import AIBus diff --git a/src/aios_kernel/knowledge_base.py b/src/aios_kernel/knowledge_base.py index 50423fe..11d6ce7 100644 --- a/src/aios_kernel/knowledge_base.py +++ b/src/aios_kernel/knowledge_base.py @@ -1,7 +1,9 @@ # define a knowledge base class import json import logging -from . import AgentPrompt, ComputeKernel, AIStorage +from .agent import AgentPrompt +from .compute_kernel import ComputeKernel +from .storage import AIStorage from knowledge import * @@ -17,7 +19,7 @@ class KnowledgeBase: def __singleton_init__(self) -> None: self.store = KnowledgeStore() - self.compute_kernel = ComputeKernel() + self.compute_kernel = ComputeKernel.get_instance() async def __embedding_document(self, document: DocumentObject): for chunk_id in document.get_chunk_list(): @@ -155,7 +157,7 @@ class KnowledgeBase: # pass 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) async def query_prompt(self, prompt: AgentPrompt): @@ -164,6 +166,7 @@ class KnowledgeBase: knowledge_prompt = self.prompt_from_objects(objects) logging.info(f"prompt_from_objects result: {knowledge_prompt.as_str()}") prompt.append(knowledge_prompt) + return prompt async def query_objects(self, prompt: AgentPrompt) -> [ObjectID]: results = [] diff --git a/src/aios_kernel/knowledge_pipeline.py b/src/aios_kernel/knowledge_pipeline.py index c60adad..c1f0a72 100644 --- a/src/aios_kernel/knowledge_pipeline.py +++ b/src/aios_kernel/knowledge_pipeline.py @@ -15,10 +15,10 @@ An example of a local file is as follows: │ └── dd-login-service-min.png """ - +import asyncio +import datetime +import sqlite3 import imaplib -import os -import toml import logging import mailparser import hashlib @@ -26,63 +26,139 @@ import json import base64 from bs4 import BeautifulSoup 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): - # logger config - self.logger = logging.getLogger('email spider') - self.logger.setLevel(logging.DEBUG) - ch = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s [%(name)s] [%(levelname)s] %(message)s') - ch.setFormatter(formatter) - self.logger.addHandler(ch) + knowledge_dir = os.path.join(AIStorage.get_instance().get_myai_dir(), "knowledge") + if not os.path.exists(knowledge_dir): + os.makedirs(knowledge_dir) + self.journal_path = os.path.join(knowledge_dir, "journal.db") + + conn = sqlite3.connect(self.journal_path) + 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 # 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() + await self.read_emails() 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( - host=self.config.get('EMAIL_IMAP_SERVER'), - port=self.config.get('EMAIL_IMAP_PORT') + host=self.config.get('imap_server'), + 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 - def list_box(self): - _, mailbox_list = self.client.list() - for mailbox in mailbox_list: - print(mailbox.decode()) - - def read_emails(self, folder: str = 'INBOX', imap_keyword: str = "UNSEEN"): + async def read_emails(self, folder: str = 'INBOX', imap_keyword: str = "UNSEEN"): self.client.select(folder) _, data = self.client.uid('search', None, imap_keyword) # get email uid list 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() for uid in email_list: if self.check_email_saved(uid): - self.logger.info(f"email uid {uid} already saved") + logging.info(f"email uid {uid} already saved") else: 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): message_parts = "(BODY.PEEK[])" _, email_data = self.client.uid('fetch', uid, message_parts) 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) 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 = hashlib.md5(name.encode('utf-8')).hexdigest() return f"{dir}/{name}" @@ -91,9 +167,9 @@ class EmailSpider: message_parts = "(BODY[HEADER])" _, email_data = self.client.uid('fetch', uid, message_parts) 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) - self.logger.info(f"check email saved {dir}") + logging.info(f"check email saved {dir}") file = f"{dir}/email.txt" if os.path.exists(file): return False @@ -116,7 +192,7 @@ class EmailSpider: image_data = image_data.encode() with open(filefullname, 'wb') as f: 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) def save_body_images(self, html_content: str, email_dir: str): @@ -124,7 +200,7 @@ class EmailSpider: soup = BeautifulSoup(html_content, 'html.parser') img_tags = soup.find_all('img') 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): os.makedirs(email_dir) @@ -138,17 +214,17 @@ class EmailSpider: with open(img_filename, 'wb') as img_file: for chunk in response.iter_content(1024): img_file.write(chunk) - self.logger.info(f'Downloaded {img_url} to {img_filename}') + logging.info(f'Downloaded {img_url} to {img_filename}') else: - self.logger.info(f'Failed to download {img_url}') + logging.info(f'Failed to download {img_url}') # save email content to local dir 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): os.makedirs(dir) 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): os.makedirs(email_dir) with open(f"{email_dir}/email.txt", "w") as f: @@ -158,14 +234,58 @@ class EmailSpider: if 'body' in mail_dict: del mail_dict['body'] 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_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 class KnowledgePipline: @@ -178,15 +298,71 @@ class KnowledgePipline: cls._instance.__singleton_init__() 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.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 - def declare_user_config(cls): - user_config = AIStorage.get_instance().get_user_config() - user_config.add_user_config("email_spiders","email addresses to build knowledge base",True,None,"list") - user_config.add_user_config("personal_dirs", "personal directories to build knowledge base", True, None, "list") + def __config_path(cls) -> str: + user_data_dir = AIStorage.get_instance().get_myai_dir() + return os.path.abspath(f"{user_data_dir}/etc/knowledge.cfg.toml") - 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() diff --git a/src/knowledge/core_object/document_object.py b/src/knowledge/core_object/document_object.py index 7210f16..672b4a3 100644 --- a/src/knowledge/core_object/document_object.py +++ b/src/knowledge/core_object/document_object.py @@ -49,7 +49,7 @@ class DocumentObjectBuilder: self.text = text 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( self.text, 1024 * 4, @@ -60,6 +60,6 @@ class DocumentObjectBuilder: # Add relation to store 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 diff --git a/src/knowledge/core_object/email_object.py b/src/knowledge/core_object/email_object.py index c86c3a2..37531ff 100644 --- a/src/knowledge/core_object/email_object.py +++ b/src/knowledge/core_object/email_object.py @@ -94,7 +94,7 @@ class EmailObjectBuilder: with open(content_file, "r", encoding="utf-8") as f: text = f.read() - document = DocumentObjectBuilder({}, {}, text).build(relation_store=relation) + document = DocumentObjectBuilder({}, {}, text).build() document_id = document.calculate_id() store.put_object(document_id, document.encode()) documents = {"email.txt": document_id} diff --git a/src/knowledge/core_object/image_object.py b/src/knowledge/core_object/image_object.py index 30b0e8a..2c02ad6 100644 --- a/src/knowledge/core_object/image_object.py +++ b/src/knowledge/core_object/image_object.py @@ -52,7 +52,7 @@ def get_exif_data(image_path: str): return { TAGS.get(key): exif_data[key] 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: return {} diff --git a/src/knowledge/data/writer.py b/src/knowledge/data/writer.py index 3ac0ed1..ad6c520 100644 --- a/src/knowledge/data/writer.py +++ b/src/knowledge/data/writer.py @@ -46,7 +46,7 @@ class ChunkListWriter: ) 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) diff --git a/src/knowledge/store.py b/src/knowledge/store.py index 03e48d0..56ec64b 100644 --- a/src/knowledge/store.py +++ b/src/knowledge/store.py @@ -4,7 +4,7 @@ from .object import ObjectStore, ObjectRelationStore from .data import ChunkStore, ChunkTracker, ChunkListWriter, ChunkReader from .vector import ChromaVectorStore, VectorBase 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 class KnowledgeStore: @@ -13,16 +13,12 @@ class KnowledgeStore: def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) - directory = os.path.join( - os.path.dirname(__file__), "../../rootfs/data/" - ) - directory = os.path.normpath(directory) - print(directory) + knowledge_dir = aios_kernel.storage.AIStorage().get_myai_dir() / "knowledge" - if not os.path.exists(directory): - os.makedirs(directory) + if not os.path.exists(knowledge_dir): + os.makedirs(knowledge_dir) - cls._instance.__singleton_init__(directory) + cls._instance.__singleton_init__(knowledge_dir) return cls._instance @@ -64,5 +60,5 @@ class KnowledgeStore: def get_vector_store(self, model_name: str) -> VectorBase: 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] diff --git a/src/knowledge/vector/chroma_store.py b/src/knowledge/vector/chroma_store.py index 03006ae..260a8e8 100644 --- a/src/knowledge/vector/chroma_store.py +++ b/src/knowledge/vector/chroma_store.py @@ -6,16 +6,14 @@ import os class ChromaVectorStore(VectorBase): - def __init__(self, model_name: str) -> None: + def __init__(self, root_dir, model_name: str) -> None: super().__init__(model_name) logging.info( "will init chroma vector store, model={}".format(model_name) ) - directory = os.path.join( - os.path.dirname(__file__), "../../../rootfs/data/vector" - ) + directory = os.path.join(root_dir, "vector") logging.info("will use vector store: {}".format(directory)) client = chromadb.PersistentClient( diff --git a/src/service/aios_shell/aios_shell.py b/src/service/aios_shell/aios_shell.py index f5b9dec..3577d28 100644 --- a/src/service/aios_shell/aios_shell.py +++ b/src/service/aios_shell/aios_shell.py @@ -22,8 +22,11 @@ from prompt_toolkit.styles import Style directory = os.path.dirname(__file__) 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 +from aios_kernel import * + + sys.path.append(directory + '/../../component/') from agent_manager import AgentManager @@ -115,7 +118,8 @@ class AIOS_Shell: await AgentTunnel.load_all_tunnels_from_config(tunnel_config) except Exception as e: logger.warning(f"load tunnels config from {tunnels_config_path} failed!") - + + KnowledgePipline.get_instance().initial() return True @@ -163,8 +167,7 @@ class AIOS_Shell: tunnel_config[key] = user_input - return tunnel_config - + return tunnel_config async def append_tunnel_config(self,tunnel_config): user_data_dir = AIStorage.get_instance().get_myai_dir() @@ -179,6 +182,55 @@ class AIOS_Shell: except Exception as e: 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): match func_name: case 'send': @@ -221,6 +273,8 @@ class AIOS_Shell: show_text = FormattedText([("class:title", f"connect to {tunnel_target} success!")]) return show_text + case 'knowledge': + return await self.handle_knowledge_commands(args) case 'open': if len(args) >= 1: target_id = args[0] @@ -400,6 +454,9 @@ async def main(): '/history $num $offset', '/login $username', '/connect $target', + '/knowledge add email | dir', + '/knowledge journal [$topn]', + '/knowledge query $query' '/set_config $key', '/list_config', '/show',