From 894f73c828b06fab58aa7a7acbc4a698226a9092 Mon Sep 17 00:00:00 2001 From: Alfredo Fomitchenko Date: Mon, 24 Apr 2023 20:57:10 +0200 Subject: [PATCH 1/2] Refactor with Black. --- remo.py | 20 ++-- utils.py | 275 +++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 211 insertions(+), 84 deletions(-) diff --git a/remo.py b/remo.py index f10ad9f..1bc1ed1 100644 --- a/remo.py +++ b/remo.py @@ -4,39 +4,47 @@ app = FastAPI() root_folder = os.getcwd() -#root_folder = 'C:/raven_private/REMO/' +# root_folder = 'C:/raven_private/REMO/' max_cluster_size = 5 # REMO = Rolling Episodic Memory Organizer + @app.post("/add_message") -async def add_message(message: str, speaker: str, timestamp: float): +async def add_message( + message: str, + speaker: str, + timestamp: float, +): # Add message to REMO new_message = utils.create_message(message, speaker, timestamp) - print('\n\nADD MESSAGE -', new_message) + print("\n\nADD MESSAGE -", new_message) utils.save_message(root_folder, new_message) return {"detail": "Message added"} + @app.get("/search") async def search(query: str): # Search the tree for relevant nodes - print('\n\nSEARCH -', query) + print("\n\nSEARCH -", query) taxonomy = utils.search_tree(root_folder, query) return {"results": taxonomy} + @app.post("/rebuild_tree") async def rebuild_tree(): # Trigger full tree rebuilding event - print('\n\nREBUILD TREE') + print("\n\nREBUILD TREE") utils.rebuild_tree(root_folder, max_cluster_size) return {"detail": "Tree rebuilding completed"} + @app.post("/maintain_tree") async def maintain_tree(): # Trigger tree maintenance event - print('\n\nMAINTAIN TREE') + print("\n\nMAINTAIN TREE") utils.maintain_tree(root_folder) return {"detail": "Tree maintenance completed"} diff --git a/utils.py b/utils.py index de0be3a..f9d4573 100644 --- a/utils.py +++ b/utils.py @@ -10,40 +10,51 @@ import tensorflow_hub as hub -embedding_model = hub.load("https://tfhub.dev/google/universal-sentence-encoder-large/5") +embedding_model = hub.load( + "https://tfhub.dev/google/universal-sentence-encoder-large/5" +) def open_file(filepath): - with open(filepath, 'r', encoding='utf-8', errors='ignore') as infile: + with open(filepath, "r", encoding="utf-8", errors="ignore") as infile: return infile.read() + def save_file(filepath, content): - with open(filepath, 'w', encoding='utf-8') as outfile: + with open(filepath, "w", encoding="utf-8") as outfile: outfile.write(content) + def save_yaml(filepath, data): - with open(filepath, 'w', encoding='utf-8') as file: + with open(filepath, "w", encoding="utf-8") as file: yaml.dump(data, file, allow_unicode=True) + def load_yaml(filepath): - with open(filepath, 'r', encoding='utf-8') as file: + with open(filepath, "r", encoding="utf-8") as file: data = yaml.load(file, Loader=yaml.FullLoader) return data -def create_message(message: str, speaker: str, timestamp: float) -> Dict[str, Any]: + +def create_message( + message: str, speaker: str, timestamp: float +) -> Dict[str, Any]: # Create message dictionary return {"content": message, "speaker": speaker, "timestamp": timestamp} + def save_message(root_folder, message: Dict[str, Any]): - timestamp, speaker = message['timestamp'], message['speaker'] + timestamp, speaker = message["timestamp"], message["speaker"] filename = f"chat_{timestamp}_{speaker}.yaml" filepath = os.path.join(root_folder, "L1_raw_logs", filename) os.makedirs(os.path.dirname(filepath), exist_ok=True) save_yaml(filepath, message) + def search_tree(root_folder, query): # TODO add a "forks" parameter to allow for branching relevance - # TODO add a "fuzziness" parameter that can generate a random vector to modify the search query + # TODO add a "fuzziness" parameter + # that can generate a random vector to modify the search query query_embedding = embedding_model([query]).numpy() level = 6 taxonomy = [] @@ -55,13 +66,23 @@ def search_tree(root_folder, query): level -= 1 while level > 2: - level_files = [os.path.join(level_dir, f) for f in os.listdir(level_dir) if f.endswith(".yaml")] + level_files = [ + os.path.join(level_dir, f) + for f in os.listdir(level_dir) + if f.endswith(".yaml") + ] max_similarity = -1 closest_file = None for file in level_files: data = load_yaml(file) - similarity = cosine_similarity(query_embedding, np.array(data["vector"]).reshape(1, -1))[0][0] + similarity = ( + cosine_similarity( + query_embedding, + np.array(data["vector"]).reshape(1, -1), + ) + [0][0] + ) if similarity > max_similarity: max_similarity = similarity @@ -98,22 +119,34 @@ def rebuild_tree(root_folder: str, max_cluster_size: int = 10): process_missing_messages(root_folder) # Cluster L2 message pairs using cosine similarity, up to 10 per cluster - clusters = cluster_elements(root_folder, "L2_message_pairs", max_cluster_size) + clusters = cluster_elements( + root_folder, "L2_message_pairs", max_cluster_size + ) # Create summaries and save them in the next rank (L3_summaries) - create_summaries(root_folder, clusters, f"L3_summaries", "L2_message_pairs") + create_summaries( + root_folder, clusters, f"L3_summaries", "L2_message_pairs" + ) - # If top rank (e.g. L3_summaries) has > max_cluster_size files, repeat process, creating new taxonomical ranks + # If top rank (e.g. L3_summaries) has > max_cluster_size files, + # repeat process, creating new taxonomical ranks current_rank = 3 while True: # calculate clusters at new rank - clusters = cluster_elements(root_folder, f"L{current_rank}_summaries", max_cluster_size) - + clusters = cluster_elements( + root_folder, f"L{current_rank}_summaries", max_cluster_size + ) + # summarize those clusters - create_summaries(root_folder, clusters, f"L{current_rank + 1}_summaries", f"L{current_rank}_summaries") + create_summaries( + root_folder, + clusters, + f"L{current_rank + 1}_summaries", + f"L{current_rank}_summaries", + ) current_rank += 1 - - # if clusters less than max cluster size, we are done :) + + # if clusters less than max cluster size, we are done :) if len(clusters) <= max_cluster_size: break @@ -141,26 +174,34 @@ def process_missing_messages(root_folder: str): file1_data = load_yaml(file1_path) file2_data = load_yaml(file2_path) - context = file1_data['content'] - response = file2_data['content'] - speaker = file2_data['speaker'] - timestamp = file2_data['timestamp'] + context = file1_data["content"] + response = file2_data["content"] + speaker = file2_data["speaker"] + timestamp = file2_data["timestamp"] combined_text = context + " --- " + response embedding = embedding_model([combined_text]).numpy().tolist() message_pair_data = { - 'content': combined_text, - 'speaker': speaker, - 'timestamp': timestamp, - 'vector': embedding + "content": combined_text, + "speaker": speaker, + "timestamp": timestamp, + "vector": embedding, } # Save message pair in L2_message_pairs folder - message_pair_path = os.path.join(message_pairs_dir, message_pair_filename) + message_pair_path = os.path.join( + message_pairs_dir, message_pair_filename + ) save_yaml(message_pair_path, message_pair_data) -def create_summaries(root_folder: str, clusters: List[List[str]], target_folder: str, source_folder: str): + +def create_summaries( + root_folder: str, + clusters: List[List[str]], + target_folder: str, + source_folder: str, +): source_folder_path = os.path.join(root_folder, source_folder) target_folder_path = os.path.join(root_folder, target_folder) os.makedirs(target_folder_path, exist_ok=True) @@ -186,7 +227,7 @@ def create_summaries(root_folder: str, clusters: List[List[str]], target_folder: "content": summary, "vector": summary_embedding, "files": files, - "timestamp": time() + "timestamp": time(), } timestamp = time() @@ -195,9 +236,15 @@ def create_summaries(root_folder: str, clusters: List[List[str]], target_folder: save_yaml(summary_filepath, summary_data) -def cluster_elements(root_folder: str, target_folder: str, max_cluster_size: int = 10) -> List[List[str]]: +def cluster_elements( + root_folder: str, target_folder: str, max_cluster_size: int = 10 +) -> List[List[str]]: folder_path = os.path.join(root_folder, target_folder) - yaml_files = [f for f in os.listdir(folder_path) if f.endswith(".yaml")] + yaml_files = [ + f + for f in os.listdir(folder_path) + if f.endswith(".yaml") + ] # Load vectors vectors = [] @@ -219,84 +266,120 @@ def cluster_elements(root_folder: str, target_folder: str, max_cluster_size: int return clusters + def maintain_tree(root_folder: str): l2_message_pairs_dir = os.path.join(root_folder, "L2_message_pairs") - + # Create L2 directory if it does not exist if not os.path.exists(l2_message_pairs_dir): os.makedirs(l2_message_pairs_dir) - + # Get list of files in L2 before processing missing messages l2_files_before = set(os.listdir(l2_message_pairs_dir)) - + # Process missing messages to generate new message pairs in L2 process_missing_messages(root_folder) - + # Get list of files in L2 after processing missing messages l2_files_after = set(os.listdir(l2_message_pairs_dir)) - - # Calculate the difference between the two lists to obtain the new message pairs - new_message_pairs = l2_files_after - l2_files_before - #new_message_pairs = [os.path.join("L2_message_pairs", f) for f in l2_files_after - l2_files_before] - - # Iterate through new files in L2 and check cosine similarity to files in L3 - integrate_new_elements(root_folder, "L3_summaries", new_message_pairs, 0.75) -def integrate_new_elements(root_folder: str, target_folder: str, new_elements: List[str], threshold: float): + # Calculate the difference between the two lists + # to obtain the new message pairs + new_message_pairs = l2_files_after - l2_files_before + # new_message_pairs = [ + # os.path.join("L2_message_pairs", f) + # for f in l2_files_after - l2_files_before + # ] + + # Iterate through new files in L2 + # and check cosine similarity to files in L3 + integrate_new_elements( + root_folder, "L3_summaries", new_message_pairs, 0.75 + ) + + +def integrate_new_elements( + root_folder: str, + target_folder: str, + new_elements: List[str], + threshold: float, +): target_dir = os.path.join(root_folder, target_folder) - + # Create target directory if it does not exist if not os.path.exists(target_dir): os.makedirs(target_dir) - + for new_element in new_elements: - new_element_path = os.path.join(root_folder, "L2_message_pairs", new_element) + new_element_path = os.path.join( + root_folder, "L2_message_pairs", new_element + ) new_element_data = load_yaml(new_element_path) - new_element_vector = np.array(new_element_data["vector"]).reshape(1, -1) - + new_element_vector = ( + np.array(new_element_data["vector"]) + .reshape(1, -1) + ) + max_similarity = -1 closest_file = None - + for file in os.listdir(target_dir): file_path = os.path.join(target_dir, file) file_data = load_yaml(file_path) file_vector = np.array(file_data["vector"]).reshape(1, -1) + + similarity = ( + cosine_similarity(new_element_vector, file_vector) + [0][0] + ) - similarity = cosine_similarity(new_element_vector, file_vector)[0][0] if similarity > max_similarity: max_similarity = similarity closest_file = file - + if max_similarity > threshold: - # Update the corresponding summary and record the name of the modified file + # Update the corresponding summary + # and record the name of the modified file closest_file_path = os.path.join(target_dir, closest_file) closest_file_data = load_yaml(closest_file_path) closest_file_data["files"].append(new_element) - - combined_content = closest_file_data["content"] + " --- " + new_element_data["content"] + + combined_content = ( + closest_file_data["content"] + + " --- " + + new_element_data["content"] + ) updated_summary = quick_summarize(combined_content) - updated_summary_embedding = embedding_model([updated_summary]).numpy().tolist() - + updated_summary_embedding = ( + embedding_model([updated_summary]).numpy().tolist() + ) + closest_file_data["content"] = updated_summary closest_file_data["vector"] = updated_summary_embedding closest_file_data["timestamp"] = time() - + save_yaml(closest_file_path, closest_file_data) else: # Create a new summary for the new_element combined_content = new_element_data["content"] new_summary = quick_summarize(combined_content) - new_summary_embedding = embedding_model([new_summary]).numpy().tolist() - + new_summary_embedding = ( + embedding_model([new_summary]).numpy().tolist() + ) + new_summary_data = { "content": new_summary, "vector": new_summary_embedding, "files": [new_element], - "timestamp": time() + "timestamp": time(), } - - new_summary_filename = f"summary_{len(os.listdir(target_dir))}.yaml" - new_summary_filepath = os.path.join(target_dir, new_summary_filename) + + new_summary_filename = ( + f"summary_{len(os.listdir(target_dir))}.yaml" + ) + new_summary_filepath = os.path.join( + target_dir, new_summary_filename + ) save_yaml(new_summary_filepath, new_summary_data) @@ -304,19 +387,35 @@ def quick_summarize(text): max_chunk_size = 10000 if len(text) <= max_chunk_size: - prompt = 'Write a detailed summary of the following:\n\n%s\n\nDETAILED SUMMARY:' % text + prompt = ( + ( + "Write a detailed summary of the following:" + "\n\n%s" + "\n\nDETAILED SUMMARY:" + ) + % text + ) response = gpt3_completion(prompt) return response else: # Split the text into evenly sized chunks num_chunks = int(np.ceil(len(text) / max_chunk_size)) chunk_size = int(np.ceil(len(text) / num_chunks)) - text_chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)] + text_chunks = [ + text[i : i + chunk_size] for i in range(0, len(text), chunk_size) + ] # Summarize each chunk summaries = [] for chunk in text_chunks: - prompt = 'Write a detailed summary of the following:\n\n%s\n\nDETAILED SUMMARY:' % chunk + prompt = ( + ( + "Write a detailed summary of the following:" + "\n\n%s" + "\n\nDETAILED SUMMARY:" + ) + % chunk + ) response = gpt3_completion(prompt) summaries.append(response) @@ -325,12 +424,24 @@ def quick_summarize(text): return final_summary -def gpt3_completion(prompt, engine='text-davinci-003', temp=0.0, top_p=1.0, tokens=1000, freq_pen=0.0, pres_pen=0.0, stop=['asdfasdfasdf']): - openai.api_key = open_file('key_openai.txt') +def gpt3_completion( + prompt, + engine="text-davinci-003", + temp=0.0, + top_p=1.0, + tokens=1000, + freq_pen=0.0, + pres_pen=0.0, + stop=["asdfasdfasdf"], +): + openai.api_key = open_file("key_openai.txt") + max_retry = 5 retry = 0 - prompt = prompt.encode(encoding='ASCII',errors='ignore').decode() + prompt = prompt.encode(encoding="ASCII", errors="ignore").decode() + while True: + try: response = openai.Completion.create( engine=engine, @@ -340,18 +451,26 @@ def gpt3_completion(prompt, engine='text-davinci-003', temp=0.0, top_p=1.0, toke top_p=top_p, frequency_penalty=freq_pen, presence_penalty=pres_pen, - stop=stop) - text = response['choices'][0]['text'].strip() - #text = re.sub('[\r\n]+', '\n', text) - #text = re.sub('[\t ]+', ' ', text) - filename = '%s_gpt3.txt' % time() - if not os.path.exists('gpt3_logs'): - os.makedirs('gpt3_logs') - save_file('gpt3_logs/%s' % filename, prompt + '\n\n==========\n\n' + text) + stop=stop, + ) + text = response["choices"][0]["text"].strip() + # text = re.sub('[\r\n]+', '\n', text) + # text = re.sub('[\t ]+', ' ', text) + + filename = "%s_gpt3.txt" % time() + if not os.path.exists("gpt3_logs"): + os.makedirs("gpt3_logs") + + save_file( + "gpt3_logs/%s" % filename, + prompt + "\n\n==========\n\n" + text, + ) + return text + except Exception as oops: retry += 1 if retry >= max_retry: return "GPT3 error: %s" % oops - print('Error communicating with OpenAI:', oops) + print("Error communicating with OpenAI:", oops) sleep(1) From 482534a24b62327f4f6f7b2876ed66a6fe7f4846 Mon Sep 17 00:00:00 2001 From: Alfredo Fomitchenko Date: Tue, 25 Apr 2023 08:57:12 +0200 Subject: [PATCH 2/2] Add option for local testing. --- .gitignore | 4 +++ README.md | 44 ++++++++++++++++++++++++++++++ docs/images/embedding_local_1.png | Bin 0 -> 59286 bytes remo.py | 5 ++++ utils.py | 20 ++++++++++++-- 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 docs/images/embedding_local_1.png diff --git a/.gitignore b/.gitignore index b3d2c52..58e3177 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,8 @@ var/ # pytest *pytest_cache +# Credentials key_openai.txt + +# Models saved locally +models/ diff --git a/README.md b/README.md index 2d4053b..e46fde3 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,50 @@ To run REMO, you will need the following: 2. Interact with the API using a REST client or web browser: `http://localhost:8000` +## Models + +### Embedding Model + +REMO currently uses the +Universal Sentence Encoder v5 for generating embeddings. + +#### Loading from TensorFlow Hub + +This is the default option. + +When + +```python +ARE_YOU_TESTING__LOAD_MODEL_LOCAL = False +``` + +in file `utils.py`, the model is loaded from TensorFlow Hub. + +#### Loading from a local file + +Downloading the model from TensorFlow Hub every time you need to spin up +the microservice would be expensive and time-consuming. + +1. Download the `.tar.gz` file from + TensorFlow Hub: https://tfhub.dev/google/universal-sentence-encoder-large/5 + + ![img.png](docs/images/embedding_local_1.png) + +2. Extract the file to the folder + ``` + models/universal-sentence-encoder-large_5/ + ``` + with + ```shell + tar -xvzf universal-sentence-encoder-large_5.tar.gz + ``` + +3. Set + ```python + ARE_YOU_TESTING__LOAD_MODEL_LOCAL = True + ``` + in file `utils.py`. + ## API Endpoints - **POST /add_message**: Add a new message to REMO. Speaker, timestamp, and content required. diff --git a/docs/images/embedding_local_1.png b/docs/images/embedding_local_1.png new file mode 100644 index 0000000000000000000000000000000000000000..c49cefe5d14af3c5a64a04d877884c595131c8c6 GIT binary patch literal 59286 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
=(*8|5gC&{Yc1tl8IU0K>HJ26 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 literal 0 HcmV?d00001 diff --git a/remo.py b/remo.py index 1bc1ed1..3aae363 100644 --- a/remo.py +++ b/remo.py @@ -1,6 +1,7 @@ from fastapi import FastAPI import utils import os +import uvicorn app = FastAPI() root_folder = os.getcwd() @@ -48,3 +49,7 @@ async def maintain_tree(): utils.maintain_tree(root_folder) return {"detail": "Tree maintenance completed"} + + +if __name__ == '__main__': + uvicorn.run(app, host='0.0.0.0', port=8000) diff --git a/utils.py b/utils.py index f9d4573..1ccd7be 100644 --- a/utils.py +++ b/utils.py @@ -1,4 +1,6 @@ import os +from pathlib import Path + import yaml import shutil import openai @@ -7,12 +9,24 @@ from typing import Dict, Any, List from sklearn.metrics.pairwise import cosine_similarity from sklearn.cluster import KMeans +import tensorflow as tf import tensorflow_hub as hub -embedding_model = hub.load( - "https://tfhub.dev/google/universal-sentence-encoder-large/5" -) +ARE_YOU_TESTING__LOAD_MODEL_LOCAL = True + +ROOT_REPO_PATH = Path().parent.absolute() + +if ARE_YOU_TESTING__LOAD_MODEL_LOCAL: + embedding_model = tf.saved_model.load( + ROOT_REPO_PATH + / "models/universal-sentence-encoder-large_5" + ) + +else: + embedding_model = hub.load( + "https://tfhub.dev/google/universal-sentence-encoder-large/5" + ) def open_file(filepath):