From f7f283ae6365ca56f6bd74656aa7d061769350a9 Mon Sep 17 00:00:00 2001 From: seachellemz Date: Tue, 30 Jul 2024 00:27:50 -0400 Subject: [PATCH 1/5] Refactored and add slack_loader --- .gitignore | 2 +- ai_local_rag/__init__.py | 0 api.py => ai_local_rag/api.py | 10 +- ai_local_rag/chroma/__init__.py | 0 ai_local_rag/chroma/chroma_client.py | 36 ++++ .../chroma/populate_database.py | 12 +- .../chroma/reset_database.py | 3 +- ai_local_rag/chroma/slack_loader.py | 194 ++++++++++++++++++ .../models}/rag_query_model.py | 0 query_data.py => ai_local_rag/query_data.py | 2 +- ai_local_rag/utils/__init__.py | 0 {utils => ai_local_rag/utils}/async_utils.py | 0 ai_local_rag/utils/get_embedding_function.py | 55 +++++ get_embedding_function.py | 14 -- requirements.txt | 19 +- .../data_boardgames}/monopoly.pdf | Bin .../data_boardgames}/ticket_to_ride.pdf | Bin .../MachineLearning-Lecture01.pdf | Bin .../MachineLearning-Lecture02.pdf | Bin .../MachineLearning-Lecture03.pdf | Bin .../MachineLearning-Lecture04.pdf | Bin .../MachineLearning-Lecture05.pdf | Bin .../MachineLearning-Lecture06.pdf | Bin ...Slack export Jul 25 2024 - Jul 26 2024.zip | Bin 0 -> 98023 bytes test_rag.py | 8 +- 25 files changed, 323 insertions(+), 32 deletions(-) create mode 100644 ai_local_rag/__init__.py rename api.py => ai_local_rag/api.py (92%) create mode 100644 ai_local_rag/chroma/__init__.py create mode 100644 ai_local_rag/chroma/chroma_client.py rename populate_database.py => ai_local_rag/chroma/populate_database.py (98%) rename reset_database.py => ai_local_rag/chroma/reset_database.py (80%) create mode 100644 ai_local_rag/chroma/slack_loader.py rename {models => ai_local_rag/models}/rag_query_model.py (100%) rename query_data.py => ai_local_rag/query_data.py (96%) create mode 100644 ai_local_rag/utils/__init__.py rename {utils => ai_local_rag/utils}/async_utils.py (100%) create mode 100644 ai_local_rag/utils/get_embedding_function.py delete mode 100644 get_embedding_function.py rename {data_boardgames => source_data/data_boardgames}/monopoly.pdf (100%) rename {data_boardgames => source_data/data_boardgames}/ticket_to_ride.pdf (100%) rename {data_machine_learning_lectures => source_data/data_machine_learning_lectures}/MachineLearning-Lecture01.pdf (100%) rename {data_machine_learning_lectures => source_data/data_machine_learning_lectures}/MachineLearning-Lecture02.pdf (100%) rename {data_machine_learning_lectures => source_data/data_machine_learning_lectures}/MachineLearning-Lecture03.pdf (100%) rename {data_machine_learning_lectures => source_data/data_machine_learning_lectures}/MachineLearning-Lecture04.pdf (100%) rename {data_machine_learning_lectures => source_data/data_machine_learning_lectures}/MachineLearning-Lecture05.pdf (100%) rename {data_machine_learning_lectures => source_data/data_machine_learning_lectures}/MachineLearning-Lecture06.pdf (100%) create mode 100644 source_data/data_slack/WhatsCookinTeam Slack export Jul 25 2024 - Jul 26 2024.zip diff --git a/.gitignore b/.gitignore index a1987fe..8ffc2ab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ .vscode/ .vscode/launch.json backup -chroma_boardgames/ +vector_store/ env/ .env __pycache__ diff --git a/ai_local_rag/__init__.py b/ai_local_rag/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api.py b/ai_local_rag/api.py similarity index 92% rename from api.py rename to ai_local_rag/api.py index 2f3627a..6fbb94c 100644 --- a/api.py +++ b/ai_local_rag/api.py @@ -1,11 +1,13 @@ import logging -from query_data import query_rag -from fastapi import FastAPI, Request, HTTPException, status + +from fastapi import FastAPI, HTTPException, Request, status from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from pydantic import BaseModel -from models.rag_query_model import QueryInput, QueryOutput -from utils.async_utils import async_retry + +from ai_local_rag.models.rag_query_model import QueryInput, QueryOutput +from ai_local_rag.query_data import query_rag +from ai_local_rag.utils.async_utils import async_retry class Message(BaseModel): diff --git a/ai_local_rag/chroma/__init__.py b/ai_local_rag/chroma/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ai_local_rag/chroma/chroma_client.py b/ai_local_rag/chroma/chroma_client.py new file mode 100644 index 0000000..bbdbdd9 --- /dev/null +++ b/ai_local_rag/chroma/chroma_client.py @@ -0,0 +1,36 @@ +from chromadb.config import Settings +import chromadb +import logging +import os + +import dotenv +from langchain.vectorstores import Chroma + +from ai_local_rag.utils.get_embedding_function import get_embedding_function + + +dotenv.load_dotenv() + + +logging.basicConfig() +logger = logging.getLogger("chroma_client") +logger.setLevel(logging.DEBUG) + + +chroma_path = os.getenv("CHROMA_PATH_SLACK") +collection_name = os.getenv("CHROMA_SLACK_COLLECTION") + + +client = chromadb.Client(Settings( + persist_directory=chroma_path +)) + +logger.info(f"Number of collections: {client.count_collections()}") + +logger.info(client.list_collections()) +""" +collection = client.get_collection(name=collection_name) +result = collection.query(n_results=5) +logging.info(result) +logging.info(len(client.list_collections())) +""" diff --git a/populate_database.py b/ai_local_rag/chroma/populate_database.py similarity index 98% rename from populate_database.py rename to ai_local_rag/chroma/populate_database.py index 8e07dd7..2778c0e 100644 --- a/populate_database.py +++ b/ai_local_rag/chroma/populate_database.py @@ -1,16 +1,16 @@ import argparse import os -from dotenv import load_dotenv - - import shutil + +from dotenv import load_dotenv +from langchain.schema.document import Document # from langchain.document_loaders.pdf import PyPDFDirectoryLoader from langchain_community.document_loaders import PyPDFDirectoryLoader -from langchain_text_splitters import RecursiveCharacterTextSplitter -from langchain.schema.document import Document -from get_embedding_function import get_embedding_function # from langchain.vectorstores.chroma import Chroma from langchain_community.vectorstores import Chroma +from langchain_text_splitters import RecursiveCharacterTextSplitter + +from ai_local_rag.utils.get_embedding_function import get_embedding_function # Load Config Settings load_dotenv() # take environment variables from .env. diff --git a/reset_database.py b/ai_local_rag/chroma/reset_database.py similarity index 80% rename from reset_database.py rename to ai_local_rag/chroma/reset_database.py index 50dc21c..5b78748 100644 --- a/reset_database.py +++ b/ai_local_rag/chroma/reset_database.py @@ -1,6 +1,6 @@ import os from dotenv import load_dotenv -import populate_database +import ai_local_rag.chroma.populate_database as populate_database # Load Config Settings load_dotenv() # take environment variables from .env. @@ -8,4 +8,3 @@ populate_database.clear_database() print(f"Removed all content from database {CHROMA_PATH}") - diff --git a/ai_local_rag/chroma/slack_loader.py b/ai_local_rag/chroma/slack_loader.py new file mode 100644 index 0000000..03c7f76 --- /dev/null +++ b/ai_local_rag/chroma/slack_loader.py @@ -0,0 +1,194 @@ +""" +Loader for slack +""" +import os + +import dotenv +import logging +from dotenv import load_dotenv +import chromadb +from chromadb.config import Settings +from langchain import hub +from langchain.agents import AgentExecutor, create_openai_tools_agent +from langchain.schema.document import Document +from langchain_text_splitters import RecursiveCharacterTextSplitter +from langchain_community.agent_toolkits import SlackToolkit +from langchain_community.chat_models import ChatOllama +from langchain_community.document_loaders import SlackDirectoryLoader +from langchain_community.vectorstores import Chroma +from langchain_community.embeddings.ollama import OllamaEmbeddings +from ai_local_rag.utils.get_embedding_function import get_embedding_function, CustomOpenAIEmbeddings, CustomOllamaEmbeddings + +dotenv.load_dotenv() +logging.basicConfig() +logger = logging.getLogger("slack_loader") +logger.setLevel(logging.DEBUG) + + +def slack_toolkit(): + toolkit = SlackToolkit() + my_tools = toolkit.get_tools() + + # llm = ChatOpenAI(temperature=0, model="gpt-4") + llm = ChatOllama(model="mistral") + + prompt = hub.pull("hwchase17/openai-tools-agent") + agent = create_openai_tools_agent( + tools=my_tools, + llm=llm, + prompt=prompt, + ) + agent_executor = AgentExecutor(agent=agent, tools=my_tools, verbose=True) + agent_executor.invoke( + { + "input": "Send a greeting to my coworkers in the #general channel. Note use `channel` as key of channel id, and `message` as key of content to sent in the channel." + } + ) + agent_executor.invoke( + {"input": "How many channels are in the workspace? Please list out their names."} + ) + agent_executor.invoke( + { + "input": "Tell me the number of messages sent in the #introductions channel from the past month." + } + ) + + +def _slack_loader(): + local_zip_file = os.getenv("SLACK_EXPORT_ZIP") + slack_workspace_url = os.getenv("SLACK_WORKSPACE_URL") + + loader = SlackDirectoryLoader(local_zip_file, slack_workspace_url) + docs = loader.load() + logger.info(f"Slack export contains {len(docs)} docs") + return docs + + +def _split_documents(documents: list[Document]): + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=800, + chunk_overlap=80, + length_function=len, + is_separator_regex=False, + ) + return text_splitter.split_documents(documents) + + +def _print_chunks(chunks): + i = 0 + for chunk in chunks: + logger.info(f"chunk {i} contains: {chunk}\n") + i += 1 + if i > 5: + break + + +def _calculate_chunk_ids(chunks: list[Document]): + + # This will create IDs like "c-linkedtrust:U069UCY6WPL:1721902091.115329:2" + # Channel : UserId: Timestamp: Chunk Index + + last_page_id = None + current_chunk_index = 0 + + response = {} + chunk_id_list = [] + metadata_list = [] + page_content = [] + + for chunk in chunks: + user = chunk.metadata.get("user") + channel = chunk.metadata.get("channel") + timestamp = chunk.metadata.get("timestamp") + current_page_id = f"{channel}:{user}:{timestamp}" + + page_content.append(chunk.page_content) + + # If the page ID is the same as the last one, increment the index. + if current_page_id == last_page_id: + current_chunk_index += 1 + else: + current_chunk_index = 0 + + # Calculate the chunk ID. + chunk_id = f"{current_page_id}:{current_chunk_index}" + last_page_id = current_page_id + + # Add the id to the page meta-data. + chunk.metadata["id"] = chunk_id + + # Add the metadata to the metadata list + metadata_list.append(chunk.metadata) + + # Add it to the list of ids + chunk_id_list.append(chunk_id) + + response["chunks"] = page_content + response["chunk_ids"] = chunk_id_list + response["metadata"] = metadata_list + + # return chunks + return response + + +def _add_to_chroma_with_langchain(chunks: list[Document]): + chroma_path = os.getenv("CHROMA_PATH_SLACK") + chroma_collection = os.getenv("CHROMA_SLACK_COLLECTION") + logger.info(f"_add_to_chroma - collection name: {chroma_collection}") + # Load the existing database. + db = Chroma(collection_name=chroma_collection, + persist_directory=chroma_path, embedding_function=get_embedding_function() + ) + + db.persist() + + +def _add_to_chroma(chunks_with_ids: list[Document]): + chroma_path = os.getenv("CHROMA_PATH_SLACK") + chroma_collection = os.getenv("CHROMA_SLACK_COLLECTION") + + chroma_client = chromadb.PersistentClient(path=chroma_path) + # settings=Settings(chroma_db_impl="duckdb+parquet")) + + logger.info(f"_add_to_chroma - collection name: {chroma_collection}") + + chroma_client = chromadb.Client() + embedding_function = get_embedding_function() + collection = chroma_client.get_or_create_collection( + name=chroma_collection, embedding_function=embedding_function + ) + + # Calculate Page IDs. + # chunks_with_ids = _calculate_chunk_ids(chunks) + + # collection = chroma_client.create_collection(name=chroma_collection) + + collection.add(ids=chunks_with_ids["chunk_ids"], + metadatas=chunks_with_ids["metadata"], + documents=chunks_with_ids["chunks"]) + + results = collection.query( + # Chroma will embed this for you + query_texts=["This is a query document about c-linked-trust"], + n_results=2 # how many results to return + ) + + logger.info("QUERY RESULTS") + logger.info(results) + + +def main(): + # Load Config Settings + logger.info("STARTING") + load_dotenv() # take environment variables from .env. + documents = _slack_loader() + chunks = _split_documents(documents) + _print_chunks(chunks) + chunks = _calculate_chunk_ids(chunks) + _print_chunks(chunks) + _add_to_chroma(chunks) + logger.info("FINISHED") + + +if __name__ == "__main__": + main() diff --git a/models/rag_query_model.py b/ai_local_rag/models/rag_query_model.py similarity index 100% rename from models/rag_query_model.py rename to ai_local_rag/models/rag_query_model.py diff --git a/query_data.py b/ai_local_rag/query_data.py similarity index 96% rename from query_data.py rename to ai_local_rag/query_data.py index eea12be..a2c9a55 100644 --- a/query_data.py +++ b/ai_local_rag/query_data.py @@ -6,7 +6,7 @@ from langchain.prompts import ChatPromptTemplate from langchain_community.llms.ollama import Ollama -from get_embedding_function import get_embedding_function +from ai_local_rag.utils.get_embedding_function import get_embedding_function # Load Config Settings load_dotenv() # take environment variables from .env. diff --git a/ai_local_rag/utils/__init__.py b/ai_local_rag/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/async_utils.py b/ai_local_rag/utils/async_utils.py similarity index 100% rename from utils/async_utils.py rename to ai_local_rag/utils/async_utils.py diff --git a/ai_local_rag/utils/get_embedding_function.py b/ai_local_rag/utils/get_embedding_function.py new file mode 100644 index 0000000..01ed213 --- /dev/null +++ b/ai_local_rag/utils/get_embedding_function.py @@ -0,0 +1,55 @@ +from langchain_community.embeddings.ollama import OllamaEmbeddings +from langchain_openai import OpenAIEmbeddings +from chromadb.utils import embedding_functions +# from langchain_community.embeddings.bedrock import BedrockEmbeddings +# from langchain_community.embeddings import FastEmbedEmbeddings + + +def get_embedding_function(): + # embeddings = BedrockEmbeddings( + # credentials_profile_name="default", region_name="us-east-1" + # ) + + # Make sure you run this first: ollama pull nomic-embed-text + # embeddings = OllamaEmbeddings(model="nomic-embed-text") + + # embeddings = embedding_functions.DefaultEmbeddingFunction() + embeddings = embedding_functions.SentenceTransformerEmbeddingFunction( + model_name="all-MiniLM-L6-v2") + # embeddings = FastEmbedEmbeddings() + return embeddings + + +class CustomOpenAIEmbeddings(OpenAIEmbeddings): + + def __init__(self, openai_api_key, *args, **kwargs): + super().__init__(openai_api_key=openai_api_key, *args, **kwargs) + + def _embed_documents(self, texts): + embeddings = [ + self.client.create( + input=text, model="text-embedding-ada-002").data[0].embedding + for text in texts + ] + return embeddings + + def __call__(self, input): + return self._embed_documents(input) + + +class CustomOllamaEmbeddings(OllamaEmbeddings): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _embed_documents(self, texts): + embeddings = [ + self. + self.client.create( + input=text, model="nomic-embed-text").data[0].embedding + for text in texts + ] + return embeddings + + def __call__(self, input): + return self._embed_documents(input) diff --git a/get_embedding_function.py b/get_embedding_function.py deleted file mode 100644 index cd9563e..0000000 --- a/get_embedding_function.py +++ /dev/null @@ -1,14 +0,0 @@ -from langchain_community.embeddings.ollama import OllamaEmbeddings -# from langchain_community.embeddings.bedrock import BedrockEmbeddings -# from langchain_community.embeddings import FastEmbedEmbeddings - - -def get_embedding_function(): - # embeddings = BedrockEmbeddings( - # credentials_profile_name="default", region_name="us-east-1" - # ) - - # Make sure you run this first: ollama pull nomic-embed-text - embeddings = OllamaEmbeddings(model="nomic-embed-text") - # embeddings = FastEmbedEmbeddings() - return embeddings diff --git a/requirements.txt b/requirements.txt index 18d9d39..9b42277 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ click==8.1.7 coloredlogs==15.0.1 dataclasses-json==0.6.7 Deprecated==1.2.14 +distro==1.9.0 dnspython==2.6.1 email_validator==2.2.0 exceptiongroup==1.2.2 @@ -44,13 +45,16 @@ importlib_resources==6.4.0 iniconfig==2.0.0 Jinja2==3.1.4 jmespath==1.0.1 +joblib==1.4.2 jsonpatch==1.33 jsonpointer==3.0.0 kubernetes==30.1.0 langchain==0.2.10 langchain-community==0.2.9 -langchain-core==0.2.22 +langchain-core==0.2.24 +langchain-openai==0.1.19 langchain-text-splitters==0.2.2 +langchainhub==0.1.20 langsmith==0.1.93 loguru==0.7.2 markdown-it-py==3.0.0 @@ -62,10 +66,12 @@ monotonic==1.6 mpmath==1.3.0 multidict==6.0.5 mypy-extensions==1.0.0 +networkx==3.3 numpy==1.26.4 oauthlib==3.2.2 onnx==1.16.1 onnxruntime==1.18.1 +openai==1.37.1 opentelemetry-api==1.25.0 opentelemetry-exporter-otlp-proto-common==1.25.0 opentelemetry-exporter-otlp-proto-grpc==1.25.0 @@ -97,23 +103,34 @@ python-dateutil==2.9.0.post0 python-dotenv==1.0.1 python-multipart==0.0.9 PyYAML==6.0.1 +regex==2024.7.24 requests==2.32.3 requests-oauthlib==2.0.0 rich==13.7.1 rsa==4.9 s3transfer==0.10.2 +safetensors==0.4.3 +scikit-learn==1.5.1 +scipy==1.14.0 +sentence-transformers==3.0.1 shellingham==1.5.4 six==1.16.0 +slack_sdk==3.31.0 sniffio==1.3.1 snowballstemmer==2.2.0 SQLAlchemy==2.0.31 starlette==0.37.2 sympy==1.13.1 tenacity==8.5.0 +threadpoolctl==3.5.0 +tiktoken==0.7.0 tokenizers==0.19.1 tomli==2.0.1 +torch==2.4.0 tqdm==4.66.4 +transformers==4.43.3 typer==0.12.3 +types-requests==2.32.0.20240712 typing-inspect==0.9.0 typing_extensions==4.12.2 urllib3==2.2.2 diff --git a/data_boardgames/monopoly.pdf b/source_data/data_boardgames/monopoly.pdf similarity index 100% rename from data_boardgames/monopoly.pdf rename to source_data/data_boardgames/monopoly.pdf diff --git a/data_boardgames/ticket_to_ride.pdf b/source_data/data_boardgames/ticket_to_ride.pdf similarity index 100% rename from data_boardgames/ticket_to_ride.pdf rename to source_data/data_boardgames/ticket_to_ride.pdf diff --git a/data_machine_learning_lectures/MachineLearning-Lecture01.pdf b/source_data/data_machine_learning_lectures/MachineLearning-Lecture01.pdf similarity index 100% rename from data_machine_learning_lectures/MachineLearning-Lecture01.pdf rename to source_data/data_machine_learning_lectures/MachineLearning-Lecture01.pdf diff --git a/data_machine_learning_lectures/MachineLearning-Lecture02.pdf b/source_data/data_machine_learning_lectures/MachineLearning-Lecture02.pdf similarity index 100% rename from data_machine_learning_lectures/MachineLearning-Lecture02.pdf rename to source_data/data_machine_learning_lectures/MachineLearning-Lecture02.pdf diff --git a/data_machine_learning_lectures/MachineLearning-Lecture03.pdf b/source_data/data_machine_learning_lectures/MachineLearning-Lecture03.pdf similarity index 100% rename from data_machine_learning_lectures/MachineLearning-Lecture03.pdf rename to source_data/data_machine_learning_lectures/MachineLearning-Lecture03.pdf diff --git a/data_machine_learning_lectures/MachineLearning-Lecture04.pdf b/source_data/data_machine_learning_lectures/MachineLearning-Lecture04.pdf similarity index 100% rename from data_machine_learning_lectures/MachineLearning-Lecture04.pdf rename to source_data/data_machine_learning_lectures/MachineLearning-Lecture04.pdf diff --git a/data_machine_learning_lectures/MachineLearning-Lecture05.pdf b/source_data/data_machine_learning_lectures/MachineLearning-Lecture05.pdf similarity index 100% rename from data_machine_learning_lectures/MachineLearning-Lecture05.pdf rename to source_data/data_machine_learning_lectures/MachineLearning-Lecture05.pdf diff --git a/data_machine_learning_lectures/MachineLearning-Lecture06.pdf b/source_data/data_machine_learning_lectures/MachineLearning-Lecture06.pdf similarity index 100% rename from data_machine_learning_lectures/MachineLearning-Lecture06.pdf rename to source_data/data_machine_learning_lectures/MachineLearning-Lecture06.pdf diff --git a/source_data/data_slack/WhatsCookinTeam Slack export Jul 25 2024 - Jul 26 2024.zip b/source_data/data_slack/WhatsCookinTeam Slack export Jul 25 2024 - Jul 26 2024.zip new file mode 100644 index 0000000000000000000000000000000000000000..65b563546668429a2a55917c15576e3e72943060 GIT binary patch literal 98023 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

_hM|@}yJr(x=K9YyfYk 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�c$=EwTEpT)f3k!Jbj+s467ztHCQA7)*5 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 gxBXwan*AR5|5+o*#|JC`AN2bXG{fq)j| Date: Tue, 30 Jul 2024 14:02:16 -0400 Subject: [PATCH 2/5] Fixed embeddings separation for slack and pdfs --- README.md | 13 ++++++++++--- ai_local_rag/chroma/populate_database.py | 4 ++-- ai_local_rag/chroma/slack_loader.py | 6 +++--- ai_local_rag/query_data.py | 4 ++-- ai_local_rag/utils/get_embedding_function.py | 17 ++++++++++++++++- source_data/chroma_boardgames/chroma.sqlite3 | Bin 0 -> 155648 bytes 6 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 source_data/chroma_boardgames/chroma.sqlite3 diff --git a/README.md b/README.md index e8dee1c..e35e2e3 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ Clone this repository and create a clean python v3.10 virtual environment and ac The following is a high-level list of components used to run this local RAG: - langchain -- streamlit -- streamlit-chat +- ollama - pypdf - chromadb - fastembed @@ -53,7 +52,7 @@ If you need to clear the database for any reason, run: The above command will remove the chroma database. If you need to recreate it, simply rerun `populate_database.py` -## Running the RAG +## Running the RAG from the commandline: The instruction manuals for both Monopoly and Ticket To Ride have been loaded into the Chroma DB. Ask the RAG questions about these two board games and see how well it does answering your questions. The RAG can be invoked using the following command with the sample question: @@ -68,6 +67,14 @@ Here are some additional questions you can try: You can also browse the instruction manuals that are in the `./data_boardgames` folder to come up with your own questions. +## Running the FASTAPI server to expose the RAG via API + +Start the FASTPI server to expose api's that can be called from the ai_rag_ui or curl. + +``` +python query_data.py How do I build a hotel in monopoly? +``` + ## Running the test cases ``` diff --git a/ai_local_rag/chroma/populate_database.py b/ai_local_rag/chroma/populate_database.py index 2778c0e..7abdc6b 100644 --- a/ai_local_rag/chroma/populate_database.py +++ b/ai_local_rag/chroma/populate_database.py @@ -10,7 +10,7 @@ from langchain_community.vectorstores import Chroma from langchain_text_splitters import RecursiveCharacterTextSplitter -from ai_local_rag.utils.get_embedding_function import get_embedding_function +from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_pdf # Load Config Settings load_dotenv() # take environment variables from .env. @@ -59,7 +59,7 @@ def _add_to_chroma(chunks: list[Document]): chroma_path = os.getenv("CHROMA_PATH") # Load the existing database. db = Chroma( - persist_directory=chroma_path, embedding_function=get_embedding_function() + persist_directory=chroma_path, embedding_function=get_embedding_function_for_pdf() ) # Calculate Page IDs. diff --git a/ai_local_rag/chroma/slack_loader.py b/ai_local_rag/chroma/slack_loader.py index 03c7f76..b9d5bb1 100644 --- a/ai_local_rag/chroma/slack_loader.py +++ b/ai_local_rag/chroma/slack_loader.py @@ -17,7 +17,7 @@ from langchain_community.document_loaders import SlackDirectoryLoader from langchain_community.vectorstores import Chroma from langchain_community.embeddings.ollama import OllamaEmbeddings -from ai_local_rag.utils.get_embedding_function import get_embedding_function, CustomOpenAIEmbeddings, CustomOllamaEmbeddings +from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_slack, CustomOpenAIEmbeddings, CustomOllamaEmbeddings dotenv.load_dotenv() logging.basicConfig() @@ -137,7 +137,7 @@ def _add_to_chroma_with_langchain(chunks: list[Document]): logger.info(f"_add_to_chroma - collection name: {chroma_collection}") # Load the existing database. db = Chroma(collection_name=chroma_collection, - persist_directory=chroma_path, embedding_function=get_embedding_function() + persist_directory=chroma_path, embedding_function=get_embedding_function_for_slack() ) db.persist() @@ -153,7 +153,7 @@ def _add_to_chroma(chunks_with_ids: list[Document]): logger.info(f"_add_to_chroma - collection name: {chroma_collection}") chroma_client = chromadb.Client() - embedding_function = get_embedding_function() + embedding_function = get_embedding_function_for_slack() collection = chroma_client.get_or_create_collection( name=chroma_collection, embedding_function=embedding_function ) diff --git a/ai_local_rag/query_data.py b/ai_local_rag/query_data.py index a2c9a55..31f3b84 100644 --- a/ai_local_rag/query_data.py +++ b/ai_local_rag/query_data.py @@ -6,7 +6,7 @@ from langchain.prompts import ChatPromptTemplate from langchain_community.llms.ollama import Ollama -from ai_local_rag.utils.get_embedding_function import get_embedding_function +from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_pdf # Load Config Settings load_dotenv() # take environment variables from .env. @@ -34,7 +34,7 @@ def main(): def query_rag(query_text: str): # Prepare the DB. - embedding_function = get_embedding_function() + embedding_function = get_embedding_function_for_pdf() db = Chroma(persist_directory=CHROMA_PATH, embedding_function=embedding_function) diff --git a/ai_local_rag/utils/get_embedding_function.py b/ai_local_rag/utils/get_embedding_function.py index 01ed213..cb06e19 100644 --- a/ai_local_rag/utils/get_embedding_function.py +++ b/ai_local_rag/utils/get_embedding_function.py @@ -5,7 +5,7 @@ # from langchain_community.embeddings import FastEmbedEmbeddings -def get_embedding_function(): +def get_embedding_function_for_slack(): # embeddings = BedrockEmbeddings( # credentials_profile_name="default", region_name="us-east-1" # ) @@ -20,6 +20,21 @@ def get_embedding_function(): return embeddings +def get_embedding_function_for_pdf(): + # embeddings = BedrockEmbeddings( + # credentials_profile_name="default", region_name="us-east-1" + # ) + + # Make sure you run this first: ollama pull nomic-embed-text + embeddings = OllamaEmbeddings(model="nomic-embed-text") + + # embeddings = embedding_functions.DefaultEmbeddingFunction() + # embeddings = embedding_functions.SentenceTransformerEmbeddingFunction( + # model_name="all-MiniLM-L6-v2") + # embeddings = FastEmbedEmbeddings() + return embeddings + + class CustomOpenAIEmbeddings(OpenAIEmbeddings): def __init__(self, openai_api_key, *args, **kwargs): diff --git a/source_data/chroma_boardgames/chroma.sqlite3 b/source_data/chroma_boardgames/chroma.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..696c94e9ef3dc6cfbd24be94a428c9cf7d095f7d GIT binary patch literal 155648 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 LKQ(5y3_bC`u+a=G literal 0 HcmV?d00001 From f74bff61095ceb1bb38f9e0afe0d8d2d518e0827 Mon Sep 17 00:00:00 2001 From: seachellemz Date: Tue, 6 Aug 2024 08:21:31 -0400 Subject: [PATCH 3/5] Interim commit for Slack Embeddings --- README.md | 2 +- ai_local_rag/api.py | 15 +++-- ai_local_rag/chroma/chroma_client.py | 2 +- ai_local_rag/chroma/populate_database.py | 10 +-- ai_local_rag/chroma/reset_database.py | 4 +- ai_local_rag/chroma/slack_loader.py | 13 ++-- ai_local_rag/query_data.py | 4 +- ai_local_rag/query_slack_data.py | 67 +++++++++++++++++++ ai_local_rag/utils/get_embedding_function.py | 48 ++++++++----- source_data/chroma_boardgames/chroma.sqlite3 | Bin 155648 -> 0 bytes 10 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 ai_local_rag/query_slack_data.py delete mode 100644 source_data/chroma_boardgames/chroma.sqlite3 diff --git a/README.md b/README.md index e35e2e3..4f9f94e 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Create a `.env` file in the root directory and add the following environment var ```.env -CHROMA_PATH=chroma_boardgames +CHROMA_DB_PATH_PDF=chroma_boardgames DATA_PATH_BG=data_boardgames ``` diff --git a/ai_local_rag/api.py b/ai_local_rag/api.py index 6fbb94c..b43cd89 100644 --- a/ai_local_rag/api.py +++ b/ai_local_rag/api.py @@ -25,6 +25,10 @@ class Message(BaseModel): channel_list = ["general", "dev", "marketing"] message_map = {} +logging.basicConfig() +logger = logging.getLogger("api") +logger.setLevel(logging.DEBUG) + @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): @@ -62,20 +66,21 @@ def post_message(message: Message): @app.post("/rag-query") async def query_rag_api(query: QueryInput): - print(f"api.py - API Request Data: {query}") + logger.info(f"api.py - API Request Data: {query}") query_response = query_rag({"input": query}) - print(query_response) + logger.debug(f"Query Response: - {query_response}") # query: str # response: str # sources: list[str] query_response2 = { "query": query, "response": query_response["response"], "sources": query_response["sources"]} - print(f"Query Response2: {query_response2}") + logger.info(f"Query Response2: {query_response2}") # query_response["intermediate_steps"] = [ # str(s) for s in query_response["intermediate_steps"] # ] + logger.debug(f"Query Response: - {query_response2}") return query_response2 @@ -85,7 +90,7 @@ async def query_rag_api2( query: QueryInput, ) -> QueryOutput: query_response = query_rag({"input": query}) - print(query_response) + logger.debug(f"Query Response: {query_response}") # query: str # response: str @@ -93,7 +98,7 @@ async def query_rag_api2( query_text = query["query"] query_response2 = { "query": query_text, "response": query_response["response"], "sources": query_response["sources"]} - print(f"Query Response2: {query_response2}") + logger.debug(f"Query Response2: {query_response2}") # query_response["intermediate_steps"] = [ # str(s) for s in query_response["intermediate_steps"] diff --git a/ai_local_rag/chroma/chroma_client.py b/ai_local_rag/chroma/chroma_client.py index bbdbdd9..38ea2cc 100644 --- a/ai_local_rag/chroma/chroma_client.py +++ b/ai_local_rag/chroma/chroma_client.py @@ -6,7 +6,7 @@ import dotenv from langchain.vectorstores import Chroma -from ai_local_rag.utils.get_embedding_function import get_embedding_function +# from ai_local_rag.utils.get_embedding_function import get_embedding_function dotenv.load_dotenv() diff --git a/ai_local_rag/chroma/populate_database.py b/ai_local_rag/chroma/populate_database.py index 7abdc6b..b76fbcb 100644 --- a/ai_local_rag/chroma/populate_database.py +++ b/ai_local_rag/chroma/populate_database.py @@ -56,10 +56,10 @@ def _split_documents(documents: list[Document]): def _add_to_chroma(chunks: list[Document]): - chroma_path = os.getenv("CHROMA_PATH") + chroma_db_path_pdf = os.getenv("CHROMA_DB_PATH_PDF") # Load the existing database. db = Chroma( - persist_directory=chroma_path, embedding_function=get_embedding_function_for_pdf() + persist_directory=chroma_db_path_pdf, embedding_function=get_embedding_function_for_pdf() ) # Calculate Page IDs. @@ -119,10 +119,10 @@ def clear_database(): Remove the database so that we can rebuild it """ load_dotenv() # take environment variables from .env. - chroma_path = os.getenv("CHROMA_PATH") + chroma_db_path = os.getenv("CHROMA_DB_PATH_PDF") - if os.path.exists(chroma_path): - shutil.rmtree(chroma_path) + if os.path.exists(chroma_db_path): + shutil.rmtree(chroma_db_path) if __name__ == "__main__": diff --git a/ai_local_rag/chroma/reset_database.py b/ai_local_rag/chroma/reset_database.py index 5b78748..63eb923 100644 --- a/ai_local_rag/chroma/reset_database.py +++ b/ai_local_rag/chroma/reset_database.py @@ -4,7 +4,7 @@ # Load Config Settings load_dotenv() # take environment variables from .env. -CHROMA_PATH = chroma_path = os.getenv("CHROMA_PATH") +chroma_db_path_pdf = os.getenv("CHROMA_DB_PATH_PDF") populate_database.clear_database() -print(f"Removed all content from database {CHROMA_PATH}") +print(f"Removed all content from database {chroma_db_path_pdf}") diff --git a/ai_local_rag/chroma/slack_loader.py b/ai_local_rag/chroma/slack_loader.py index b9d5bb1..729dcd9 100644 --- a/ai_local_rag/chroma/slack_loader.py +++ b/ai_local_rag/chroma/slack_loader.py @@ -17,13 +17,16 @@ from langchain_community.document_loaders import SlackDirectoryLoader from langchain_community.vectorstores import Chroma from langchain_community.embeddings.ollama import OllamaEmbeddings -from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_slack, CustomOpenAIEmbeddings, CustomOllamaEmbeddings +from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_slack dotenv.load_dotenv() logging.basicConfig() logger = logging.getLogger("slack_loader") logger.setLevel(logging.DEBUG) +chroma_path = os.getenv("CHROMA_DB_PATH_SLACK") +chroma_collection = os.getenv("CHROMA_SLACK_COLLECTION") + def slack_toolkit(): toolkit = SlackToolkit() @@ -132,8 +135,7 @@ def _calculate_chunk_ids(chunks: list[Document]): def _add_to_chroma_with_langchain(chunks: list[Document]): - chroma_path = os.getenv("CHROMA_PATH_SLACK") - chroma_collection = os.getenv("CHROMA_SLACK_COLLECTION") + logger.info(f"_add_to_chroma - collection name: {chroma_collection}") # Load the existing database. db = Chroma(collection_name=chroma_collection, @@ -144,8 +146,6 @@ def _add_to_chroma_with_langchain(chunks: list[Document]): def _add_to_chroma(chunks_with_ids: list[Document]): - chroma_path = os.getenv("CHROMA_PATH_SLACK") - chroma_collection = os.getenv("CHROMA_SLACK_COLLECTION") chroma_client = chromadb.PersistentClient(path=chroma_path) # settings=Settings(chroma_db_impl="duckdb+parquet")) @@ -154,6 +154,7 @@ def _add_to_chroma(chunks_with_ids: list[Document]): chroma_client = chromadb.Client() embedding_function = get_embedding_function_for_slack() + # chroma_client.delete_collection(chroma_collection) collection = chroma_client.get_or_create_collection( name=chroma_collection, embedding_function=embedding_function ) @@ -167,6 +168,8 @@ def _add_to_chroma(chunks_with_ids: list[Document]): metadatas=chunks_with_ids["metadata"], documents=chunks_with_ids["chunks"]) + logger.debug(f"Total number of embeddings loaded: {collection.count()}") + results = collection.query( # Chroma will embed this for you query_texts=["This is a query document about c-linked-trust"], diff --git a/ai_local_rag/query_data.py b/ai_local_rag/query_data.py index 31f3b84..b839045 100644 --- a/ai_local_rag/query_data.py +++ b/ai_local_rag/query_data.py @@ -10,7 +10,7 @@ # Load Config Settings load_dotenv() # take environment variables from .env. -CHROMA_PATH = chroma_path = os.getenv("CHROMA_PATH") +chroma_db_path_pdf = os.getenv("CHROMA_DB_PATH_PDF") PROMPT_TEMPLATE = """ Answer the question based only on the following context: @@ -35,7 +35,7 @@ def main(): def query_rag(query_text: str): # Prepare the DB. embedding_function = get_embedding_function_for_pdf() - db = Chroma(persist_directory=CHROMA_PATH, + db = Chroma(persist_directory=chroma_db_path_pdf, embedding_function=embedding_function) # Search the DB. diff --git a/ai_local_rag/query_slack_data.py b/ai_local_rag/query_slack_data.py new file mode 100644 index 0000000..5f3ba5d --- /dev/null +++ b/ai_local_rag/query_slack_data.py @@ -0,0 +1,67 @@ +import argparse +import os +from dotenv import load_dotenv +# from langchain.vectorstores.chroma import Chroma +from langchain_community.vectorstores import Chroma +from langchain.prompts import ChatPromptTemplate +from langchain_community.llms.ollama import Ollama +from langchain_community.embeddings.ollama import OllamaEmbeddings + +from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_slack + +# Load Config Settings +load_dotenv() # take environment variables from .env. +chroma_db_path_pdf = os.getenv("CHROMA_DB_PATH_SLACK") + +PROMPT_TEMPLATE = """ +Answer the question based only on the following context: + +{context} + +--- + +Answer the question based on the above context: {question} +""" + + +def main(): + # Create CLI. + parser = argparse.ArgumentParser() + parser.add_argument("query_text", type=str, help="The query text.") + args = parser.parse_args() + query_text = args.query_text + query_rag(query_text) + + +def query_rag(query_text: str): + # Prepare the DB. + # embedding_function = get_embedding_function_for_slack() + embedding_function = OllamaEmbeddings(model="nomic-embed-text") + db = Chroma(persist_directory=chroma_db_path_pdf, + embedding_function=embedding_function) + + # Search the DB. + results = db.similarity_search_with_score(query_text, k=5) + + context_text = "\n\n---\n\n".join( + [doc.page_content for doc, _score in results]) + prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE) + prompt = prompt_template.format(context=context_text, question=query_text) + # print(prompt) + + model = Ollama(model="mistral") + response_text = model.invoke(prompt) + + sources = [doc.metadata.get("id", None) for doc, _score in results] + formatted_response = f"Response: {response_text}\nSources: {sources}" + print(formatted_response) + + # Format the response + response_message = {"query": query_text, + "response": response_text, "sources": sources} + # print(f"qyery_data:query_rag: Response Message: {response_message}") + return response_message + + +if __name__ == "__main__": + main() diff --git a/ai_local_rag/utils/get_embedding_function.py b/ai_local_rag/utils/get_embedding_function.py index cb06e19..aa056ed 100644 --- a/ai_local_rag/utils/get_embedding_function.py +++ b/ai_local_rag/utils/get_embedding_function.py @@ -1,8 +1,16 @@ +import os + +import dotenv + from langchain_community.embeddings.ollama import OllamaEmbeddings from langchain_openai import OpenAIEmbeddings from chromadb.utils import embedding_functions +from sentence_transformers import SentenceTransformer +import nomic +from nomic import embed # from langchain_community.embeddings.bedrock import BedrockEmbeddings # from langchain_community.embeddings import FastEmbedEmbeddings +dotenv.load_dotenv() def get_embedding_function_for_slack(): @@ -11,11 +19,15 @@ def get_embedding_function_for_slack(): # ) # Make sure you run this first: ollama pull nomic-embed-text - # embeddings = OllamaEmbeddings(model="nomic-embed-text") + # ollama_model = OllamaModel("nomic-embed-text") + # embeddings = CustomOllamaEmbedding("nomic-embed-text-v1") + embeddings = CustomSentenceTransformerEmbedding('paraphrase-MiniLM-L6-v2') # embeddings = embedding_functions.DefaultEmbeddingFunction() - embeddings = embedding_functions.SentenceTransformerEmbeddingFunction( - model_name="all-MiniLM-L6-v2") + + # embeddings = embedding_functions.SentenceTransformerEmbeddingFunction( + # model_name="all-MiniLM-L6-v2") + # embeddings = FastEmbedEmbeddings() return embeddings @@ -52,19 +64,25 @@ def __call__(self, input): return self._embed_documents(input) -class CustomOllamaEmbeddings(OllamaEmbeddings): +class CustomOllamaEmbedding: + def __init__(self, model_name): + self.model_name = model_name - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def _embed_documents(self, texts): - embeddings = [ - self. - self.client.create( - input=text, model="nomic-embed-text").data[0].embedding - for text in texts - ] + def __call__(self, input): + nomic_api_key = os.getenv("NOMIC_API_KEY") + nomic.login(nomic_api_key) + if isinstance(input, str): + input = [input] + # Assuming nomic library provides a function to embed text + embeddings = embed.text(input, model=self.model_name) return embeddings + +class CustomSentenceTransformerEmbedding: + def __init__(self, model_name): + self.model = SentenceTransformer(model_name) + def __call__(self, input): - return self._embed_documents(input) + if isinstance(input, str): + input = [input] + return self.model.encode(input) diff --git a/source_data/chroma_boardgames/chroma.sqlite3 b/source_data/chroma_boardgames/chroma.sqlite3 deleted file mode 100644 index 696c94e9ef3dc6cfbd24be94a428c9cf7d095f7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155648 zcmeI*Uu@e*eg|;TlC4-yqU`PN>9W~0jB~rTaF!#I`n$#=_1a3#t^aInX>;2iAPmW& ztwtstk#^$U9tu0j0xb#@MS;ViZv_rl6e!RI3S6&6AN$ak-ct)43S7~?6=>1E6ljY> z4;}ueWXaZU^T%<&+LcX{LvrRbBqh#ohS|8g+_EUI8J%rn@lxzkjN@Xz$n&vSY?{5_ zW$(-EJ<8sb?44k5|Hb(e$NZj-&FX()ugs-?W+(dv_t!CNU=ai$009U<00Izz00bZa z0SG_<0?%0B?R4bc{5tNITl#@kO&8d}w zB3IO6QKZ!ajvH;XE;qy~k;_FQ=M+g&as^pabJcRb%Jy8XkWy8v(z4QL&wEs{jLyw1 z-F%p98wzRXc4)_JnHE()sx8#l)`!W19seIm|4A(U6ZQiy2tWV=5P$##AOHafKmY;| zfB*zegTQpW!41<;F)|fzj1SYUfbsvQp<0m#2tWV=5P$##AOHafKmY;|fWY$=!1({? z>x->J00Izz00bZa0SG_<0uX=z1Wtp19slRj{}yBa;{^c-KmY;|fB*y_009U<00Izz zz?l%Z#EoQCs*&Bc)wHNibjyzaQcvbmoA{(kh|?+xJe|G)Ty z82cYD2tWV=5P$##AOHafKmY;|fWS)@IK0Ot8`oluClgYMh=dg7TwbY^a|J<^ainQ7$`lhl)TDl+PJ}vExDfK5Qz6uC+FIJH%=k`s923r<3LmQ7D_5 zY>SgK{3O-vG^W+rRlFoZKJU&qDd{L%EU946(08|+)>elS)%0hr*b^nTtV7M#C$y;% z#WFhf%vq7mm5HTnHSHzN3eINDeO+-jV{RF3)t%3hHm&^~CsW^C-MF_9&GV9O(T+~q zli!IZTQJ&f>STC8VmJPe{{Jue0LShj009U<00Izz00bZa0SG_<0$)nN?*BhbTZw!B z|KnKt$6snUSP23UfB*y_009U<00Izz00bZaffp_yB(62azfkuF`~E+6>4j$$JA?oP zAOHafKmY;|fB*y_009U<-~|fUT9!u-a<{929OOPX}9@;q6>x?JU-GbGJiz zYl~Ps@ixY95tBFc_CEVTgTnF;ThGk=APwRDv}G9R>C7HyJU z%W(c~y8APmf;W-=_Jw5T$`$S)?I?S5yE5^)B!I`+Qf9dzX4y_hq7iVYK_Dj2yoFbeQoO zo@Ijh-b{^U7OtM)%WB&%*2KZ=crtVKDtCA@>S_ARQ~mtRld9a>-|0PnToJ|Szd$kC zZK_x;Lw6w~=T5lDK7CTz+PSgJ$I_q@hN&Y5QJ(c0x zz=Me_@sFv#B|4o)Rafr)1M4s!ch0*fuRC6JTc)bo(l^Z}yL#@@-oF!FQoT!q^X`Ft z+4RnYt}C;1xF{V;EUGryWv{-pQroD{uYAY{*Vo0`?fJ%XonL6Iv&(F~86jda;$GwmSWa+MCS&dyjTZ z`#kKg)^821)n=cDM?Nu~D(sh@eFhDi8TKK!HEi0hD!+r2aeg zSLZLW$bT;JJMn*wuSEZQCcDN)AaJ$>4&Ht@nORxk4!+UdJlxcF+iit(7EPuDFiF4|tz`v8stgUeeZj@v2 z_EbYLTc3o@*+=r{C3t7p40`FOSnwifQjN4!&hX|ZPNQ=6;L6wCEWUHHAd9?NgUi5( z?F3&RHj7^R!C6%D*@M{|$;_QQ+`+G&kh_-pXmHNh!W+Zpilv>h16(b2hJ`#k+cW&m zrf$qc>p8kO+gsyrO(ru-OGDH+i!%<+u^ok+95%~#;=zh?+feSib$yu3Br}4*J$6Ic z;qLti!qGX&u1p+V9){%nhWkjwDM;=FoZR@u;GiW7YCPw-rCq0PymI1)@mpu>N{G@x z;DrfDZyg(AB#+p~t{r1#A;w_(YkQZS2;)K7jW7la!X%lli%mE`h}*i|H2g?u^z+P( z^E*3ru604YHkDoY+F0hEFen}xT%#g^`=j%|%4mP(`S2cGzMjn7x^?bg;rNfZFjGC> zZ9KG(me%Wy`DL#g(!kVwV`FLc4zF2eahmlM2`;+q)6Te0b@nCX{V6t^1&Oz(veH+N ztLI=JER-Bhj~%}Lrc=?6A9NSU4AYEuRdhK2=%-1aI_^FQy2ItcrjGQBazB0C!?ODb zahLp`g8qIls%XcUXjWMNk>VR;nWY53-gV_`#f4o_Iec086$ZBsKieE`)ZrtFW zxZjH-iz1V+ch$mVWXWV$5znymn%_0fIak#d>v9#1_`B8Mr#Vu8(auqRqCx#wc=y|W z^Vdv6Z?c2j3FW)n_iM`TI4~3RUt@b?RV2JNmZ@DER7Jr~n_@8e72(ows|dj=ui^QB z&*wUdtwI0-5P$##AOHafKmY;|fB*zelYrg-{~Y%}v5P-A|6dccsqc?|miU8_KW8)W zI{N|#)pN-VclUs-nER$GC-dx8&V?;+Y|@TX&${C!?O$vh<^~%9L`~MH6%1B$Q|)>K zjntMigpG7s)SOMtFV}19UMq08ipkF}F7gX&%Z-)QzRhEEDM2aIk|fJSDwIe;Btk_l z5xJ}hO0`mD!>%4vHgt`npS|Peeay~#DVle$^)u&36MIT=cbN?V(o+}+tR zI&5UAGoFiAwkQizy5mS$CswkC8f>1h;$5=hz zIeg!(=TWb)&Xk=hxO*H08tP`GuO@H)zd&P5|#bDBz>{2A#bSuRCdlj z;Or}4&mLGfmR(AFlk*0<;&#{x&z>OAXe=$7sR29pGsi}E+x;`#^0_CX9hTvP{Me_c z{mS+E;VfW3re?y7_#2MSbGOUNk{XnqBZpKZQ})%31&#d3RZslL9&@_E9{&zP1wi$T#Ow&Q;ER$V4QQ-Iv730_TKaBb4V?YCNms$db8KzEG*K z%OkC53ad<7$cvIv7KA)46eOuw6%LY#u?%-_-~9xL+c}n^pY#Di=Dd$QGqU@x%d@}y zmIT{US0pQ*$3U?iTF>`Avvc#{bzEwXcLhX>g zkgDp+so(u=E+ti@qNtX|B2fiJDCNrqjVJ{%FN>mFC`r=cJuaCWUwD#P3HmX3k6dso zE}T2)91K&qk81GFMXv{j-&-(pSb1mEtNrkWxs)oEi$zsq-=;;GR%k`a3tB-fYlECuWFT|bN@e*{`Xk= zXY2=F5P$##AOHafKmY;|fB*y_009V`1%Xs59(&6xNSuqu&SU)lS*TW&0Rj+!00bZa z0SG_<0uX=z1R!v{0RH{I<9)Cc0uX=z1Rwwb2tWV=5P$##AaE80aQ}Z6Y7u3C00bZa z0SG_<0uX=z1Rwwb2plh9_y6b8{}E&V;{^c-KmY;|fB*y_009U<00Izzz!?&lh+pB@ LKQ(5y3_bC`u+a=G From ea33e243bb6a68e518b280b2c1ca1b05d4a4b870 Mon Sep 17 00:00:00 2001 From: seachellemz Date: Sat, 10 Aug 2024 19:23:30 -0400 Subject: [PATCH 4/5] Embeddings Loading and query working --- ai_local_rag/chroma/slack_loader.py | 141 +++++++++++++++---- ai_local_rag/query_slack_data.py | 130 ++++++++++++----- ai_local_rag/utils/get_embedding_function.py | 9 +- 3 files changed, 219 insertions(+), 61 deletions(-) diff --git a/ai_local_rag/chroma/slack_loader.py b/ai_local_rag/chroma/slack_loader.py index 729dcd9..6a09009 100644 --- a/ai_local_rag/chroma/slack_loader.py +++ b/ai_local_rag/chroma/slack_loader.py @@ -2,12 +2,12 @@ Loader for slack """ import os - -import dotenv import logging +import dotenv + +import numpy as np from dotenv import load_dotenv import chromadb -from chromadb.config import Settings from langchain import hub from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain.schema.document import Document @@ -16,7 +16,6 @@ from langchain_community.chat_models import ChatOllama from langchain_community.document_loaders import SlackDirectoryLoader from langchain_community.vectorstores import Chroma -from langchain_community.embeddings.ollama import OllamaEmbeddings from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_slack dotenv.load_dotenv() @@ -91,6 +90,8 @@ def _calculate_chunk_ids(chunks: list[Document]): # This will create IDs like "c-linkedtrust:U069UCY6WPL:1721902091.115329:2" # Channel : UserId: Timestamp: Chunk Index + # Document format is:[Document(metadata={'soource': 'xxxx'})] + last_page_id = None current_chunk_index = 0 @@ -126,9 +127,9 @@ def _calculate_chunk_ids(chunks: list[Document]): # Add it to the list of ids chunk_id_list.append(chunk_id) - response["chunks"] = page_content - response["chunk_ids"] = chunk_id_list - response["metadata"] = metadata_list + response["texts"] = page_content + response["ids"] = chunk_id_list + response["metadatas"] = metadata_list # return chunks return response @@ -152,32 +153,119 @@ def _add_to_chroma(chunks_with_ids: list[Document]): logger.info(f"_add_to_chroma - collection name: {chroma_collection}") - chroma_client = chromadb.Client() + # Create or load a collection + collection_name = chroma_collection + collection = None + try: + collection = chroma_client.get_or_create_collection(collection_name) + except Exception as e: + print(f"Error accessing collection: {e}") + + texts = chunks_with_ids['texts'] + metadatas = chunks_with_ids['metadatas'] + ids = chunks_with_ids['ids'] + + # Get the embeddings function embedding_function = get_embedding_function_for_slack() - # chroma_client.delete_collection(chroma_collection) - collection = chroma_client.get_or_create_collection( - name=chroma_collection, embedding_function=embedding_function - ) - # Calculate Page IDs. - # chunks_with_ids = _calculate_chunk_ids(chunks) + # Generate the embeddings + embeddings = embedding_function(texts) + # print("Embeddings: ", embeddings) - # collection = chroma_client.create_collection(name=chroma_collection) + # Debugging: Print lengths of all inputs + logger.debug(f"Texts length: {len(texts)}\n") + logger.debug(f"Embeddings length: {len(embeddings)}\n") + logger.debug(f"Metadata length: {len(metadatas)}\n") + logger.debug(f"IDs length: {len(ids)}\n") - collection.add(ids=chunks_with_ids["chunk_ids"], - metadatas=chunks_with_ids["metadata"], - documents=chunks_with_ids["chunks"]) + # Ensure all lists have the same length + assert len(texts) == len(embeddings) == len(metadatas) == len( + ids), "Lengths of input lists do not match." - logger.debug(f"Total number of embeddings loaded: {collection.count()}") + # Ensure we have an embedding for each ID + for i in range(len(ids)): + print(f"ID: {ids[i]}") + print(f"Embedding: {embeddings[i]}") + + # Add text and embeddings to the collection + try: + collection.add(documents=texts, embeddings=embeddings, + metadatas=metadatas, ids=ids) + + except Exception as e: + print(f"Error adding to collection: {e}") + + logger.info("DONE LOADING and QUERING") - results = collection.query( - # Chroma will embed this for you - query_texts=["This is a query document about c-linked-trust"], - n_results=2 # how many results to return - ) - logger.info("QUERY RESULTS") - logger.info(results) +def _query(): + collection = [] + + # Create or load a collection + collection_name = chroma_collection + collection = None + chroma_client = chromadb.PersistentClient(path=chroma_path) + try: + collection = chroma_client.get_collection(collection_name) + except Exception as e: + print(f"Error accessing collection: {e}") + + # QUERY WITH METADATA + query_text = 'Great i have tasks on the front and in back so i will contact with both' + embedding_function = get_embedding_function_for_slack() + query_embedding = embedding_function(query_text) + query_include = ["metadatas", "distances", "embeddings"] + + # Ensure query_embedding is in the expected format + if isinstance(query_embedding, np.ndarray): + query_embedding = query_embedding.tolist() + + # Query the collection + try: + result = collection.query( + query_embedding, n_results=1, include=query_include) + print("Query Result: ", result) + except Exception as e: + logger.info(f"Error querying the collection: {e}") + + # Example: Query the collection and retrieve results with metadata + try: + results = collection.query( + query_embeddings=query_embedding, + n_results=5, # Number of results to retrieve + include=query_include + ) + + # Process and print results + ids = results.get('ids', []) + embeddings = results.get('embeddings', []) + distances = results.get('distances', []) + metadatas = results.get('metadatas', []) + + # Process and print results + # for i in range(len(ids)): + for i, result_id in enumerate(ids): + print(f"Result {i + 1}:") + print(f"ID: {result_id}") + + if distances: + print(f"Distance: {distances[i]}") + else: + print("Distance not available.") + + if metadatas: + print(f"Metadata: {metadatas[i]}") + else: + print("Metadata not available.") + + if embeddings: + print(f"Embedding: {embeddings[i]}") + else: + print("Embedding not available.") + + print("-" * 40) + except Exception as e: + print(f"Error querying the collection: {e}") def main(): @@ -190,6 +278,7 @@ def main(): chunks = _calculate_chunk_ids(chunks) _print_chunks(chunks) _add_to_chroma(chunks) + _query() logger.info("FINISHED") diff --git a/ai_local_rag/query_slack_data.py b/ai_local_rag/query_slack_data.py index 5f3ba5d..a9be63b 100644 --- a/ai_local_rag/query_slack_data.py +++ b/ai_local_rag/query_slack_data.py @@ -1,17 +1,29 @@ import argparse +import logging import os + +import chromadb +import numpy as np from dotenv import load_dotenv -# from langchain.vectorstores.chroma import Chroma -from langchain_community.vectorstores import Chroma -from langchain.prompts import ChatPromptTemplate -from langchain_community.llms.ollama import Ollama -from langchain_community.embeddings.ollama import OllamaEmbeddings -from ai_local_rag.utils.get_embedding_function import get_embedding_function_for_slack +from ai_local_rag.utils.get_embedding_function import \ + get_embedding_function_for_slack # Load Config Settings load_dotenv() # take environment variables from .env. -chroma_db_path_pdf = os.getenv("CHROMA_DB_PATH_SLACK") + + +logging.basicConfig() +logger = logging.getLogger("slack_loader") +logger.setLevel(logging.DEBUG) + +chroma_path = os.getenv("CHROMA_DB_PATH_SLACK") +chroma_collection = os.getenv("CHROMA_SLACK_COLLECTION") + +verbose_str = os.getenv("VERBOSE").lower() +VERBOSE = False +if verbose_str == "true": + VERBOSE = True PROMPT_TEMPLATE = """ Answer the question based only on the following context: @@ -33,34 +45,84 @@ def main(): query_rag(query_text) +def _get_collection(): + # Create or load a collection + collection_name = chroma_collection + collection = None + chroma_client = chromadb.PersistentClient(path=chroma_path) + try: + collection = chroma_client.get_collection(collection_name) + + return collection + except Exception as e: + print(f"Error accessing collection: {e}") + + def query_rag(query_text: str): - # Prepare the DB. - # embedding_function = get_embedding_function_for_slack() - embedding_function = OllamaEmbeddings(model="nomic-embed-text") - db = Chroma(persist_directory=chroma_db_path_pdf, - embedding_function=embedding_function) - - # Search the DB. - results = db.similarity_search_with_score(query_text, k=5) - - context_text = "\n\n---\n\n".join( - [doc.page_content for doc, _score in results]) - prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE) - prompt = prompt_template.format(context=context_text, question=query_text) - # print(prompt) - - model = Ollama(model="mistral") - response_text = model.invoke(prompt) - - sources = [doc.metadata.get("id", None) for doc, _score in results] - formatted_response = f"Response: {response_text}\nSources: {sources}" - print(formatted_response) - - # Format the response - response_message = {"query": query_text, - "response": response_text, "sources": sources} - # print(f"qyery_data:query_rag: Response Message: {response_message}") - return response_message + # Load the collection + collection = _get_collection() + + # QUERY WITH METADATA + embedding_function = get_embedding_function_for_slack() + query_embedding = embedding_function(query_text) + query_include = ["metadatas", "documents", "distances", "embeddings"] + + # Ensure query_embedding is in the expected format + if isinstance(query_embedding, np.ndarray): + query_embedding = query_embedding.tolist() + + # Query the collection and retrieve results with the specified includes + try: + results = collection.query( + query_embeddings=query_embedding, + n_results=5, # Number of results to retrieve + include=query_include + ) + result_len = len(results.get('ids', [])) + logger.info(f"Query returned {result_len} item(s)") + + if VERBOSE: + + # Process and print results + ids = results.get('ids', []) + embeddings = results.get('embeddings', []) + distances = results.get('distances', []) + metadatas = results.get('metadatas', []) + documents = results.get('documents', []) + + # Process and print results + # for i in range(len(ids)): + for i, result_id in enumerate(ids): + logger.debug(f"Result {i + 1}:") + logger.debug(f"ID: {result_id}") + + if distances: + logger.debug( + f"Distance (Similarity Score) {i + 1}: {distances[i]}\n") + else: + logger.debug("Distance not available.") + + if documents: + logger.debug(f"Documents {i + 1}: {documents[i]}\n") + else: + logger.debug("Distance not available.") + + if metadatas: + logger.debug(f"Metadata {i + 1}: {metadatas[i]}\n") + else: + logger.debug("Metadata not available.") + + if embeddings: + # logger.debug(f"Embedding: {embeddings[i]}") + logger.debug(f"Retreived an embedding {i + 1}\n") + else: + print("Embedding not available.") + + logger.debug("-" * 40) + + return results + except Exception as e: + print(f"Error querying the collection: {e}") if __name__ == "__main__": diff --git a/ai_local_rag/utils/get_embedding_function.py b/ai_local_rag/utils/get_embedding_function.py index aa056ed..ae8add6 100644 --- a/ai_local_rag/utils/get_embedding_function.py +++ b/ai_local_rag/utils/get_embedding_function.py @@ -2,6 +2,8 @@ import dotenv +import numpy as np + from langchain_community.embeddings.ollama import OllamaEmbeddings from langchain_openai import OpenAIEmbeddings from chromadb.utils import embedding_functions @@ -85,4 +87,9 @@ def __init__(self, model_name): def __call__(self, input): if isinstance(input, str): input = [input] - return self.model.encode(input) + # Generate embeddings + embeddings = self.model.encode(input) + # Ensure embeddings are in list format if using NumPy arrays + if isinstance(embeddings, np.ndarray): + embeddings = embeddings.tolist() + return embeddings From 5d6ac04e9389a8e75352b0a4965ed8345c572631 Mon Sep 17 00:00:00 2001 From: seachellemz Date: Sat, 10 Aug 2024 21:24:02 -0400 Subject: [PATCH 5/5] Added Prompt, call to LLM and formatted response --- ai_local_rag/query_slack_data.py | 109 +++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/ai_local_rag/query_slack_data.py b/ai_local_rag/query_slack_data.py index a9be63b..db716a1 100644 --- a/ai_local_rag/query_slack_data.py +++ b/ai_local_rag/query_slack_data.py @@ -6,6 +6,9 @@ import numpy as np from dotenv import load_dotenv +from langchain.prompts import ChatPromptTemplate +from langchain_community.llms.ollama import Ollama + from ai_local_rag.utils.get_embedding_function import \ get_embedding_function_for_slack @@ -14,7 +17,7 @@ logging.basicConfig() -logger = logging.getLogger("slack_loader") +logger = logging.getLogger("query_slack_data") logger.setLevel(logging.DEBUG) chroma_path = os.getenv("CHROMA_DB_PATH_SLACK") @@ -26,13 +29,14 @@ VERBOSE = True PROMPT_TEMPLATE = """ -Answer the question based only on the following context: - -{context} +The following is a relevant document based on your query about "{query}": ---- +Document ID: {doc_id} +Similarity Score: {score} +Document Text: +{doc_text} -Answer the question based on the above context: {question} +How can I assist you further with this information? """ @@ -42,7 +46,7 @@ def main(): parser.add_argument("query_text", type=str, help="The query text.") args = parser.parse_args() query_text = args.query_text - query_rag(query_text) + query_slack_rag(query_text) def _get_collection(): @@ -58,7 +62,7 @@ def _get_collection(): print(f"Error accessing collection: {e}") -def query_rag(query_text: str): +def query_slack_rag(query_text: str): # Load the collection collection = _get_collection() @@ -75,11 +79,11 @@ def query_rag(query_text: str): try: results = collection.query( query_embeddings=query_embedding, - n_results=5, # Number of results to retrieve + n_results=1, # Number of results to retrieve include=query_include ) result_len = len(results.get('ids', [])) - logger.info(f"Query returned {result_len} item(s)") + logger.info(f"Query returned {result_len} item(s)\n") if VERBOSE: @@ -120,7 +124,90 @@ def query_rag(query_text: str): logger.debug("-" * 40) - return results + ##### Query Complete #### + + # Extract the first result (most similar) + # Extract the first result (most similar) + result_id = results.get('ids', [None])[0] + result_metadata = results.get('metadatas', [None])[0] + result_text = results.get('documents', [None])[0] + result_distance = results.get('distances', [None])[0] + + logger.debug(f"Most similar result ID: {result_id}") + logger.debug(f"Distance (similarity score): {result_distance}\n") + logger.debug(f"Document text: {result_text}") + logger.debug(f"Metadata: {result_metadata}\n") + + ##### Build the Prompt #### + chat_prompt_template = ChatPromptTemplate.from_template( + PROMPT_TEMPLATE) + # Fill the template with actual data from the query result + filled_prompt = [] + filled_prompt.append(chat_prompt_template.format( + query=query_text[0], + doc_id=result_id[0], + score=round(result_distance[0], + 4) if result_distance is not None else "N/A", + doc_text=result_text[0] or "No document found." + )) + + logger.debug(f"Formatted Chat Prompt: {filled_prompt}\n") + + # Assume you have a language model set up (like an OpenAI model) + language_model = Ollama(model="mistral") + llm_result = language_model.generate(filled_prompt) + # max_tokens=50, + # temperature=0.7, + # num_return_sequences=3 + # logger.debug(f"\nLanguage Model's 'Generate' Response:") + # logger.debug(llm_result) + + # The 'invoke' function returns a much more simple response than 'generate' + # response2 = language_model.invoke(filled_prompt) + # logger.debug(f"\nLanguage Model's 'Invoke' Response:") + # logger.debug(response2) + + # Extracting elements from the LLMResult object + + # 1. Extract the generated texts + generated_texts = [ + generation.text for generation_list in llm_result.generations for generation in generation_list] + logger.debug(f"Generated Texts:") + for text in generated_texts: + logger.debug(f"- {text}") + + # 2. Extract token usage information (if available) + if llm_result.llm_output and "token_usage" in llm_result.llm_output: + token_usage = llm_result.llm_output["token_usage"] + total_tokens = token_usage.get("total_tokens", 0) + prompt_tokens = token_usage.get("prompt_tokens", 0) + completion_tokens = token_usage.get("completion_tokens", 0) + + logger.debug(f"Token Usage:") + logger.debug(f"- Total Tokens: {total_tokens}") + logger.debug(f"- Prompt Tokens: {prompt_tokens}") + logger.debug(f"- Completion Tokens: {completion_tokens}\n") + + # 3. Extract the model name (if available) + # model_name = llm_result.llm_output.get("model_name", "Unknown Model") + # print(f"\nModel Name: {model_name}") + + # 4. Optionally, handle run_info (if it's included in your version of LangChain) + # This would typically be done if the LLMResult included any runtime info you want to log or analyze. + # Example: + run_info = llm_result.run + logger.debug(f"Run Info: {run_info}\n") + # if run_info: + # print("\nRun Info:") + # for key, value in run_info.items(): + # print(f"{key}: {value}") + + # Format the response message + response_message = { + "query": query_text, "response": generated_texts, "sources": result_metadata[0]['source'], "channel": result_metadata[0]['channel']} + + logger.debug(f"Response Message: {response_message}\n") + return response_message except Exception as e: print(f"Error querying the collection: {e}")