diff --git a/bootstraprag/cli.py b/bootstraprag/cli.py index 8a2935d..4957363 100644 --- a/bootstraprag/cli.py +++ b/bootstraprag/cli.py @@ -17,33 +17,50 @@ def create_zip(project_name): @click.command() @click.argument('project_name') -@click.option('--template', type=click.Choice(['simple-search', 'hybrid-search', 'llamaindex-rag', 'rag-with-cot', 'rag-with-ReACT', 'rag-with-HyDE']), - prompt="Which template would you like to use?") -@click.option('--technology', type=click.Choice(['llamaindex', 'langchain', 'haystack', 'qdrant']), +@click.option('--framework', type=click.Choice(['llamaindex', 'langchain', 'haystack', 'None']), prompt="Which technology would you like to use?") -@click.option('--observability', type=click.Choice(['Yes', 'No']), prompt="Would you like to set up observability?") -@click.option('--api_key', prompt="Please provide your OpenAI API key (leave blank to skip)", default='', - required=False) -@click.option('--data_source', type=click.Choice(['PDF', 'TXT']), - prompt="Which data source would you like to use?") -@click.option('--vector_db', type=click.Choice(['Yes', 'No']), prompt="Would you like to use a vector database?") -def create(project_name, template, technology, observability, api_key, data_source, vector_db): - """Creates a new project with the specified type.""" - template_path = Path(__file__).parent / 'templates' / technology / template +@click.option('--template', type=click.Choice([]), prompt=False) +@click.option('--observability', type=click.Choice([]), prompt=False) +def create(project_name, framework, template, observability): + template_choices = [] + observability_choices = [] + + if framework == 'llamaindex' or framework == 'langchain' or framework == 'haystack': + template_choices = ['simple-rag', 'self-rag', 'rag-with-cot', 'rag-with-react', 'rag-with-hyde'] + elif framework == 'None': + framework = 'qdrant' + template_choices = ['simple-search', 'hybrid-search'] + + template = click.prompt("Which template would you like to use?", + type=click.Choice(template_choices) + ) + if framework == 'llamaindex' or framework == 'langchain' or framework == 'haystack': + observability_choices = ['Yes', 'No'] + observability = click.prompt("Do you wish to enable observability?", + type=click.Choice(observability_choices) + ) + + click.echo(f'You have selected framework: {framework} and template: {template} and observability: {observability}') + download_and_extract_template(project_name, framework, template, observability) + + +def download_and_extract_template(project_name, framework, template, observability_selection): + if observability_selection == 'Yes': + folder_name = template.replace('-', '_') + '_observability' + base_path = Path(__file__).parent / 'templates' / framework / folder_name + else: + base_path = Path(__file__).parent / 'templates' / framework / str(template).replace('-', '_') project_path = Path.cwd() / project_name if project_path.exists(): click.echo(f"Error: Project directory {project_name} already exists!") return - shutil.copytree(template_path, project_path) - - # Optionally handle the API key and other parameters here if necessary - - zip_path = create_zip(project_name) - - click.echo(f"Created {template} project at {project_path}") - click.echo(f"Project archived as {zip_path}") + try: + shutil.copytree(base_path, project_path) + click.echo(f"Project {project_name} created successfully at {project_path}") + except Exception as e: + click.echo(f"Error: {e}") cli.add_command(create) diff --git a/bootstraprag/templates/llamaindex/llamaindex-rag/__init__.py b/bootstraprag/templates/llamaindex/rag_with_hyde/__init__.py similarity index 100% rename from bootstraprag/templates/llamaindex/llamaindex-rag/__init__.py rename to bootstraprag/templates/llamaindex/rag_with_hyde/__init__.py diff --git a/bootstraprag/templates/llamaindex/llamaindex-rag/base_rag.py b/bootstraprag/templates/llamaindex/rag_with_hyde/base_rag.py similarity index 100% rename from bootstraprag/templates/llamaindex/llamaindex-rag/base_rag.py rename to bootstraprag/templates/llamaindex/rag_with_hyde/base_rag.py diff --git a/bootstraprag/templates/llamaindex/llamaindex-rag/main.py b/bootstraprag/templates/llamaindex/rag_with_hyde/main.py similarity index 100% rename from bootstraprag/templates/llamaindex/llamaindex-rag/main.py rename to bootstraprag/templates/llamaindex/rag_with_hyde/main.py diff --git a/bootstraprag/templates/llamaindex/llamaindex-rag/readme.md b/bootstraprag/templates/llamaindex/rag_with_hyde/readme.md similarity index 61% rename from bootstraprag/templates/llamaindex/llamaindex-rag/readme.md rename to bootstraprag/templates/llamaindex/rag_with_hyde/readme.md index 406ebb5..90718e8 100644 --- a/bootstraprag/templates/llamaindex/llamaindex-rag/readme.md +++ b/bootstraprag/templates/llamaindex/rag_with_hyde/readme.md @@ -3,4 +3,6 @@ - Navigate to the root of the project and run the below command - `pip install -r requirements.txt` - In the data folder place your data preferably any ".pdf" +- change the data directory in `main.py` +#### Note: ensure your qdrant and ollama (if LLM models are pointing to local) are running - run `python main.py` diff --git a/bootstraprag/templates/llamaindex/llamaindex-rag/requirements.txt b/bootstraprag/templates/llamaindex/rag_with_hyde/requirements.txt similarity index 100% rename from bootstraprag/templates/llamaindex/llamaindex-rag/requirements.txt rename to bootstraprag/templates/llamaindex/rag_with_hyde/requirements.txt diff --git a/bootstraprag/templates/llamaindex/rag_with_hyde_observability/__init__.py b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bootstraprag/templates/llamaindex/rag_with_hyde_observability/base_rag.py b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/base_rag.py new file mode 100644 index 0000000..5cbcea3 --- /dev/null +++ b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/base_rag.py @@ -0,0 +1,133 @@ +import os + +from llama_index.core import ( + SimpleDirectoryReader, + VectorStoreIndex, + StorageContext, + Settings, + get_response_synthesizer) +from llama_index.core.query_engine import RetrieverQueryEngine, TransformQueryEngine +from llama_index.core.node_parser import SentenceSplitter +from llama_index.core.schema import TextNode, MetadataMode +from llama_index.vector_stores.qdrant import QdrantVectorStore +from llama_index.embeddings.ollama import OllamaEmbedding +# enable if you are using openai +# from llama_index.embeddings.openai import OpenAIEmbedding +from llama_index.llms.ollama import Ollama +# enable if you are using openai +# from llama_index.llms.openai import OpenAI +from llama_index.core.retrievers import VectorIndexRetriever +from llama_index.core.indices.query.query_transform import HyDEQueryTransform +from llama_index.core.base.response.schema import Response, StreamingResponse, AsyncStreamingResponse, PydanticResponse +import qdrant_client +import logging +from dotenv import load_dotenv, find_dotenv +from typing import Union +import phoenix as px +import llama_index + +_ = load_dotenv(find_dotenv()) + +logging.basicConfig(level=int(os.environ['INFO'])) +logger = logging.getLogger(__name__) + +session = px.launch_app() +llama_index.core.set_global_handler("arize_phoenix") + + +class BaseRAG: + RESPONSE_TYPE = Union[ + Response, StreamingResponse, AsyncStreamingResponse, PydanticResponse + ] + + def __init__(self, data_path: str, chunk_size: int = 512, chunk_overlap: int = 200, + required_exts: list[str] = ['.pdf'], + show_progress: bool = False, similarity_top_k: int = 3): + # load the local data directory and chunk the data for further processing + self.docs = SimpleDirectoryReader(input_dir=data_path, required_exts=required_exts).load_data( + show_progress=show_progress) + self.text_parser = SentenceSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap) + + # Create a local Qdrant vector store + logger.info("initializing the vector store related objects") + client = qdrant_client.QdrantClient(url=os.environ['DB_URL'], api_key=os.environ['DB_API_KEY']) + self.vector_store = QdrantVectorStore(client=client, collection_name=os.environ['COLLECTION_NAME']) + + # use your prefered vector embeddings model + logger.info("initializing the OllamaEmbedding") + embed_model = OllamaEmbedding(model_name=os.environ['OLLMA_EMBED_MODEL'], + base_url=os.environ['OLLAMA_BASE_URL']) + # openai embeddings, embedding_model_name="text-embedding-3-large" + # embed_model = OpenAIEmbedding(embed_batch_size=10, model=embedding_model_name) + + # use your prefered llm + llm = Ollama(model=os.environ['OLLMA_LLM_MODEL'], base_url=os.environ['OLLAMA_BASE_URL'], request_timeout=600) + # llm = OpenAI(model="gpt-4o") + + logger.info("initializing the global settings") + Settings.embed_model = embed_model + Settings.llm = llm + + Settings.transformations = [self.text_parser] + + self.text_chunks = [] + self.doc_ids = [] + self.nodes = [] + + self.similarity_top_k = similarity_top_k + self.hyde_query_engine = None + + # preprocess the data like chunking, nodes, metadata etc + self._pre_process() + + def _pre_process(self): + logger.info("enumerating docs") + for doc_idx, doc in enumerate(self.docs): + curr_text_chunks = self.text_parser.split_text(doc.text) + self.text_chunks.extend(curr_text_chunks) + self.doc_ids.extend([doc_idx] * len(curr_text_chunks)) + + logger.info("enumerating text_chunks") + for idx, text_chunk in enumerate(self.text_chunks): + node = TextNode(text=text_chunk) + src_doc = self.docs[self.doc_ids[idx]] + node.metadata = src_doc.metadata + self.nodes.append(node) + + logger.info("enumerating nodes") + for node in self.nodes: + node_embedding = Settings.embed_model.get_text_embedding( + node.get_content(metadata_mode=MetadataMode.ALL) + ) + node.embedding = node_embedding + + # create vector store, index documents and creates retriever + self._create_index_and_retriever() + + def _create_index_and_retriever(self): + logger.info("initializing the storage context") + storage_context = StorageContext.from_defaults(vector_store=self.vector_store) + logger.info("indexing the nodes in VectorStoreIndex") + index = VectorStoreIndex( + nodes=self.nodes, + storage_context=storage_context, + transformations=Settings.transformations, + ) + + logger.info("initializing the VectorIndexRetriever with top_k as 5") + vector_retriever = VectorIndexRetriever(index=index, similarity_top_k=self.similarity_top_k) + response_synthesizer = get_response_synthesizer() + logger.info("creating the RetrieverQueryEngine instance") + vector_query_engine = RetrieverQueryEngine( + retriever=vector_retriever, + response_synthesizer=response_synthesizer, + ) + logger.info("creating the HyDEQueryTransform instance") + hyde = HyDEQueryTransform(include_original=True) + hyde_query_engine = TransformQueryEngine(vector_query_engine, hyde) + + self.hyde_query_engine = hyde_query_engine + + def query(self, query_string: str) -> RESPONSE_TYPE: + response = self.hyde_query_engine.query(str_or_query_bundle=query_string) + return response diff --git a/bootstraprag/templates/llamaindex/rag_with_hyde_observability/main.py b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/main.py new file mode 100644 index 0000000..ad1d245 --- /dev/null +++ b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/main.py @@ -0,0 +1,17 @@ +from base_rag import BaseRAG + +# this step will do pre processing, indexing in vector store, creating retriever (hyDE). +# this may take some time based on your document size and chunk strategy. +base_rag = BaseRAG(show_progress=True, + data_path='') # leaving all the defaults. if needed override them in constructor +# Start a loop to continually get input from the user +while True: + # Get a query from the user + user_query = input("Enter your query [type 'bye' to 'exit']: ") + + # Check if the user wants to terminate the loop + if user_query.lower() == "bye" or user_query.lower() == "exit": + break + + response = base_rag.query(query_string=user_query) + print(response) diff --git a/bootstraprag/templates/llamaindex/rag_with_hyde_observability/readme.md b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/readme.md new file mode 100644 index 0000000..a779bc7 --- /dev/null +++ b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/readme.md @@ -0,0 +1,9 @@ +## Instructions to run the code + +- Navigate to the root of the project and run the below command +- `pip install -r requirements.txt` +- In the data folder place your data preferably any ".pdf" +- change the data directory in `main.py` +#### Note: ensure your qdrant and ollama (if LLM models are pointing to local) are running +- run `python main.py` +- visit http://localhost:6006/ for all the observability diff --git a/bootstraprag/templates/llamaindex/rag_with_hyde_observability/requirements.txt b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/requirements.txt new file mode 100644 index 0000000..d0319fe --- /dev/null +++ b/bootstraprag/templates/llamaindex/rag_with_hyde_observability/requirements.txt @@ -0,0 +1,11 @@ +qdrant-client==1.10.1 +python-dotenv==1.0.1 +qdrant-client==1.10.1 +arize-phoenix==4.14.1 +llama-index==0.10.58 +llama-index-llms-openai==0.1.27 +llama-index-llms-ollama==0.2.0 +llama-index-embeddings-openai==0.1.11 +llama-index-embeddings-ollama==0.1.2 +llama-index-vector-stores-qdrant==0.2.14 +llama-index-callbacks-arize-phoenix==0.1.6 \ No newline at end of file