diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index fdb07cb5aae36..62e7457da3760 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -14,6 +14,7 @@ env: jobs: build-n-publish: name: Build and publish to PyPI + if: github.repository == 'run-llama/llama_index' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/publish_sub_package.yml b/.github/workflows/publish_sub_package.yml new file mode 100644 index 0000000000000..3dd6ae4bc18cb --- /dev/null +++ b/.github/workflows/publish_sub_package.yml @@ -0,0 +1,43 @@ +name: Publish Sub-Package to PyPI if Needed + +on: + push: + branches: + - main + +env: + POETRY_VERSION: "1.6.1" + PYTHON_VERSION: "3.10" + +jobs: + publish_subpackage_if_needed: + if: github.repository == 'run-llama/llama_index' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: ${{ env.POETRY_VERSION }} + - name: Get changed pyproject files + id: changed-files + run: | + echo "changed_files=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | grep -v llama-index-core | grep llama-index | grep pyproject | xargs)" >> $GITHUB_OUTPUT + - name: Publish changed packages + env: + PYPI_TOKEN: ${{ secrets.LLAMA_INDEX_PYPI_TOKEN }} + run: | + for file in ${{ steps.changed-files.outputs.changed_files }}; do + cd `echo $file | sed 's/\/pyproject.toml//g'` + poetry lock + pip install -e . + poetry config pypi-token.pypi $PYPI_TOKEN + poetry publish --build + cd - + done diff --git a/CHANGELOG.md b/CHANGELOG.md index 275302e0025e8..b2676ba77f4da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # ChangeLog +## [0.10.16] - 2024-03-05 + +### New Features + +- Anthropic support for new models (#11623, #11612) +- Easier creation of chat prompts (#11583) +- Added a raptor retriever llama-pack (#11527) +- Improve batch cohere embeddings through bedrock (#11572) +- Added support for vertex AI embeddings (#11561) + +### Bug Fixes / Nits + +- Ensure order in async embeddings generation (#11562) +- Fixed empty metadata for csv reader (#11563) +- Serializable fix for composable retrievers (#11617) +- Fixed milvus metadata filter support (#11566) +- FIxed pydantic import in clickhouse vector store (#11631) +- Fixed system prompts for gemini/vertext-gemini (#11511) + +## [0.10.15] - 2024-03-01 + +### New Features + +- Added FeishuWikiReader (#11491) +- Added videodb retriever integration (#11463) +- Added async to opensearch vector store (#11513) +- New LangFuse one-click callback handler (#11324) + +### Bug Fixes / Nits + +- Fixed deadlock issue with async chat streaming (#11548) +- Improved hidden file check in SimpleDirectoryReader (#11496) +- Fixed null values in document metadata when using SimpleDirectoryReader (#11501) +- Fix for sqlite utils in jsonalyze query engine (#11519) +- Added base url and timeout to ollama multimodal LLM (#11526) +- Updated duplicate handling in query fusion retriever (#11542) +- Fixed bug in kg indexx struct updating (#11475) + ## [0.10.14] - 2024-02-28 ### New Features diff --git a/docs/BUILD b/docs/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/docs/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/docs/community/integrations/uptrain.md b/docs/community/integrations/uptrain.md index d07e25fcbf1b5..da808ae3db271 100644 --- a/docs/community/integrations/uptrain.md +++ b/docs/community/integrations/uptrain.md @@ -1,89 +1,121 @@ # Perform Evaluations on LlamaIndex with UpTrain -**Overview**: In this example, we will see how to use UpTrain with LlamaIndex. +**Overview**: In this example, we will see how to use UpTrain with LlamaIndex. UpTrain ([github](https://github.com/uptrain-ai/uptrain) || [website](https://github.com/uptrain-ai/uptrain/) || [docs](https://docs.uptrain.ai/)) is an open-source platform to evaluate and improve GenAI applications. It provides grades for 20+ preconfigured checks (covering language, code, embedding use cases), performs root cause analysis on failure cases and gives insights on how to resolve them. More details on UpTrain's evaluations can be found [here](https://github.com/uptrain-ai/uptrain?tab=readme-ov-file#pre-built-evaluations-we-offer-). -**Problem**: There are two main problems: +**Problem**: As an increasing number of companies are graduating their LLM prototypes to production-ready applications, their RAG pipelines are also getting complex. Developers are utilising modules like QueryRewrite, Context ReRank, etc., to enhance the accuracy of their RAG systems. -1. The data that most Large Language Models are trained on is not representative of the data that they are used on. This leads to a mismatch between the training and test distributions, which can lead to poor performance. -2. The results generated by Large Language Models are not always reliable. The responses might not be relevant to the prompt, not align with the desired tone or the context, or might be offensive etc. +With increasing complexity comes more points of failure. -**Solution**: The above two problems are solved by two different tools and we will show you how to use them together: +1. Advanced Evals are needed to evaluate the quality of these newer modules and determine if they actually improve the system's accuracy. +2. A robust experimentation framework is needed to systematically test different modules and make data-driven decisions. -1. LlamaIndex solves the first problem by allowing you to perform Retrieval Augmented Generation (RAG) with a retriever that is fine-tuned on your own data. This allows you to use your own data to fine-tune a retriever, and then use that retriever to perform RAG. -2. UpTrain solves the second problem by allowing you to perform evaluations on the generated responses. This helps you to ensure that the responses are relevant to the prompt, align with the desired tone or the context, and are not offensive etc. +**Solution**: UpTrain helps to solve for both: + +1. UpTrain provides a series of checks to evaluate the quality of generated response, retrieved-context as well as all the interim steps. The relevant checks are ContextRelevance, SubQueryCompleteness, ContextReranking, ContextConciseness, FactualAccuracy, ContextUtilization, ResponseCompleteness, ResponseConciseness, etc. +2. UpTrain also allows you to experiment with different embedding models as well as have an "evaluate_experiments" method to compare different RAG configurations. # How to go about it? -There two ways you can use UpTrain with LlamaIndex: +There are two ways you can use UpTrain with LlamaIndex: -1. **Using the UpTrain Callback Handler**: This method allows you to seamlessly integrate UpTrain with LlamaIndex. You can simply add UpTrainCallbackHandler to your existing LlamaIndex pipeline and it will take care of sending the generated responses to the UpTrain Managed Service for evaluations. This is the recommended method as it is the easiest to use and provides you with dashboards and insights with minimal effort. +1. **Using the UpTrain Callback Handler**: This method allows you to seamlessly integrate UpTrain with LlamaIndex. You can simply add UpTrainCallbackHandler to your existing LlamaIndex pipeline and it will evaluate all components of your RAG pipeline. This is the recommended method as it is the easiest to use and provides you with dashboards and insights with minimal effort. 2. **Using UpTrain's EvalLlamaIndex**: This method allows you to use UpTrain to perform evaluations on the generated responses. You can use the EvalLlamaIndex object to generate responses for the queries and then perform evaluations on the responses. You can find a detailed tutorial on how to do this below. This method offers more flexibility and control over the evaluations, but requires more effort to set up and use. # 1. Using the UpTrain Callback Handler Open In Colab -Three additional evaluations for Llamaindex have been introduced, complementing existing ones. These evaluations run automatically, with results displayed in the output. More details on UpTrain's evaluations can be found [here](https://github.com/uptrain-ai/uptrain?tab=readme-ov-file#pre-built-evaluations-we-offer-). +Below is how to use UpTrain Callback Handler to evaluate different components of your RAG pipelines. + +## 1. **RAG Query Engine Evaluations**: + +The RAG query engine plays a crucial role in retrieving context and generating responses. To ensure its performance and response quality, we conduct the following evaluations: + +- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not. +- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context. +- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively. + +## 2. **Sub-Question Query Generation Evaluation**: + +The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using an RAG query engine. To measure it's accuracy, we use: + +- **[Sub Query Completeness](https://docs.uptrain.ai/predefined-evaluations/query-quality/sub-query-completeness)**: Assures that the sub-questions accurately and comprehensively cover the original query. + +## 3. **Re-Ranking Evaluations**: + +Re-ranking involves reordering nodes based on relevance to the query and choosing the top nodes. Different evaluations are performed based on the number of nodes returned after re-ranking. + +a. Same Number of Nodes + +- **[Context Reranking](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-reranking)**: Checks if the order of re-ranked nodes is more relevant to the query than the original order. + +b. Different Number of Nodes: + +- **[Context Conciseness](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-conciseness)**: Examines whether the reduced number of nodes still provides all the required information. + +These evaluations collectively ensure the robustness and effectiveness of the RAG query engine, SubQuestionQueryGeneration operator, and the re-ranking process in the LlamaIndex pipeline. + +#### **Note:** -Selected operators from the LlamaIndex pipeline are highlighted for demonstration: +- We have performed evaluations using a basic RAG query engine; the same evaluations can be performed using the advanced RAG query engine as well. +- Same is true for Re-Ranking evaluations, we have performed evaluations using SentenceTransformerRerank, the same evaluations can be performed using other re-rankers as well. ## 1. **RAG Query Engine Evaluations**: The RAG query engine plays a crucial role in retrieving context and generating responses. To ensure its performance and response quality, we conduct the following evaluations: -- **Context Relevance**: Determines if the context extracted from the query is relevant to the response. -- **Factual Accuracy**: Assesses if the LLM is hallcuinating or providing incorrect information. -- **Response Completeness**: Checks if the response contains all the information requested by the query. +- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not. +- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context. +- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively. ## 2. **Sub-Question Query Generation Evaluation**: -The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using a RAG query engine. Given the complexity, we include the previous evaluations and add: +The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using an RAG query engine. To measure it's accuracy, we use: -- **Sub Query Completeness**: Assures that the sub-questions accurately and comprehensively cover the original query. +- **[Sub Query Completeness](https://docs.uptrain.ai/predefined-evaluations/query-quality/sub-query-completeness)**: Assures that the sub-questions accurately and comprehensively cover the original query. ## 3. **Re-Ranking Evaluations**: -Re-ranking involves reordering nodes based on relevance to the query and choosing top n nodes. Different evaluations are performed based on the number of nodes returned after re-ranking. +Re-ranking involves reordering nodes based on relevance to the query and choosing the top nodes. Different evaluations are performed based on the number of nodes returned after re-ranking. a. Same Number of Nodes -- **Context Reranking**: Checks if the order of re-ranked nodes is more relevant to the query than the original order. +- **[Context Reranking](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-reranking)**: Checks if the order of re-ranked nodes is more relevant to the query than the original order. b. Different Number of Nodes: -- **Context Conciseness**: Examines whether the reduced number of nodes still provides all the required information. +- **[Context Conciseness](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-conciseness)**: Examines whether the reduced number of nodes still provides all the required information. These evaluations collectively ensure the robustness and effectiveness of the RAG query engine, SubQuestionQueryGeneration operator, and the re-ranking process in the LlamaIndex pipeline. #### **Note:** - We have performed evaluations using basic RAG query engine, the same evaluations can be performed using the advanced RAG query engine as well. -- Same is true for Re-Ranking evaluations, we have performed evaluations using CohereRerank, the same evaluations can be performed using other re-rankers as well. +- Same is true for Re-Ranking evaluations, we have performed evaluations using SentenceTransformerRerank, the same evaluations can be performed using other re-rankers as well. ## Install Dependencies and Import Libraries Install notebook dependencies. ```bash -pip install -q html2text llama-index pandas tqdm uptrain cohere +%pip install llama-index-readers-web +%pip install llama-index-callbacks-uptrain +%pip install -q html2text llama-index pandas tqdm uptrain torch sentence-transformers ``` Import libraries. ```python -from llama_index import ( - ServiceContext, - VectorStoreIndex, -) -from llama_index.node_parser import SentenceSplitter -from llama_index.readers import SimpleWebPageReader -from llama_index.callbacks import CallbackManager, UpTrainCallbackHandler -from llama_index.postprocessor.cohere_rerank import CohereRerank -from llama_index.service_context import set_global_service_context -from llama_index.query_engine.sub_question_query_engine import ( - SubQuestionQueryEngine, -) -from llama_index.tools.query_engine import QueryEngineTool -from llama_index.tools.types import ToolMetadata +from llama_index.core import Settings, VectorStoreIndex +from llama_index.core.node_parser import SentenceSplitter +from llama_index.readers.web import SimpleWebPageReader +from llama_index.core.callbacks import CallbackManager +from llama_index.callbacks.uptrain.base import UpTrainCallbackHandler +from llama_index.core.query_engine import SubQuestionQueryEngine +from llama_index.core.tools import QueryEngineTool, ToolMetadata +from llama_index.core.postprocessor import SentenceTransformerRerank +from llama_index.llms.openai import OpenAI + +import os ``` ## Setup @@ -123,16 +155,17 @@ Parameters: **Note:** The `project_name_prefix` will be used as prefix for the project names in the UpTrain dashboard. These will be different for different types of evals. For example, if you set project_name_prefix="llama" and perform the sub_question evaluation, the project name will be "llama_sub_question_answering". ```python +os.environ[ + "OPENAI_API_KEY" +] = "sk-***********" # Replace with your OpenAI API key + callback_handler = UpTrainCallbackHandler( key_type="openai", - api_key="sk-******************************", + api_key=os.environ["OPENAI_API_KEY"], project_name_prefix="llama", ) -callback_manager = CallbackManager([callback_handler]) -service_context = ServiceContext.from_defaults( - callback_manager=callback_manager -) -set_global_service_context(service_context) + +Settings.callback_manager = CallbackManager([callback_handler]) ``` ## Load and Parse Documents @@ -158,13 +191,13 @@ nodes = parser.get_nodes_from_documents(documents) UpTrain callback handler will automatically capture the query, context and response once generated and will run the following three evaluations _(Graded from 0 to 1)_ on the response: -- **Context Relevance**: Check if the context extractedfrom the query is relevant to the response. -- **Factual Accuracy**: Check how factually accurate the response is. -- **Response Completeness**: Check if the response contains all the information that the query is asking for. +- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not. +- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context. +- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively. ```python index = VectorStoreIndex.from_documents( - documents, service_context=service_context + documents, ) query_engine = index.as_query_engine() @@ -181,55 +214,66 @@ for query in queries: ``` Question: What did Paul Graham do growing up? + Response: Paul Graham wrote short stories and started programming on the IBM 1401 in 9th grade using an early version of Fortran. Later, he convinced his father to buy a TRS-80, where he wrote simple games, a program to predict rocket heights, and a word processor. + Context Relevance Score: 0.0 Factual Accuracy Score: 1.0 - Response Completeness Score: 0.0 + Response Completeness Score: 1.0 Question: When and how did Paul Graham's mother die? + Response: Paul Graham's mother died when he was 18 years old, from a brain tumor. + Context Relevance Score: 0.0 - Factual Accuracy Score: 1.0 - Response Completeness Score: 0.0 + Factual Accuracy Score: 0.0 + Response Completeness Score: 1.0 Question: What, in Paul Graham's opinion, is the most distinctive thing about YC? - Context Relevance Score: 1.0 - Factual Accuracy Score: 1.0 + Response: The most distinctive thing about Y Combinator, according to Paul Graham, is that instead of deciding for himself what to work on, the problems come to him. Every 6 months, a new batch of startups brings their problems, which then become the focus of YC's work. + + Context Relevance Score: 0.0 + Factual Accuracy Score: 0.5 Response Completeness Score: 1.0 Question: When and how did Paul Graham meet Jessica Livingston? + Response: Paul Graham met Jessica Livingston at a big party at his house in October 2003. + Context Relevance Score: 1.0 - Factual Accuracy Score: 1.0 - Response Completeness Score: 0.5 + Factual Accuracy Score: 0.5 + Response Completeness Score: 1.0 Question: What is Bel, and when and where was it written? + Response: Bel is a new Lisp that was written in Arc. It was developed over a period of 4 years, from March 26, 2015 to October 12, 2019. Most of the work on Bel was done in England, where the author had moved to in the summer of 2016. + Context Relevance Score: 1.0 Factual Accuracy Score: 1.0 - Response Completeness Score: 0.0 + Response Completeness Score: 1.0 Here's an example of the dashboard showing how you can filter and drill down to the failing cases and get insights on the failing cases: ![image-2.png](https://uptrain-assets.s3.ap-south-1.amazonaws.com/images/llamaindex/image-2.png) # 2. Sub-Question Query Engine Evaluation -The **sub question query engine** is used to tackle the problem of answering a complex query using multiple data sources. It first breaks down the complex query into sub questions for each relevant data source, then gather all the intermediate responses and synthesizes a final response. +The **sub-question query engine** is used to tackle the problem of answering a complex query using multiple data sources. It first breaks down the complex query into sub-questions for each relevant data source, then gathers all the intermediate responses and synthesizes a final response. UpTrain callback handler will automatically capture the sub-question and the responses for each of them once generated and will run the following three evaluations _(Graded from 0 to 1)_ on the response: -- **Context Relevance**: Check if the context extractedfrom the query is relevant to the response. -- **Factual Accuracy**: Check how factually accurate the response is. -- **Response Completeness**: Check if the response contains all the information that the query is asking for. +- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not. +- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context. +- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively. In addition to the above evaluations, the callback handler will also run the following evaluation: -- **Sub Query Completeness**: Checks if the sub-questions accurately and completely cover the original query. +- **[Sub Query Completeness](https://docs.uptrain.ai/predefined-evaluations/query-quality/sub-query-completeness)**: Assures that the sub-questions accurately and comprehensively cover the original query. ```python # build index and query engine vector_query_engine = VectorStoreIndex.from_documents( - documents=documents, use_async=True, service_context=service_context + documents=documents, + use_async=True, ).as_query_engine() query_engine_tools = [ @@ -244,7 +288,6 @@ query_engine_tools = [ query_engine = SubQuestionQueryEngine.from_defaults( query_engine_tools=query_engine_tools, - service_context=service_context, use_async=True, ) @@ -253,22 +296,38 @@ response = query_engine.query( ) ``` - Question: What did Paul Graham work on during YC? - Context Relevance Score: 0.5 + Generated 3 sub questions. + [documents] Q: What did Paul Graham work on before Y Combinator? + [documents] Q: What did Paul Graham work on during Y Combinator? + [documents] Q: What did Paul Graham work on after Y Combinator? + [documents] A: Paul Graham worked on a project with Robert and Trevor after Y Combinator. + [documents] A: Paul Graham worked on projects with his colleagues Robert and Trevor before Y Combinator. + [documents] A: Paul Graham worked on writing essays and working on Y Combinator during his time at Y Combinator. +  + + + Question: What did Paul Graham work on after Y Combinator? + Response: Paul Graham worked on a project with Robert and Trevor after Y Combinator. + + Context Relevance Score: 0.0 Factual Accuracy Score: 1.0 Response Completeness Score: 0.5 - Question: What did Paul Graham work on after YC? - Context Relevance Score: 0.5 + Question: What did Paul Graham work on before Y Combinator? + Response: Paul Graham worked on projects with his colleagues Robert and Trevor before Y Combinator. + + Context Relevance Score: 0.0 Factual Accuracy Score: 1.0 Response Completeness Score: 0.5 - Question: What did Paul Graham work on before YC? - Context Relevance Score: 1.0 - Factual Accuracy Score: 1.0 - Response Completeness Score: 0.0 + Question: What did Paul Graham work on during Y Combinator? + Response: Paul Graham worked on writing essays and working on Y Combinator during his time at Y Combinator. + + Context Relevance Score: 0.0 + Factual Accuracy Score: 0.5 + Response Completeness Score: 0.5 Question: How was Paul Grahams life different before, during, and after YC? @@ -280,7 +339,7 @@ Here's an example of the dashboard visualizing the scores of the sub-questions i # 3. Re-ranking -Re-ranking is the process of reordering the nodes based on their relevance to the query. There are multiple classes of re-ranking algorithms offered by Llamaindex. We have used CohereRerank for this example. +Re-ranking is the process of reordering the nodes based on their relevance to the query. There are multiple classes of re-ranking algorithms offered by Llamaindex. We have used LLMRerank for this example. The re-ranker allows you to enter the number of top n nodes that will be returned after re-ranking. If this value remains the same as the original number of nodes, the re-ranker will only re-rank the nodes and not change the number of nodes. Otherwise, it will re-rank the nodes and return the top n nodes. @@ -290,22 +349,28 @@ We will perform different evaluations based on the number of nodes returned afte If the number of nodes returned after re-ranking is the same as the original number of nodes, the following evaluation will be performed: -- **Context Reranking**: Check if the order of the re-ranked nodes is more relevant to the query than the original order. +- **[Context Reranking](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-reranking)**: Checks if the order of re-ranked nodes is more relevant to the query than the original order. ```python -api_key = "**********************************" # Insert cohere API key here -cohere_rerank = CohereRerank( - api_key=api_key, top_n=5 -) # In this example, the number of nodes before re-ranking is 5 and after re-ranking is also 5. +callback_handler = UpTrainCallbackHandler( + key_type="openai", + api_key=os.environ["OPENAI_API_KEY"], + project_name_prefix="llama", +) +Settings.callback_manager = CallbackManager([callback_handler]) + +rerank_postprocessor = SentenceTransformerRerank( + top_n=3, # number of nodes after reranking + keep_retrieval_score=True, +) index = VectorStoreIndex.from_documents( - documents=documents, service_context=service_context + documents=documents, ) query_engine = index.as_query_engine( - similarity_top_k=10, - node_postprocessors=[cohere_rerank], - service_context=service_context, + similarity_top_k=3, # number of nodes before reranking + node_postprocessors=[rerank_postprocessor], ) response = query_engine.query( @@ -316,25 +381,39 @@ response = query_engine.query( Question: What did Sam Altman do in this essay? Context Reranking Score: 0.0 + + Question: What did Sam Altman do in this essay? + Response: Sam Altman was asked to become the president of Y Combinator after the original founders decided to step back and reorganize the company for long-term sustainability. + + Context Relevance Score: 1.0 + Factual Accuracy Score: 1.0 + Response Completeness Score: 0.5 + # 3b. Re-ranking (With different number of nodes) If the number of nodes returned after re-ranking is the lesser as the original number of nodes, the following evaluation will be performed: -- **Context Conciseness**: If the re-ranked nodes are able to provide all the information required by the query. +- **[Context Conciseness](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-conciseness)**: Examines whether the reduced number of nodes still provides all the required information. ```python -api_key = "**********************************" # insert cohere API key here -cohere_rerank = CohereRerank( - api_key=api_key, top_n=2 -) # In this example, the number of nodes before re-ranking is 5 and after re-ranking is 2. +callback_handler = UpTrainCallbackHandler( + key_type="openai", + api_key=os.environ["OPENAI_API_KEY"], + project_name_prefix="llama", +) +Settings.callback_manager = CallbackManager([callback_handler]) + +rerank_postprocessor = SentenceTransformerRerank( + top_n=2, # Number of nodes after re-ranking + keep_retrieval_score=True, +) index = VectorStoreIndex.from_documents( - documents=documents, service_context=service_context + documents=documents, ) query_engine = index.as_query_engine( - similarity_top_k=10, - node_postprocessors=[cohere_rerank], - service_context=service_context, + similarity_top_k=5, # Number of nodes before re-ranking + node_postprocessors=[rerank_postprocessor], ) # Use your advanced RAG @@ -344,18 +423,20 @@ response = query_engine.query( ``` Question: What did Sam Altman do in this essay? - Context Conciseness Score: 1.0 + Context Conciseness Score: 0.0 -# UpTrain's Managed Service Dashboard and Insights -The UpTrain Managed Service offers the following features: + Question: What did Sam Altman do in this essay? + Response: Sam Altman offered unsolicited advice to the author during a visit to California for interviews. + + + Context Relevance Score: 1.0 + Factual Accuracy Score: 1.0 + Response Completeness Score: 0.5 -1. Advanced dashboards with drill-down and filtering options. -1. Identification of insights and common themes among unsuccessful cases. -1. Real-time observability and monitoring of production data. -1. Integration with CI/CD pipelines for seamless regression testing. +# UpTrain's Managed Service Dashboard and Insights -To define the UpTrain callback handler, the only change required is to set the `key_type` and `api_key` parameters. The rest of the code remains the same. +To use the UpTrain's managed service via the UpTrain callback handler, the only change required is to set the `key_type` and `api_key` parameters. The rest of the code remains the same. ```python callback_handler = UpTrainCallbackHandler( @@ -380,12 +461,13 @@ pip install uptrain llama_index ## Import required libraries ```python +import httpx import os import openai import pandas as pd -from llama_index.core import VectorStoreIndex, SimpleDirectoryReader -from uptrain import Evals, EvalLlamaIndex, Settings +from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings +from uptrain import Evals, EvalLlamaIndex, Settings as UpTrainSettings ``` ## Create the dataset folder for the query engine @@ -399,8 +481,6 @@ if not os.path.exists("nyc_wikipedia"): dataset_path = os.path.join("./nyc_wikipedia", "nyc_text.txt") if not os.path.exists(dataset_path): - import httpx - r = httpx.get(url) with open(dataset_path, "wb") as f: f.write(r.content) @@ -436,8 +516,6 @@ openai.api_key = "sk-************************" # your OpenAI API key Let's create a vector store index using LLamaIndex and then use that as a query engine to retrieve relevant sections from the documentation. ```python -from llama_index.core import Settings - Settings.chunk_size = 512 documents = SimpleDirectoryReader("./nyc_wikipedia/").load_data() @@ -452,7 +530,7 @@ query_engine = vector_index.as_query_engine() # Alternative 1: Evaluate using UpTrain's Open-Source Software (OSS) ```python -settings = Settings( +settings = UpTrainSettings( openai_api_key=openai.api_key, ) ``` @@ -502,7 +580,7 @@ You can create a free UpTrain account [here](https://uptrain.ai/) and get free t UPTRAIN_API_KEY = "up-**********************" # your UpTrain API key # We use `uptrain_access_token` parameter instead of 'openai_api_key' in settings in this case -settings = Settings( +settings = UpTrainSettings( uptrain_access_token=UPTRAIN_API_KEY, ) ``` diff --git a/docs/cookbooks/mixedbread_reranker.ipynb b/docs/cookbooks/mixedbread_reranker.ipynb new file mode 100644 index 0000000000000..1f95a31e234ba --- /dev/null +++ b/docs/cookbooks/mixedbread_reranker.ipynb @@ -0,0 +1,280 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "964030f7-40e4-4398-a5ab-668aabcf3bad", + "metadata": {}, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "id": "360313ab-9393-430e-9647-e0d5545809b9", + "metadata": {}, + "source": [ + "# mixedbread Rerank Cookbook\n", + "\n", + "mixedbread.ai has released three fully open-source reranker models under the Apache 2.0 license. For more in-depth information, you can check out their detailed [blog post](https://www.mixedbread.ai/blog/mxbai-rerank-v1). The following are the three models:\n", + "\n", + "1. `mxbai-rerank-xsmall-v1`\n", + "2. `mxbai-rerank-base-v1`\n", + "3. `mxbai-rerank-large-v1`\n", + "\n", + "In this notebook, we'll demonstrate how to use the `mxbai-rerank-base-v1` model with the `SentenceTransformerRerank` module in LlamaIndex. This setup allows you to seamlessly swap in any reranker model of your choice using the `SentenceTransformerRerank` module to enhance your RAG pipeline." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "856ecfdc-04fa-4fe9-a81c-9a5858cd4a6d", + "metadata": {}, + "source": [ + "### Installation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfb5314f-e6c7-409c-86df-8e1a5ca59adb", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install llama-index\n", + "!pip install sentence-transformers" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "5f5393fb-b410-4769-9380-0ef90a33b82e", + "metadata": {}, + "source": [ + "### Set API Keys" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9782acf-b0ab-4933-bb41-27cd2a02b5dd", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = \"YOUR OPENAI API KEY\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7596ddf-e1de-4098-81f3-fce504d2da94", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import (\n", + " VectorStoreIndex,\n", + " SimpleDirectoryReader,\n", + ")\n", + "\n", + "from llama_index.core.postprocessor import SentenceTransformerRerank" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8011ff9c-2b82-47b4-983f-4fafc29e3127", + "metadata": {}, + "source": [ + "### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6dd335cb-900b-462f-987a-d4af2aac88fa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2024-03-01 09:52:09-- https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.108.133, 185.199.109.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 75042 (73K) [text/plain]\n", + "Saving to: ‘data/paul_graham/paul_graham_essay.txt’\n", + "\n", + "data/paul_graham/pa 100%[===================>] 73.28K --.-KB/s in 0.007s \n", + "\n", + "2024-03-01 09:52:09 (9.86 MB/s) - ‘data/paul_graham/paul_graham_essay.txt’ saved [75042/75042]\n", + "\n" + ] + } + ], + "source": [ + "!mkdir -p 'data/paul_graham/'\n", + "!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "e482b09c-a0df-4788-a75b-a33ade7001d1", + "metadata": {}, + "source": [ + "### Load Documents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "342c91b8-301f-40ed-9d09-9acdb1bbdc44", + "metadata": {}, + "outputs": [], + "source": [ + "documents = SimpleDirectoryReader(\"./data/paul_graham/\").load_data()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8afdfeb1-57ae-4d2b-ae73-683db205be32", + "metadata": {}, + "source": [ + "### Build Index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47c335e9-dd4d-475c-bade-e2a588e33294", + "metadata": {}, + "outputs": [], + "source": [ + "index = VectorStoreIndex.from_documents(documents=documents)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f1ab8157-dbcb-4588-9b3c-5bd2fc4a721e", + "metadata": {}, + "source": [ + "### Define postprocessor for `mxbai-rerank-base-v1` reranker" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fcc5590-2e58-4a7e-8b18-a7153c06d1ff", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core.postprocessor import SentenceTransformerRerank\n", + "\n", + "postprocessor = SentenceTransformerRerank(\n", + " model=\"mixedbread-ai/mxbai-rerank-base-v1\", top_n=2\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c7c81b0d-0449-4092-80cb-88080e69f980", + "metadata": {}, + "source": [ + "### Create Query Engine\n", + "\n", + "We will first retrieve 10 relevant nodes and pick top-2 nodes using the defined postprocessor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1b23700-15ae-4f1a-9443-43eb1eecab5f", + "metadata": {}, + "outputs": [], + "source": [ + "query_engine = index.as_query_engine(\n", + " similarity_top_k=10,\n", + " node_postprocessors=[postprocessor],\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "93871f9c-8871-4f43-8ee9-b3ca4e403d86", + "metadata": {}, + "source": [ + "### Test Queries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "658d3092-7d86-4520-83a2-c3e630dc02b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sam Altman initially declined the offer of becoming president of Y Combinator because he wanted to start a startup focused on making nuclear reactors.\n" + ] + } + ], + "source": [ + "response = query_engine.query(\n", + " \"Why did Sam Altman decline the offer of becoming president of Y Combinator?\",\n", + ")\n", + "\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "497e715e-3f7a-4140-a3ba-34356e473702", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Paul Graham started YC because he and his partners wanted to create an investment firm where they could implement their own ideas and provide the kind of support to startups that they felt was lacking when they were founders themselves. They aimed to not only make seed investments but also assist startups with various aspects of setting up a company, similar to the help they had received from others in the past.\n" + ] + } + ], + "source": [ + "response = query_engine.query(\n", + " \"Why did Paul Graham start YC?\",\n", + ")\n", + "\n", + "print(response)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/agent/custom_agent.ipynb b/docs/examples/agent/custom_agent.ipynb index 7a26db4db1cfe..729d6cec4dd41 100644 --- a/docs/examples/agent/custom_agent.ipynb +++ b/docs/examples/agent/custom_agent.ipynb @@ -79,7 +79,7 @@ " Task,\n", " AgentChatResponse,\n", ")\n", - "from typing import Dict, Any, List, Tuple\n", + "from typing import Dict, Any, List, Tuple, Optional\n", "from llama_index.core.tools import BaseTool, QueryEngineTool\n", "from llama_index.core.program import LLMTextCompletionProgram\n", "from llama_index.core.output_parsers import PydanticOutputParser\n", @@ -200,7 +200,7 @@ " return {\"count\": 0, \"current_reasoning\": []}\n", "\n", " def _run_step(\n", - " self, state: Dict[str, Any], task: Task\n", + " self, state: Dict[str, Any], task: Task, input: Optional[str] = None\n", " ) -> Tuple[AgentChatResponse, bool]:\n", " \"\"\"Run step.\n", "\n", diff --git a/docs/examples/callbacks/LangfuseCallbackHandler.ipynb b/docs/examples/callbacks/LangfuseCallbackHandler.ipynb new file mode 100644 index 0000000000000..639178110583d --- /dev/null +++ b/docs/examples/callbacks/LangfuseCallbackHandler.ipynb @@ -0,0 +1,288 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "d6509c3a", + "metadata": {}, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "id": "c0d8b66c", + "metadata": {}, + "source": [ + "# Langfuse Callback Handler\n", + "\n", + "[Langfuse](https://langfuse.com/docs) is an open source LLM engineering platform to help teams collaboratively debug, analyze and iterate on their LLM Applications.\n", + "\n", + "The `LangfuseCallbackHandler` is integrated with Langfuse and empowers you to seamlessly track and monitor performance, traces, and metrics of your LlamaIndex application. Detailed traces of the LlamaIndex context augmentation and the LLM querying processes are captured and can be inspected directly in the Langfuse UI." + ] + }, + { + "cell_type": "markdown", + "id": "4a59a00e", + "metadata": {}, + "source": [ + "![langfuse-tracing](https://static.langfuse.com/llamaindex-langfuse-docs.gif)" + ] + }, + { + "cell_type": "markdown", + "id": "3b9057da", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "id": "5d9dfc7f", + "metadata": {}, + "source": [ + "### Install packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49c3527e", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install llama-index llama-index-callbacks-langfuse" + ] + }, + { + "cell_type": "markdown", + "id": "bc10630b", + "metadata": {}, + "source": [ + "### Configure environment" + ] + }, + { + "cell_type": "markdown", + "id": "4c256817", + "metadata": {}, + "source": [ + "If you haven't done yet, [sign up on Langfuse](https://cloud.langfuse.com/auth/sign-up) and obtain your API keys from the project settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "787e836d", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# Langfuse\n", + "os.environ[\"LANGFUSE_SECRET_KEY\"] = \"sk-lf-...\"\n", + "os.environ[\"LANGFUSE_PUBLIC_KEY\"] = \"pk-lf-...\"\n", + "os.environ[\n", + " \"LANGFUSE_HOST\"\n", + "] = \"https://cloud.langfuse.com\" # 🇪🇺 EU region, 🇺🇸 US region: \"https://us.cloud.langfuse.com\"\n", + "\n", + "# OpenAI\n", + "os.environ[\"OPENAI_API_KEY\"] = \"sk-...\"" + ] + }, + { + "cell_type": "markdown", + "id": "1fe2ba01", + "metadata": {}, + "source": [ + "### Register the Langfuse callback handler" + ] + }, + { + "cell_type": "markdown", + "id": "cfef9ddc", + "metadata": {}, + "source": [ + "#### Option 1: Set global LlamaIndex handler" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72afb2b9", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import global_handler, set_global_handler\n", + "\n", + "set_global_handler(\"langfuse\")\n", + "langfuse_callback_handler = global_handler" + ] + }, + { + "cell_type": "markdown", + "id": "0e6557d2", + "metadata": {}, + "source": [ + "#### Option 2: Use Langfuse callback directly" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4bdd95bf", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import Settings\n", + "from llama_index.core.callbacks import CallbackManager\n", + "from langfuse.llama_index import LlamaIndexCallbackHandler\n", + "\n", + "langfuse_callback_handler = LlamaIndexCallbackHandler()\n", + "Settings.callback_manager = CallbackManager([langfuse_callback_handler])" + ] + }, + { + "cell_type": "markdown", + "id": "e3e03ce7", + "metadata": {}, + "source": [ + "### Flush events to Langfuse" + ] + }, + { + "cell_type": "markdown", + "id": "e2c811ec", + "metadata": {}, + "source": [ + "The Langfuse SDKs queue and batches events in the background to reduce the number of network requests and improve overall performance. Before exiting your application, make sure all queued events have been flushed to Langfuse servers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e28876c", + "metadata": {}, + "outputs": [], + "source": [ + "# ... your LlamaIndex calls here ...\n", + "\n", + "langfuse_callback_handler.flush()" + ] + }, + { + "cell_type": "markdown", + "id": "6b86f1b5", + "metadata": {}, + "source": [ + "Done!✨ Traces and metrics from your LlamaIndex application are now automatically tracked in Langfuse. If you construct a new index or query an LLM with your documents in context, your traces and metrics are immediately visible in the Langfuse UI. Next, let's take a look at how traces will look in Langfuse." + ] + }, + { + "cell_type": "markdown", + "id": "1f0d4465", + "metadata": {}, + "source": [ + "## Example" + ] + }, + { + "cell_type": "markdown", + "id": "8a9f3428", + "metadata": {}, + "source": [ + "Fetch and save example data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa303ae3", + "metadata": {}, + "outputs": [], + "source": [ + "!mkdir -p 'data/'\n", + "!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham_essay.txt'" + ] + }, + { + "cell_type": "markdown", + "id": "9f053996", + "metadata": {}, + "source": [ + "Run an example index construction, query, and chat." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "983cbedd", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import SimpleDirectoryReader, VectorStoreIndex\n", + "\n", + "# Create index\n", + "documents = SimpleDirectoryReader(\"data\").load_data()\n", + "index = VectorStoreIndex.from_documents(documents)\n", + "\n", + "# Execute query\n", + "query_engine = index.as_query_engine()\n", + "query_response = query_engine.query(\"What did the author do growing up?\")\n", + "print(query_response)\n", + "\n", + "# Execute chat query\n", + "chat_engine = index.as_chat_engine()\n", + "chat_response = chat_engine.chat(\"What did the author do growing up?\")\n", + "print(chat_response)\n", + "\n", + "# As we want to immediately see result in Langfuse, we need to flush the callback handler\n", + "langfuse_callback_handler.flush()" + ] + }, + { + "cell_type": "markdown", + "id": "d5cdd88f", + "metadata": {}, + "source": [ + "Done!✨ You will now see traces of your index and query in your Langfuse project.\n", + "\n", + "Example traces (public links):\n", + "1. [Index construction](https://cloud.langfuse.com/project/clsuh9o2y0000mbztvdptt1mh/traces/1294ed01-8193-40a5-bb4e-2f0723d2c827)\n", + "2. [Query Engine](https://cloud.langfuse.com/project/clsuh9o2y0000mbztvdptt1mh/traces/eaa4ea74-78e0-42ef-ace0-7aa02c6fbbc6)\n", + "3. [Chat Engine](https://cloud.langfuse.com/project/clsuh9o2y0000mbztvdptt1mh/traces/d95914f5-66eb-4520-b996-49e84fd7f323)" + ] + }, + { + "cell_type": "markdown", + "id": "0b50845f", + "metadata": {}, + "source": [ + "## 📚 More details\n", + "\n", + "Check out the full [Langfuse documentation](https://langfuse.com/docs) for more details on Langfuse's tracing and analytics capabilities and how to make most of this integration." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/callbacks/UpTrainCallback.ipynb b/docs/examples/callbacks/UpTrainCallback.ipynb index 7ccc72565c127..d4fb0947a7a31 100644 --- a/docs/examples/callbacks/UpTrainCallback.ipynb +++ b/docs/examples/callbacks/UpTrainCallback.ipynb @@ -13,30 +13,30 @@ "source": [ "# UpTrain Callback Handler\n", "\n", - "This notebook showcases the UpTrain callback handler seamlessly integrating into your pipeline, facilitating diverse evaluations. Three additional evaluations for Llamaindex have been introduced, complementing existing ones. These evaluations run automatically, with results displayed in the output. More details on UpTrain's evaluations can be found [here](https://github.com/uptrain-ai/uptrain?tab=readme-ov-file#pre-built-evaluations-we-offer-). \n", + "UpTrain ([github](https://github.com/uptrain-ai/uptrain) || [website](https://github.com/uptrain-ai/uptrain/) || [docs](https://docs.uptrain.ai/)) is an open-source platform to evaluate and improve GenAI applications. It provides grades for 20+ preconfigured checks (covering language, code, embedding use cases), performs root cause analysis on failure cases and gives insights on how to resolve them. \n", "\n", - "Selected operators from the LlamaIndex pipeline are highlighted for demonstration:\n", + "This notebook showcases how to use UpTrain Callback Handler to evaluate different components of your RAG pipelines.\n", "\n", "## 1. **RAG Query Engine Evaluations**:\n", "The RAG query engine plays a crucial role in retrieving context and generating responses. To ensure its performance and response quality, we conduct the following evaluations:\n", "\n", - "- **Context Relevance**: Determines if the context extracted from the query is relevant to the response.\n", - "- **Factual Accuracy**: Assesses if the LLM is hallcuinating or providing incorrect information.\n", - "- **Response Completeness**: Checks if the response contains all the information requested by the query.\n", + "- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not.\n", + "- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context.\n", + "- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively.\n", "\n", "## 2. **Sub-Question Query Generation Evaluation**:\n", - "The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using a RAG query engine. Given the complexity, we include the previous evaluations and add:\n", + "The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using an RAG query engine. To measure it's accuracy, we use:\n", "\n", - "- **Sub Query Completeness**: Assures that the sub-questions accurately and comprehensively cover the original query.\n", + "- **[Sub Query Completeness](https://docs.uptrain.ai/predefined-evaluations/query-quality/sub-query-completeness)**: Assures that the sub-questions accurately and comprehensively cover the original query.\n", "\n", "## 3. **Re-Ranking Evaluations**:\n", - "Re-ranking involves reordering nodes based on relevance to the query and chosing top n nodes. Different evaluations are performed based on the number of nodes returned after re-ranking.\n", + "Re-ranking involves reordering nodes based on relevance to the query and choosing the top nodes. Different evaluations are performed based on the number of nodes returned after re-ranking.\n", "\n", "a. Same Number of Nodes\n", - "- **Context Reranking**: Checks if the order of re-ranked nodes is more relevant to the query than the original order.\n", + "- **[Context Reranking](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-reranking)**: Checks if the order of re-ranked nodes is more relevant to the query than the original order.\n", "\n", "b. Different Number of Nodes:\n", - "- **Context Conciseness**: Examines whether the reduced number of nodes still provides all the required information.\n", + "- **[Context Conciseness](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-conciseness)**: Examines whether the reduced number of nodes still provides all the required information.\n", "\n", "These evaluations collectively ensure the robustness and effectiveness of the RAG query engine, SubQuestionQueryGeneration operator, and the re-ranking process in the LlamaIndex pipeline." ] @@ -47,7 +47,7 @@ "source": [ "#### **Note:** \n", "- We have performed evaluations using basic RAG query engine, the same evaluations can be performed using the advanced RAG query engine as well.\n", - "- Same is true for Re-Ranking evaluations, we have performed evaluations using CohereRerank, the same evaluations can be performed using other re-rankers as well." + "- Same is true for Re-Ranking evaluations, we have performed evaluations using SentenceTransformerRerank, the same evaluations can be performed using other re-rankers as well." ] }, { @@ -65,10 +65,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install llama-index-postprocessor-cohere-rerank\n", "%pip install llama-index-readers-web\n", - "%pip install llama-index-callback-uptrain\n", - "%pip install -q html2text llama-index pandas tqdm uptrain cohere" + "%pip install llama-index-callbacks-uptrain\n", + "%pip install -q html2text llama-index pandas tqdm uptrain torch sentence-transformers" ] }, { @@ -84,15 +83,14 @@ "metadata": {}, "outputs": [], "source": [ - "from llama_index.core.settings import Settings\n", - "from llama_index.core import VectorStoreIndex\n", + "from llama_index.core import Settings, VectorStoreIndex\n", "from llama_index.core.node_parser import SentenceSplitter\n", "from llama_index.readers.web import SimpleWebPageReader\n", "from llama_index.core.callbacks import CallbackManager\n", "from llama_index.callbacks.uptrain.base import UpTrainCallbackHandler\n", "from llama_index.core.query_engine import SubQuestionQueryEngine\n", "from llama_index.core.tools import QueryEngineTool, ToolMetadata\n", - "from llama_index.core.postprocessor.llm_rerank import LLMRerank\n", + "from llama_index.core.postprocessor import SentenceTransformerRerank\n", "from llama_index.llms.openai import OpenAI\n", "\n", "import os" @@ -141,11 +139,16 @@ "metadata": {}, "outputs": [], "source": [ + "os.environ[\n", + " \"OPENAI_API_KEY\"\n", + "] = \"sk-************\" # Replace with your OpenAI API key\n", + "\n", "callback_handler = UpTrainCallbackHandler(\n", " key_type=\"openai\",\n", - " api_key=\"sk-...\", # replace with your OpenAI API key\n", + " api_key=os.environ[\"OPENAI_API_KEY\"],\n", " project_name_prefix=\"llama\",\n", ")\n", + "\n", "Settings.callback_manager = CallbackManager([callback_handler])" ] }, @@ -200,9 +203,9 @@ "metadata": {}, "source": [ "UpTrain callback handler will automatically capture the query, context and response once generated and will run the following three evaluations *(Graded from 0 to 1)* on the response:\n", - "- **Context Relevance**: Check if the context extractedfrom the query is relevant to the response.\n", - "- **Factual Accuracy**: Check how factually accurate the response is.\n", - "- **Response Completeness**: Check if the response contains all the information that the query is asking for." + "- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not.\n", + "- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context.\n", + "- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively." ] }, { @@ -214,7 +217,10 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-14 16:04:09.869\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:01<00:00, 1.33s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.36s/it]\n", + "100%|██████████| 1/1 [00:03<00:00, 3.50s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.32s/it]\n" ] }, { @@ -223,8 +229,9 @@ "text": [ "\n", "Question: What did Paul Graham do growing up?\n", - "Response: Growing up, Paul Graham worked on writing and programming. He wrote short stories and also tried his hand at programming on the IBM 1401 computer that his school district had. He later got a microcomputer, a TRS-80, and started programming more extensively, creating simple games and even a word processor.\n", - "Context Relevance Score: 0.5\n", + "Response: Growing up, Paul Graham worked on writing short stories and programming. He started programming on an IBM 1401 in 9th grade using an early version of Fortran. Later, he got a TRS-80 computer and wrote simple games, a rocket prediction program, and a word processor. Despite his interest in programming, he initially planned to study philosophy in college before eventually switching to AI.\n", + "\n", + "Context Relevance Score: 0.0\n", "Factual Accuracy Score: 1.0\n", "Response Completeness Score: 1.0\n", "\n" @@ -234,7 +241,10 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-14 16:04:36.895\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:01<00:00, 1.59s/it]\n", + "100%|██████████| 1/1 [00:00<00:00, 1.01it/s]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.76s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.28s/it]\n" ] }, { @@ -243,10 +253,11 @@ "text": [ "\n", "Question: When and how did Paul Graham's mother die?\n", - "Response: The context information does not provide any information about Paul Graham's mother or her death.\n", + "Response: Paul Graham's mother died when he was 18 years old, from a brain tumor.\n", + "\n", "Context Relevance Score: 0.0\n", "Factual Accuracy Score: 0.0\n", - "Response Completeness Score: 0.0\n", + "Response Completeness Score: 0.5\n", "\n" ] }, @@ -254,7 +265,10 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-14 16:04:55.245\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:01<00:00, 1.75s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.55s/it]\n", + "100%|██████████| 1/1 [00:03<00:00, 3.39s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.48s/it]\n" ] }, { @@ -263,10 +277,11 @@ "text": [ "\n", "Question: What, in Paul Graham's opinion, is the most distinctive thing about YC?\n", - "Response: The most distinctive thing about YC, according to Paul Graham's opinion, is that it provides a sense of community and support for startup founders. It solves the problem of isolation that founders often face by connecting them with colleagues who understand the challenges they are going through and can offer guidance and support. Additionally, YC fosters a tight-knit alumni community where startups can help each other and even become each other's customers.\n", - "Context Relevance Score: 0.0\n", - "Factual Accuracy Score: 1.0\n", - "Response Completeness Score: 0.5\n", + "Response: The most distinctive thing about Y Combinator, according to Paul Graham, is that instead of deciding for himself what to work on, the problems come to him. Every 6 months, a new batch of startups brings their problems, which then become the focus of YC. This engagement with a variety of startup problems and the direct involvement in solving them is what Graham finds most unique about Y Combinator.\n", + "\n", + "Context Relevance Score: 1.0\n", + "Factual Accuracy Score: 0.3333333333333333\n", + "Response Completeness Score: 1.0\n", "\n" ] }, @@ -274,7 +289,10 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-14 16:05:24.705\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:01<00:00, 1.92s/it]\n", + "100%|██████████| 1/1 [00:00<00:00, 1.20it/s]\n", + "100%|██████████| 1/1 [00:02<00:00, 2.15s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.08s/it]\n" ] }, { @@ -283,9 +301,10 @@ "text": [ "\n", "Question: When and how did Paul Graham meet Jessica Livingston?\n", - "Response: Paul Graham met Jessica Livingston at a party at his house in October 2003. They were introduced to each other by a mutual friend named Maria Daniels. A couple of days later, Paul asked Jessica out and they started dating.\n", - "Context Relevance Score: 0.5\n", - "Factual Accuracy Score: 1.0\n", + "Response: Paul Graham met Jessica Livingston at a big party at his house in October 2003.\n", + "\n", + "Context Relevance Score: 1.0\n", + "Factual Accuracy Score: 0.5\n", "Response Completeness Score: 1.0\n", "\n" ] @@ -294,7 +313,10 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-14 16:05:52.062\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:01<00:00, 1.82s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.14s/it]\n", + "100%|██████████| 1/1 [00:03<00:00, 3.19s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.50s/it]" ] }, { @@ -303,10 +325,18 @@ "text": [ "\n", "Question: What is Bel, and when and where was it written?\n", - "Response: Bel is a new Lisp that was written in Arc. It was written over a period of 4 years, from March 26, 2015, to October 12, 2019. The majority of Bel was written in England, as the author moved there in the summer of 2016.\n", + "Response: Bel is a new Lisp that was written in Arc. It was developed over a period of 4 years, from March 26, 2015 to October 12, 2019. The majority of Bel was written in England.\n", + "\n", "Context Relevance Score: 1.0\n", "Factual Accuracy Score: 1.0\n", - "Response Completeness Score: 0.5\n", + "Response Completeness Score: 1.0\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ "\n" ] } @@ -348,15 +378,15 @@ "source": [ "# 2. Sub-Question Query Engine Evaluation\n", "\n", - "The **sub question query engine** is used to tackle the problem of answering a complex query using multiple data sources. It first breaks down the complex query into sub questions for each relevant data source, then gather all the intermediate reponses and synthesizes a final response.\n", + "The **sub-question query engine** is used to tackle the problem of answering a complex query using multiple data sources. It first breaks down the complex query into sub-questions for each relevant data source, then gathers all the intermediate responses and synthesizes a final response.\n", "\n", "UpTrain callback handler will automatically capture the sub-question and the responses for each of them once generated and will run the following three evaluations *(Graded from 0 to 1)* on the response:\n", - "- **Context Relevance**: Check if the context extractedfrom the query is relevant to the response.\n", - "- **Factual Accuracy**: Check how factually accurate the response is.\n", - "- **Response Completeness**: Check if the response contains all the information that the query is asking for.\n", + "- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the retrieved context has sufficient information to answer the user query or not.\n", + "- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM's response can be verified via the retrieved context.\n", + "- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information required to answer the user query comprehensively.\n", "\n", "In addition to the above evaluations, the callback handler will also run the following evaluation:\n", - "- **Sub Query Completeness**: Checks if the sub-questions accurately and completely cover the original query." + "- **[Sub Query Completeness](https://docs.uptrain.ai/predefined-evaluations/query-quality/sub-query-completeness)**: Assures that the sub-questions accurately and comprehensively cover the original query." ] }, { @@ -372,9 +402,9 @@ "\u001b[1;3;38;2;237;90;200m[documents] Q: What did Paul Graham work on before YC?\n", "\u001b[0m\u001b[1;3;38;2;90;149;237m[documents] Q: What did Paul Graham work on during YC?\n", "\u001b[0m\u001b[1;3;38;2;11;159;203m[documents] Q: What did Paul Graham work on after YC?\n", - "\u001b[0m\u001b[1;3;38;2;237;90;200m[documents] A: Before Y Combinator (YC), Paul Graham worked on a startup called Viaweb.\n", - "\u001b[0m\u001b[1;3;38;2;11;159;203m[documents] A: After leaving Y Combinator, Paul Graham focused on painting. He wanted to see how good he could get at painting if he dedicated his time and effort to it. He spent most of 2014 working on his painting skills, but eventually ran out of steam in November.\n", - "\u001b[0m\u001b[1;3;38;2;90;149;237m[documents] A: During his time at Y Combinator (YC), Paul Graham worked on various projects. He initially intended to work on three things: hacking, writing essays, and working on YC. However, as YC grew and he became more excited about it, it started to take up a lot more of his attention. He also worked on writing essays and was responsible for writing all of YC's internal software in Arc.\n", + "\u001b[0m\u001b[1;3;38;2;11;159;203m[documents] A: After Y Combinator, Paul Graham decided to focus on painting as his next endeavor.\n", + "\u001b[0m\u001b[1;3;38;2;90;149;237m[documents] A: Paul Graham worked on writing essays and working on Y Combinator during YC.\n", + "\u001b[0m\u001b[1;3;38;2;237;90;200m[documents] A: Before Y Combinator, Paul Graham worked on projects with his colleagues Robert and Trevor.\n", "\u001b[0m" ] }, @@ -382,40 +412,65 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-14 08:24:08.958\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n", - "\u001b[32m2024-02-14 08:24:34.450\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 3/3 [00:02<00:00, 1.47it/s]\n", + "100%|██████████| 3/3 [00:00<00:00, 3.28it/s]\n", + "100%|██████████| 3/3 [00:01<00:00, 1.68it/s]\n", + "100%|██████████| 3/3 [00:01<00:00, 2.28it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "Question: What did Paul Graham work on before YC?\n", - "Response: Before Y Combinator (YC), Paul Graham worked on a startup called Viaweb.\n", - "Context Relevance Score: 0.0\n", - "Factual Accuracy Score: 1.0\n", - "Response Completeness Score: 0.5\n", - "\n", "\n", "Question: What did Paul Graham work on after YC?\n", - "Response: After leaving Y Combinator, Paul Graham focused on painting. He wanted to see how good he could get at painting if he dedicated his time and effort to it. He spent most of 2014 working on his painting skills, but eventually ran out of steam in November.\n", - "Context Relevance Score: 1.0\n", + "Response: After Y Combinator, Paul Graham decided to focus on painting as his next endeavor.\n", + "\n", + "Context Relevance Score: 0.0\n", "Factual Accuracy Score: 0.0\n", - "Response Completeness Score: 0.0\n", + "Response Completeness Score: 0.5\n", "\n", "\n", "Question: What did Paul Graham work on during YC?\n", - "Response: During his time at Y Combinator (YC), Paul Graham worked on various projects. He initially intended to work on three things: hacking, writing essays, and working on YC. However, as YC grew and he became more excited about it, it started to take up a lot more of his attention. He also worked on writing essays and was responsible for writing all of YC's internal software in Arc.\n", - "Context Relevance Score: 0.5\n", + "Response: Paul Graham worked on writing essays and working on Y Combinator during YC.\n", + "\n", + "Context Relevance Score: 0.0\n", "Factual Accuracy Score: 1.0\n", "Response Completeness Score: 0.5\n", "\n", + "\n", + "Question: What did Paul Graham work on before YC?\n", + "Response: Before Y Combinator, Paul Graham worked on projects with his colleagues Robert and Trevor.\n", + "\n", + "Context Relevance Score: 0.0\n", + "Factual Accuracy Score: 0.0\n", + "Response Completeness Score: 0.5\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1/1 [00:01<00:00, 1.24s/it]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\n", "Question: How was Paul Grahams life different before, during, and after YC?\n", "Sub Query Completeness Score: 1.0\n", "\n" ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] } ], "source": [ @@ -465,7 +520,7 @@ "source": [ "# 3. Re-ranking \n", "\n", - "Re-ranking is the process of reordering the nodes based on their relevance to the query. There are multiple classes of re-ranking algorithms offered by Llamaindex. We have used CohereRerank for this example.\n", + "Re-ranking is the process of reordering the nodes based on their relevance to the query. There are multiple classes of re-ranking algorithms offered by Llamaindex. We have used LLMRerank for this example.\n", "\n", "The re-ranker allows you to enter the number of top n nodes that will be returned after re-ranking. If this value remains the same as the original number of nodes, the re-ranker will only re-rank the nodes and not change the number of nodes. Otherwise, it will re-rank the nodes and return the top n nodes.\n", "\n", @@ -479,7 +534,8 @@ "## 3a. Re-ranking (With same number of nodes)\n", "\n", "If the number of nodes returned after re-ranking is the same as the original number of nodes, the following evaluation will be performed:\n", - "- **Context Reranking**: Check if the order of the re-ranked nodes is more relevant to the query than the original order." + "\n", + "- **[Context Reranking](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-reranking)**: Checks if the order of re-ranked nodes is more relevant to the query than the original order." ] }, { @@ -491,7 +547,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-13 20:00:17.459\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:01<00:00, 1.89s/it]\n" ] }, { @@ -500,28 +556,62 @@ "text": [ "\n", "Question: What did Sam Altman do in this essay?\n", + "Context Reranking Score: 1.0\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1/1 [00:01<00:00, 1.88s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.44s/it]\n", + "100%|██████████| 1/1 [00:02<00:00, 2.77s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.45s/it]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Question: What did Sam Altman do in this essay?\n", + "Response: Sam Altman was asked to become the president of Y Combinator after the original founders decided to step down and reorganize the company for long-term sustainability.\n", + "\n", "Context Relevance Score: 1.0\n", "Factual Accuracy Score: 1.0\n", - "Response Completeness Score: 1.0\n", + "Response Completeness Score: 0.5\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ "\n" ] } ], "source": [ - "os.environ[\"OPENAI_API_KEY\"] = \"sk-...\" # Replace with your OpenAI API key\n", - "llm = OpenAI(model=\"gpt-4-turbo-preview\")\n", + "callback_handler = UpTrainCallbackHandler(\n", + " key_type=\"openai\",\n", + " api_key=os.environ[\"OPENAI_API_KEY\"],\n", + " project_name_prefix=\"llama\",\n", + ")\n", + "Settings.callback_manager = CallbackManager([callback_handler])\n", "\n", - "cohere_rerank = LLMRerank(\n", - " llm=llm, top_n=5\n", - ") # In this example, the number of nodes before re-ranking is 5 and after re-ranking is also 5.\n", + "rerank_postprocessor = SentenceTransformerRerank(\n", + " top_n=3, # number of nodes after reranking\n", + " keep_retrieval_score=True,\n", + ")\n", "\n", "index = VectorStoreIndex.from_documents(\n", " documents=documents,\n", ")\n", "\n", "query_engine = index.as_query_engine(\n", - " similarity_top_k=10,\n", - " node_postprocessors=[cohere_rerank],\n", + " similarity_top_k=3, # number of nodes before reranking\n", + " node_postprocessors=[rerank_postprocessor],\n", ")\n", "\n", "response = query_engine.query(\n", @@ -536,7 +626,8 @@ "# 3b. Re-ranking (With different number of nodes)\n", "\n", "If the number of nodes returned after re-ranking is the lesser as the original number of nodes, the following evaluation will be performed:\n", - "- **Context Conciseness**: If the re-ranked nodes are able to provide all the information required by the query." + "\n", + "- **[Context Conciseness](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-conciseness)**: Examines whether the reduced number of nodes still provides all the required information." ] }, { @@ -548,7 +639,27 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2024-02-13 20:01:39.343\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36muptrain.framework.evalllm\u001b[0m:\u001b[36mevaluate\u001b[0m:\u001b[36m110\u001b[0m - \u001b[1mSending evaluation request for rows 0 to <50 to the Uptrain\u001b[0m\n" + "100%|██████████| 1/1 [00:02<00:00, 2.22s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Question: What did Sam Altman do in this essay?\n", + "Context Conciseness Score: 0.0\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1/1 [00:01<00:00, 1.58s/it]\n", + "100%|██████████| 1/1 [00:00<00:00, 1.19it/s]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.62s/it]\n", + "100%|██████████| 1/1 [00:01<00:00, 1.42s/it]" ] }, { @@ -557,27 +668,41 @@ "text": [ "\n", "Question: What did Sam Altman do in this essay?\n", - "Context Relevance Score: 0.5\n", + "Response: Sam Altman offered unsolicited advice to the author during a visit to California for interviews.\n", + "\n", + "Context Relevance Score: 0.0\n", "Factual Accuracy Score: 1.0\n", - "Response Completeness Score: 1.0\n", + "Response Completeness Score: 0.5\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ "\n" ] } ], "source": [ - "os.environ[\"OPENAI_API_KEY\"] = \"sk-...\" # Replace with your OpenAI API key\n", - "llm = OpenAI(model=\"gpt-4-turbo-preview\")\n", + "callback_handler = UpTrainCallbackHandler(\n", + " key_type=\"openai\",\n", + " api_key=os.environ[\"OPENAI_API_KEY\"],\n", + " project_name_prefix=\"llama\",\n", + ")\n", + "Settings.callback_manager = CallbackManager([callback_handler])\n", "\n", - "cohere_rerank = LLMRerank(\n", - " llm=llm, top_n=2\n", - ") # In this example, the number of nodes before re-ranking is 5 and after re-ranking is 2.\n", + "rerank_postprocessor = SentenceTransformerRerank(\n", + " top_n=2, # Number of nodes after re-ranking\n", + " keep_retrieval_score=True,\n", + ")\n", "\n", "index = VectorStoreIndex.from_documents(\n", " documents=documents,\n", ")\n", "query_engine = index.as_query_engine(\n", - " similarity_top_k=10,\n", - " node_postprocessors=[cohere_rerank],\n", + " similarity_top_k=5, # Number of nodes before re-ranking\n", + " node_postprocessors=[rerank_postprocessor],\n", ")\n", "\n", "# Use your advanced RAG\n", @@ -592,14 +717,7 @@ "source": [ "# UpTrain's Managed Service Dashboard and Insights\n", "\n", - "The UpTrain Managed Service offers the following features:\n", - "\n", - "1. Advanced dashboards with drill-down and filtering options.\n", - "1. Identification of insights and common themes among unsuccessful cases.\n", - "1. Real-time observability and monitoring of production data.\n", - "1. Integration with CI/CD pipelines for seamless regression testing.\n", - "\n", - "To define the UpTrain callback handler, the only change required is to set the `key_type` and `api_key` parameters. The rest of the code remains the same.\n", + "To use the UpTrain's managed service via the UpTrain callback handler, the only change required is to set the `key_type` and `api_key` parameters. The rest of the code remains the same.\n", "\n", "```python\n", "callback_handler = UpTrainCallbackHandler(\n", @@ -622,7 +740,7 @@ ], "metadata": { "kernelspec": { - "display_name": "phoenixdev", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -639,5 +757,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/docs/examples/customization/prompts/chat_prompts.ipynb b/docs/examples/customization/prompts/chat_prompts.ipynb index 8efc743c68d8c..18a4408e92d4c 100644 --- a/docs/examples/customization/prompts/chat_prompts.ipynb +++ b/docs/examples/customization/prompts/chat_prompts.ipynb @@ -8,7 +8,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -41,13 +40,51 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Prompt Setup\n", "\n", - "Below, we take the default prompts and customize them to always answer, even if the context is not helpful." + "Below, we take the default prompts and customize them to always answer, even if the context is not helpful.\n", + "\n", + "We show two ways of setting up the prompts:\n", + "1. Explicitly define ChatMessage and MessageRole objects.\n", + "2. Call ChatPromptTemplate.from_messages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qa_prompt_str = (\n", + " \"Context information is below.\\n\"\n", + " \"---------------------\\n\"\n", + " \"{context_str}\\n\"\n", + " \"---------------------\\n\"\n", + " \"Given the context information and not prior knowledge, \"\n", + " \"answer the question: {query_str}\\n\"\n", + ")\n", + "\n", + "refine_prompt_str = (\n", + " \"We have the opportunity to refine the original answer \"\n", + " \"(only if needed) with some more context below.\\n\"\n", + " \"------------\\n\"\n", + " \"{context_msg}\\n\"\n", + " \"------------\\n\"\n", + " \"Given the new context, refine the original answer to better \"\n", + " \"answer the question: {query_str}. \"\n", + " \"If the context isn't useful, output the original answer again.\\n\"\n", + " \"Original Answer: {existing_answer}\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1. Explicitly Define `ChatMessage` and `MessageRole` objects" ] }, { @@ -67,17 +104,7 @@ " \"Always answer the question, even if the context isn't helpful.\"\n", " ),\n", " ),\n", - " ChatMessage(\n", - " role=MessageRole.USER,\n", - " content=(\n", - " \"Context information is below.\\n\"\n", - " \"---------------------\\n\"\n", - " \"{context_str}\\n\"\n", - " \"---------------------\\n\"\n", - " \"Given the context information and not prior knowledge, \"\n", - " \"answer the question: {query_str}\\n\"\n", - " ),\n", - " ),\n", + " ChatMessage(role=MessageRole.USER, content=qa_prompt_str),\n", "]\n", "text_qa_template = ChatPromptTemplate(chat_text_qa_msgs)\n", "\n", @@ -89,26 +116,50 @@ " \"Always answer the question, even if the context isn't helpful.\"\n", " ),\n", " ),\n", - " ChatMessage(\n", - " role=MessageRole.USER,\n", - " content=(\n", - " \"We have the opportunity to refine the original answer \"\n", - " \"(only if needed) with some more context below.\\n\"\n", - " \"------------\\n\"\n", - " \"{context_msg}\\n\"\n", - " \"------------\\n\"\n", - " \"Given the new context, refine the original answer to better \"\n", - " \"answer the question: {query_str}. \"\n", - " \"If the context isn't useful, output the original answer again.\\n\"\n", - " \"Original Answer: {existing_answer}\"\n", - " ),\n", - " ),\n", + " ChatMessage(role=MessageRole.USER, content=refine_prompt_str),\n", "]\n", "refine_template = ChatPromptTemplate(chat_refine_msgs)" ] }, { - "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2. Call `ChatPromptTemplate.from_messages`\n", + "\n", + "`from_messages` is syntatic sugar that allows you to define a chat prompt template as a list of tuples, with each tuple corresponding to a chat message (\"role\", \"message\"). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import ChatPromptTemplate\n", + "\n", + "# Text QA Prompt\n", + "chat_text_qa_msgs = [\n", + " (\n", + " \"system\",\n", + " \"Always answer the question, even if the context isn't helpful.\",\n", + " ),\n", + " (\"user\", qa_prompt_str),\n", + "]\n", + "text_qa_template = ChatPromptTemplate.from_messages(chat_text_qa_msgs)\n", + "\n", + "# Refine Prompt\n", + "chat_refine_msgs = [\n", + " (\n", + " \"system\",\n", + " \"Always answer the question, even if the context isn't helpful.\",\n", + " ),\n", + " (\"user\", refine_prompt_str),\n", + "]\n", + "refine_template = ChatPromptTemplate.from_messages(chat_refine_msgs)" + ] + }, + { "cell_type": "markdown", "metadata": {}, "source": [ @@ -165,7 +216,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -181,7 +231,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "I'm sorry, but the given context does not provide any information about Joe Biden.\n" + "I'm unable to provide an answer to that question based on the context information provided.\n" ] } ], @@ -190,7 +240,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -206,7 +255,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Joe Biden is the 46th President of the United States.\n" + "Joe Biden is the current President of the United States, having taken office in January 2021. He previously served as Vice President under President Barack Obama from 2009 to 2017.\n" ] } ], @@ -223,9 +272,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "llama_index_v3", "language": "python", - "name": "python3" + "name": "llama_index_v3" }, "language_info": { "codemirror_mode": { diff --git a/docs/examples/discover_llamaindex/document_management/BUILD b/docs/examples/discover_llamaindex/document_management/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/docs/examples/discover_llamaindex/document_management/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/docs/examples/embeddings/bedrock.ipynb b/docs/examples/embeddings/bedrock.ipynb index ef727d08147c5..45bcc63e8328e 100644 --- a/docs/examples/embeddings/bedrock.ipynb +++ b/docs/examples/embeddings/bedrock.ipynb @@ -41,12 +41,12 @@ "metadata": {}, "outputs": [], "source": [ - "embed_model = BedrockEmbedding.from_credentials(\n", + "embed_model = BedrockEmbedding(\n", " aws_access_key_id=os.getenv(\"AWS_ACCESS_KEY_ID\"),\n", " aws_secret_access_key=os.getenv(\"AWS_SECRET_ACCESS_KEY\"),\n", " aws_session_token=os.getenv(\"AWS_SESSION_TOKEN\"),\n", - " aws_region=\"\",\n", - " aws_profile=\"\",\n", + " region_name=\"\",\n", + " profile_name=\"\",\n", ")" ] }, @@ -97,9 +97,7 @@ "source": [ "from llama_index.embeddings.bedrock import BedrockEmbedding\n", "\n", - "model = BedrockEmbedding().from_credentials(\n", - " model_name=\"amazon.titan-embed-g1-text-02\"\n", - ")\n", + "model = BedrockEmbedding(model=\"amazon.titan-embed-g1-text-02\")\n", "embeddings = model.get_text_embedding(\"hello world\")\n", "print(embeddings)" ] @@ -119,15 +117,13 @@ "metadata": {}, "outputs": [], "source": [ - "model = BedrockEmbedding().from_credentials(\n", - " model_name=\"cohere.embed-english-v3\"\n", - ")\n", - "coherePayload = {\n", - " \"texts\": [\"This is a test document\", \"This is another test document\"],\n", - " \"input_type\": \"search_document\",\n", - " \"truncate\": \"NONE\",\n", - "}\n", - "embeddings = model.get_text_embedding(coherePayload)\n", + "model = BedrockEmbedding(model=\"cohere.embed-english-v3\")\n", + "coherePayload = [\"This is a test document\", \"This is another test document\"]\n", + "\n", + "embed1 = model.get_text_embedding(\"This is a test document\")\n", + "print(embed1)\n", + "\n", + "embeddings = model.get_text_embedding_batch(coherePayload)\n", "print(embeddings)" ] }, @@ -144,18 +140,16 @@ "metadata": {}, "outputs": [], "source": [ - "model = BedrockEmbedding().from_credentials(\n", - " model_name=\"cohere.embed-multilingual-v3\"\n", - ")\n", - "coherePayload = {\n", - " \"texts\": [\n", - " \"This is a test document\",\n", - " \"తెలుగు అనేది ద్రావిడ భాషల కుటుంబానికి చెందిన భాష.\",\n", - " ],\n", - " \"input_type\": \"search_document\",\n", - " \"truncate\": \"NONE\",\n", - "}\n", - "embeddings = model.get_text_embedding(coherePayload)\n", + "model = BedrockEmbedding(model=\"cohere.embed-multilingual-v3\")\n", + "coherePayload = [\n", + " \"This is a test document\",\n", + " \"తెలుగు అనేది ద్రావిడ భాషల కుటుంబానికి చెందిన భాష.\",\n", + " \"Esto es una prueba de documento multilingüe.\",\n", + " \"攻殻機動隊\",\n", + " \"Combien de temps ça va prendre ?\",\n", + " \"Документ проверен\",\n", + "]\n", + "embeddings = model.get_text_embedding_batch(coherePayload)\n", "print(embeddings)" ] } diff --git a/docs/examples/evaluation/UpTrain.ipynb b/docs/examples/evaluation/UpTrain.ipynb index 6cb85c2340c9e..ba718a41234dc 100644 --- a/docs/examples/evaluation/UpTrain.ipynb +++ b/docs/examples/evaluation/UpTrain.ipynb @@ -21,7 +21,7 @@ "id": "0958c248", "metadata": {}, "source": [ - "**Overview**: In this example, we will see how to use UpTrain with LlamaIndex. " + "**Overview**: In this example, we will see how to use UpTrain with LlamaIndex. UpTrain ([github](https://github.com/uptrain-ai/uptrain) || [website](https://github.com/uptrain-ai/uptrain/) || [docs](https://docs.uptrain.ai/)) is an open-source platform to evaluate and improve GenAI applications. It provides grades for 20+ preconfigured checks (covering language, code, embedding use cases), performs root cause analysis on failure cases and gives insights on how to resolve them. More details on UpTrain's evaluations can be found [here](https://github.com/uptrain-ai/uptrain?tab=readme-ov-file#pre-built-evaluations-we-offer-).\n" ] }, { @@ -49,12 +49,25 @@ "id": "0b101745", "metadata": {}, "source": [ - "## Install UpTrain and LlamaIndex\n", - "\n", - "\n", - "```bash\n", - "pip install uptrain llama_index\n", - "```" + "## Install UpTrain and LlamaIndex" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6734276", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install -q uptrain llama-index" ] }, { @@ -70,14 +83,24 @@ "execution_count": null, "id": "6c6e7a1d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/dhruvchawla/Work/llama_index/venv/lib/python3.11/site-packages/lazy_loader/__init__.py:185: RuntimeWarning: subpackages can technically be lazily loaded, but it causes the package to be eagerly loaded even if it is already lazily loaded.So, you probably shouldn't use subpackages with this lazy feature.\n", + " warnings.warn(msg, RuntimeWarning)\n" + ] + } + ], "source": [ + "import httpx\n", "import os\n", "import openai\n", "import pandas as pd\n", "\n", - "from llama_index.core import VectorStoreIndex, SimpleDirectoryReader\n", - "from uptrain import Evals, EvalLlamaIndex, Settings" + "from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings\n", + "from uptrain import Evals, EvalLlamaIndex, Settings as UpTrainSettings" ] }, { @@ -103,8 +126,6 @@ "dataset_path = os.path.join(\"./nyc_wikipedia\", \"nyc_text.txt\")\n", "\n", "if not os.path.exists(dataset_path):\n", - " import httpx\n", - "\n", " r = httpx.get(url)\n", " with open(dataset_path, \"wb\") as f:\n", " f.write(r.content)" @@ -176,8 +197,6 @@ "metadata": {}, "outputs": [], "source": [ - "from llama_index.core import Settings\n", - "\n", "Settings.chunk_size = 512\n", "\n", "documents = SimpleDirectoryReader(\"./nyc_wikipedia/\").load_data()\n", @@ -204,7 +223,7 @@ "metadata": {}, "outputs": [], "source": [ - "settings = Settings(\n", + "settings = UpTrainSettings(\n", " openai_api_key=openai.api_key,\n", ")" ] @@ -306,101 +325,101 @@ " 0\n", " What is the population of New York City?\n", " The population of New York City is 8,804,190 a...\n", - " New York, often called New York City or NYC, i...\n", - " 1.0\n", - " The question is asking for the population of N...\n", - " 1.0\n", - " The question asks for the population of New Yo...\n", + " === Population density ===\\n\\nIn 2020, the cit...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 1\n", " What is the area of New York City?\n", " New York City has a total area of 468.484 squa...\n", - " New York, often called New York City or NYC, i...\n", - " 1.0\n", - " Step 1: The question asks for the area of New ...\n", - " 1.0\n", - " The question asks for the area of New York Cit...\n", + " Some of the natural relief in topography has b...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 2\n", " What is the largest borough in New York City?\n", " Queens is the largest borough in New York City.\n", " ==== Brooklyn ====\\nBrooklyn (Kings County), o...\n", - " 1.0\n", - " The question is asking for the largest borough...\n", - " 1.0\n", - " The question asks for the largest borough in N...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 3\n", " What is the average temperature in New York City?\n", - " The average temperature in New York City is 57...\n", + " The average temperature in New York City is 33...\n", " Similarly, readings of 0 °F (−18 °C) are also ...\n", - " 0.5\n", - " The question is asking for the average tempera...\n", - " 1.0\n", - " The question asks for the average temperature ...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 4\n", " What is the main airport in New York City?\n", - " The main airport in New York City is John F. K...\n", + " John F. Kennedy International Airport\n", " along the Northeast Corridor, and long-distanc...\n", - " 1.0\n", - " The question is asking for the main airport in...\n", - " 1.0\n", - " The question asks for the main airport in New ...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 5\n", " What is the famous landmark in New York City?\n", - " The famous landmark in New York City is the Em...\n", - " A record 66.6 million tourists visited New Yor...\n", - " 1.0\n", - " The question is asking for the famous landmark...\n", - " 1.0\n", - " The question asks for the famous landmark in N...\n", + " The famous landmark in New York City is the St...\n", + " The settlement was named New Amsterdam (Dutch:...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 6\n", " What is the official language of New York City?\n", - " The official language of New York City is not ...\n", + " As many as 800 languages are spoken in New Yor...\n", " === Accent and dialect ===\\n\\nThe New York are...\n", - " 0.0\n", - " The question is asking for the official langua...\n", - " 1.0\n", - " The question asks for the official language of...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 7\n", " What is the currency used in New York City?\n", - " The currency used in New York City is the Unit...\n", + " The currency used in New York City is the US D...\n", " === Real estate ===\\n\\nReal estate is a major ...\n", - " 0.0\n", - " The question is asking for the currency used i...\n", - " 1.0\n", - " The question asks specifically for the currenc...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 8\n", " What is the time zone of New York City?\n", " Eastern Standard Time (EST)\n", " According to the New York City Comptroller, wo...\n", - " 0.0\n", - " The question is \"What is the time zone of New ...\n", - " 1.0\n", - " The question asks for the time zone of New Yor...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", " 9\n", " What is the famous sports team in New York City?\n", " The famous sports team in New York City is the...\n", - " ==== Baseball ====\\nNew York has been describe...\n", - " 0.5\n", - " The question is asking for the famous sports t...\n", - " 1.0\n", - " The question asks for the famous sports team i...\n", + " ==== Soccer ====\\nIn soccer, New York City is ...\n", + " None\n", + " None\n", + " None\n", + " None\n", " \n", " \n", "\n", @@ -423,61 +442,49 @@ "0 The population of New York City is 8,804,190 a... \n", "1 New York City has a total area of 468.484 squa... \n", "2 Queens is the largest borough in New York City. \n", - "3 The average temperature in New York City is 57... \n", - "4 The main airport in New York City is John F. K... \n", - "5 The famous landmark in New York City is the Em... \n", - "6 The official language of New York City is not ... \n", - "7 The currency used in New York City is the Unit... \n", + "3 The average temperature in New York City is 33... \n", + "4 John F. Kennedy International Airport \n", + "5 The famous landmark in New York City is the St... \n", + "6 As many as 800 languages are spoken in New Yor... \n", + "7 The currency used in New York City is the US D... \n", "8 Eastern Standard Time (EST) \n", "9 The famous sports team in New York City is the... \n", "\n", - " context score_context_relevance \\\n", - "0 New York, often called New York City or NYC, i... 1.0 \n", - "1 New York, often called New York City or NYC, i... 1.0 \n", - "2 ==== Brooklyn ====\\nBrooklyn (Kings County), o... 1.0 \n", - "3 Similarly, readings of 0 °F (−18 °C) are also ... 0.5 \n", - "4 along the Northeast Corridor, and long-distanc... 1.0 \n", - "5 A record 66.6 million tourists visited New Yor... 1.0 \n", - "6 === Accent and dialect ===\\n\\nThe New York are... 0.0 \n", - "7 === Real estate ===\\n\\nReal estate is a major ... 0.0 \n", - "8 According to the New York City Comptroller, wo... 0.0 \n", - "9 ==== Baseball ====\\nNew York has been describe... 0.5 \n", - "\n", - " explanation_context_relevance \\\n", - "0 The question is asking for the population of N... \n", - "1 Step 1: The question asks for the area of New ... \n", - "2 The question is asking for the largest borough... \n", - "3 The question is asking for the average tempera... \n", - "4 The question is asking for the main airport in... \n", - "5 The question is asking for the famous landmark... \n", - "6 The question is asking for the official langua... \n", - "7 The question is asking for the currency used i... \n", - "8 The question is \"What is the time zone of New ... \n", - "9 The question is asking for the famous sports t... \n", + " context score_context_relevance \\\n", + "0 === Population density ===\\n\\nIn 2020, the cit... None \n", + "1 Some of the natural relief in topography has b... None \n", + "2 ==== Brooklyn ====\\nBrooklyn (Kings County), o... None \n", + "3 Similarly, readings of 0 °F (−18 °C) are also ... None \n", + "4 along the Northeast Corridor, and long-distanc... None \n", + "5 The settlement was named New Amsterdam (Dutch:... None \n", + "6 === Accent and dialect ===\\n\\nThe New York are... None \n", + "7 === Real estate ===\\n\\nReal estate is a major ... None \n", + "8 According to the New York City Comptroller, wo... None \n", + "9 ==== Soccer ====\\nIn soccer, New York City is ... None \n", "\n", - " score_response_conciseness \\\n", - "0 1.0 \n", - "1 1.0 \n", - "2 1.0 \n", - "3 1.0 \n", - "4 1.0 \n", - "5 1.0 \n", - "6 1.0 \n", - "7 1.0 \n", - "8 1.0 \n", - "9 1.0 \n", + " explanation_context_relevance score_response_conciseness \\\n", + "0 None None \n", + "1 None None \n", + "2 None None \n", + "3 None None \n", + "4 None None \n", + "5 None None \n", + "6 None None \n", + "7 None None \n", + "8 None None \n", + "9 None None \n", "\n", - " explanation_response_conciseness \n", - "0 The question asks for the population of New Yo... \n", - "1 The question asks for the area of New York Cit... \n", - "2 The question asks for the largest borough in N... \n", - "3 The question asks for the average temperature ... \n", - "4 The question asks for the main airport in New ... \n", - "5 The question asks for the famous landmark in N... \n", - "6 The question asks for the official language of... \n", - "7 The question asks specifically for the currenc... \n", - "8 The question asks for the time zone of New Yor... \n", - "9 The question asks for the famous sports team i... " + " explanation_response_conciseness \n", + "0 None \n", + "1 None \n", + "2 None \n", + "3 None \n", + "4 None \n", + "5 None \n", + "6 None \n", + "7 None \n", + "8 None \n", + "9 None " ] }, "execution_count": null, @@ -530,7 +537,7 @@ "UPTRAIN_API_KEY = \"up-**********************\" # your UpTrain API key\n", "\n", "# We use `uptrain_access_token` parameter instead of 'openai_api_key' in settings in this case\n", - "settings = Settings(\n", + "settings = UpTrainSettings(\n", " uptrain_access_token=UPTRAIN_API_KEY,\n", ")" ] diff --git a/docs/examples/finetuning/embeddings/BUILD b/docs/examples/finetuning/embeddings/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/docs/examples/finetuning/embeddings/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/docs/examples/llama_hub/llama_pack_ollama.ipynb b/docs/examples/llama_hub/llama_pack_ollama.ipynb index 630e0fa88791b..73bf384ef4094 100644 --- a/docs/examples/llama_hub/llama_pack_ollama.ipynb +++ b/docs/examples/llama_hub/llama_pack_ollama.ipynb @@ -123,8 +123,6 @@ "metadata": {}, "outputs": [], "source": [ - "from ollama_pack.base import OllamaQueryEnginePack\n", - "\n", "# You can use any llama-hub loader to get documents!\n", "ollama_pack = OllamaQueryEnginePack(model=\"llama2\", documents=documents)" ] diff --git a/docs/examples/llm/anthropic.ipynb b/docs/examples/llm/anthropic.ipynb index b4bc70c55886d..7ae2e54511a76 100644 --- a/docs/examples/llm/anthropic.ipynb +++ b/docs/examples/llm/anthropic.ipynb @@ -13,7 +13,13 @@ "id": "72ed6f61-28a7-4f90-8a45-e3f452f95dbd", "metadata": {}, "source": [ - "# Anthropic" + "# Anthropic\n", + "\n", + "Anthropic has recently released its latest models: `Claude 3 Opus`, `Claude 3 Sonnet`, and `Claude 3 Haiku` (which will be available soon). By default, the `claude-2.1 model` is used. This notebook provides guidance on how to utilize these new models.\n", + "\n", + "1. Claude 3 Opus - claude-3-opus-20240229\n", + "2. Claude 3 Sonnet\t- claude-3-sonnet-20240229\n", + "3. Claude 3 Haiku - Coming soon" ] }, { @@ -44,6 +50,32 @@ "!pip install llama-index" ] }, + { + "cell_type": "markdown", + "id": "3cbf8694-ad53-459a-84c1-64de2dadeaf5", + "metadata": {}, + "source": [ + "#### Set Tokenizer\n", + "\n", + "First we want to set the tokenizer, which is slightly different than TikToken.\n", + "\n", + "**NOTE**: The Claude 3 tokenizer has not been updated yet; using the existing Anthropic tokenizer leads to context overflow errors for 200k tokens. We've temporarily set the max tokens for Claude 3 to 180k." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6ac37cb-b588-44c7-8fd9-8eab454900a5", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.llms.anthropic import Anthropic\n", + "from llama_index.core import Settings\n", + "\n", + "tokenizer = Anthropic().tokenizer\n", + "Settings.tokenizer = tokenizer" + ] + }, { "cell_type": "markdown", "id": "b81a3ef6-2ee5-460d-9aa4-f73708774014", @@ -52,6 +84,18 @@ "#### Call `complete` with a prompt" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "85fbba23", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"ANTHROPIC_API_KEY\"] = \"YOUR ANTHROPIC API KEY\"" + ] + }, { "cell_type": "code", "execution_count": null, @@ -64,7 +108,7 @@ "# To customize your API key, do this\n", "# otherwise it will lookup ANTHROPIC_API_KEY from your env variable\n", "# llm = Anthropic(api_key=\"\")\n", - "llm = Anthropic()\n", + "llm = Anthropic(model=\"claude-3-opus-20240229\")\n", "\n", "resp = llm.complete(\"Paul Graham is \")" ] @@ -79,21 +123,21 @@ "name": "stdout", "output_type": "stream", "text": [ - " Here are some key facts about Paul Graham:\n", + "Paul Graham is a well-known entrepreneur, programmer, venture capitalist, and essayist. He is best known for co-founding Viaweb, one of the first web application companies, which was later sold to Yahoo! in 1998 and became Yahoo! Store. Graham is also the co-founder of Y Combinator, a highly successful startup accelerator that has helped launch numerous successful companies, such as Dropbox, Airbnb, and Reddit.\n", "\n", - "- Paul Graham is an American computer scientist, venture capitalist, and essayist. He is known for co-founding Viaweb, one of the first web-based application companies, which was acquired by Yahoo in 1998.\n", + "Some key points about Paul Graham:\n", "\n", - "- In 1995, Graham co-founded Viaweb with Robert Morris, Trevor Blackwell, and Jessica Livingston. The company helped popularize the business model of applying software as a service.\n", + "1. Programming: Graham is a skilled programmer and has written extensively on the subject, including his book \"Hackers & Painters: Big Ideas from the Computer Age.\"\n", "\n", - "- After selling Viaweb to Yahoo, Graham became a venture capitalist. He co-founded Y Combinator in 2005 with Jessica Livingston, Trevor Blackwell, and Robert Morris. Y Combinator is an influential startup accelerator that provides seed funding and advice to startups.\n", + "2. Essays: He is a prolific essayist, writing on various topics related to technology, startups, and entrepreneurship. His essays have been influential in the tech startup community.\n", "\n", - "- Graham has written several influential essays on startups, technology, and programming. Some of his most well-known essays include \"How to Start a Startup\", \"Do Things that Don't Scale\", and \"Beating the Averages\" about Lisp programming. \n", + "3. Lisp: Graham is an advocate for the Lisp programming language and has written several essays on its advantages.\n", "\n", - "- He pioneered the concept of using online essays to attract startup founders to apply to Y Combinator's program. His essays are often required reading in Silicon Valley.\n", + "4. Y Combinator: As a co-founder of Y Combinator, Graham has played a significant role in shaping the startup ecosystem and has mentored and invested in numerous successful companies.\n", "\n", - "- Graham has a Bachelor's degree in philosophy from Cornell University and a PhD in computer science from Harvard University. His doctoral thesis focused on Lisp compilers.\n", + "5. Wealth and inequality: In recent years, Graham has written about income inequality and the concentration of wealth, sparking discussions and debates within the tech community.\n", "\n", - "- He is considered an influential figure in the tech and startup worlds, known for his insights on startups, programming languages, and technology trends. His writings have shaped the strategies of many founders building startups.\n" + "Overall, Paul Graham is a significant figure in the technology and startup world, known for his contributions as a programmer, investor, and thought leader.\n" ] } ], @@ -125,7 +169,7 @@ " ),\n", " ChatMessage(role=\"user\", content=\"Tell me a story\"),\n", "]\n", - "resp = Anthropic().chat(messages)" + "resp = Anthropic(model=\"claude-3-opus-20240229\").chat(messages)" ] }, { @@ -138,19 +182,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "assistant: Here is a fun pirate story for you:\n", + "assistant: *clears throat and speaks in a pirate accent* Aye, gather 'round me hearties and I'll spin ye a yarn of adventure on the high seas!\n", "\n", - "Yarrr matey! Me name be Captain Redbeard, the most fearsome pirate to sail the seven seas. I be the captain of the good ship Salty Dog, and we be lookin' fer treasure! \n", + "T'was a dark and stormy night when the Black Pearl set sail from Tortuga. The salty sea spray stung me eyes as I stood at the helm, guidin' me beloved ship through the roilin' waves. Me loyal crew scurried about, securin' the riggin' and battening down the hatches. \n", "\n", - "I lost me leg in a battle with the evil Captain Bluebeard years ago. That scallywag got the better of me that time, but I'll have me revenge! Now I got me a peg leg that I can use to stomp the deck or kick me enemies right in the rear! \n", + "Suddenly, the lookout cried \"Ship ahoy!\" and pointed off the starboard bow. I raised me spyglass and spied a Spanish galleon, her decks heavily laden with treasure. The crew gave a hearty cheer - we'd be feastin' and drinkin' well tonight!\n", "\n", - "Me first mate Scurvy Sam be my best friend. We go way back to when we were just lads dreamin' of a pirate's life. He may only have one good eye after losin' the other one to a seagull, but he can still spot treasure from a league away! \n", + "I ordered the crew to ready the cannons as we drew alongside the galleon. \"Fire all!\" I bellowed and the Pearl shook as the guns unleashed a barrage. The Spaniards returned fire but they were no match for me skilled gunners.\n", "\n", - "Today we be sailin' for the fabled Treasure Island, in search of the loot buried long ago by the notorious Captain Flint. Flint was the most ruthless pirate ever to live, but he buried his treasure and no one ever found it. But I have a map, given to me by a dying sailor. I just know it'll lead us right to Flint's trove of rubies, diamonds and mountains of gold! \n", + "We boarded the galleon, swords flashin' and pistols blazin'. The fight was fast and bloody but in the end, the Pearl was victorious! We claimed the treasure as our own - mountains of gold and jewels glintin' in the moonlight.\n", "\n", - "It won't be easy. We may have to fight off Flint's ghost, or deal with tribes of cannibals, or outwit double-crossing thieves. But that's all part of a pirate's life! And when we finally get our hands on that treasure, we'll live like kings. We'll party all night and sleep all day in our fancy pirate cove. \n", + "As we sailed away, I couldn't help but grin. T'was a fine night of piratin' and I knew many more adventures lay ahead for me and me crew. No matter the danger, the Black Pearl would always prevail! Yo ho ho!\n", "\n", - "So hoist the mainsail me hearties, and let's set sail for adventure! Keep a weather eye on the horizon, mateys. Treasure awaits!\n" + "*laughs heartily* And that, me friends, is a taste of the pirate's life. May yer sails always be full and yer horizons bright. Fare thee well!\n" ] } ], @@ -183,7 +227,7 @@ "source": [ "from llama_index.llms.anthropic import Anthropic\n", "\n", - "llm = Anthropic()\n", + "llm = Anthropic(model=\"claude-3-opus-20240229\", max_tokens=100)\n", "resp = llm.stream_complete(\"Paul Graham is \")" ] }, @@ -197,21 +241,9 @@ "name": "stdout", "output_type": "stream", "text": [ - " Here are some key points about Paul Graham:\n", - "\n", - "- Paul Graham is an American computer scientist, venture capitalist, and essayist. He is known for co-founding Viaweb, one of the first web-based applications, which was acquired by Yahoo in 1998.\n", - "\n", - "- In 2005, Graham co-founded Y Combinator, a startup accelerator that provides seed funding and advice to startups. Y Combinator has backed over 2000 companies including Dropbox, Airbnb, Stripe, and Reddit. \n", - "\n", - "- Graham has written extensively about startups, programming, and technology. Some of his most popular essays include \"How to Start a Startup\", \"The Age of the Essay\", and \"Beating the Averages\" about his experiences with Viaweb.\n", + "Paul Graham is a well-known entrepreneur, programmer, venture capitalist, and essayist. He is best known for co-founding Viaweb, one of the first web application companies, which was later sold to Yahoo! in 1998 and became Yahoo! Store. \n", "\n", - "- As an essayist, Graham has a very analytical and insightful writing style. He is skilled at breaking down complex concepts and explaining ideas clearly. His essays cover a wide range of topics including startups, programming, economics, and philosophy.\n", - "\n", - "- In addition to his work with startups, Graham previously worked as a programmer at Yahoo and was also a professor of computer science at Harvard University. He studied mathematics at Cornell University and obtained a PhD in Computer Science from Harvard.\n", - "\n", - "- Graham has advocated for funding and supporting startup founders who may lack traditional credentials like college degrees. He has argued that intelligence, determination, and flexibility are more important than formal education for succeeding in startups.\n", - "\n", - "In summary, Paul Graham is a prominent figure in the tech industry known for his work with startups, programming, and influential writing and perspectives on technology. His ideas have had a major impact on the startup ecosystem." + "After the sale of Viaweb, Graham and his wife Jessica Livingston co-founded Y Combinator in 2005, a highly successful startup accelerator that has helped launch" ] } ], @@ -229,7 +261,7 @@ "source": [ "from llama_index.llms.anthropic import Anthropic\n", "\n", - "llm = Anthropic()\n", + "llm = Anthropic(model=\"claude-3-opus-20240229\")\n", "messages = [\n", " ChatMessage(\n", " role=\"system\", content=\"You are a pirate with a colorful personality\"\n", @@ -249,15 +281,23 @@ "name": "stdout", "output_type": "stream", "text": [ - " Here is a fun pirate story for you:\n", + "*clears throat and speaks in a gruff, piratey voice* \n", + "\n", + "Aye, gather 'round me hearties and I'll spin ye a yarn of adventure on the high seas! \n", + "\n", + "'Twas a dark and stormy night, the kind where the wind howls like a banshee and the waves crash over the deck. Me and me crew were sailin' the Caribbean, searchin' for treasure and glory.\n", "\n", - "Yarrr matey! Me name be Captain Redbeard, the most fearsome pirate to sail the seven seas. I be the captain of the good ship Salty Dog, and we be lookin' fer treasure! \n", + "Suddenly, the lookout cried \"Ship ahoy!\" and sure enough, a Spanish galleon was bearin' down on us, her decks bristlin' with cannons. The scurvy dogs wanted our gold, but I'd sooner walk the plank than surrender!\n", "\n", - "I lost me leg in a battle with the evil Captain Bluebeard years ago. That scallywag got the better of me that time, but I'll have me revenge! Now I got me a peg leg that I can use to kick me enemies right in the behind! Har har!\n", + "\"All hands to battle stations!\" I bellowed. \"Ready the cannons and prepare to board!\" \n", "\n", - "Just last week me crew and I found a map leading to the lost treasure of the island of Rundoon. We set sail right away, braving storms and sea creatures the size of ships! When we got to the island, it were guarded by angry natives with spears and poison darts. Me crew fought 'em off while I snuck into the temple and grabbed the treasure chest.\n", + "A mighty battle erupted, cannons boomin' and swords clashin'. We swung over on ropes and fought the Spaniards hand-to-hand on the pitchin' and rollin' deck. Me cutlass was a blur as I dueled their captain, a big brute with a wicked scar.\n", "\n", - "Now we be rich with dubloons and jewels! I plan to stash me loot on a remote island, then find a tavern and drink grog until I can't stand up straight. Being a pirate captain be a tough life, but someone's got to sail the high seas in search of adventure! Maybe one day I'll get enough treasure to retire and open up a little beach shack...but probably not, cause I love me pirate life too much! Har har har!" + "Finally, I drove me blade into that bilge rat's black heart and he fell dead at me feet. His crew surrendered and we took their ship as a prize. In the hold, we found chests overflowing with gold doubloons and jewels - a king's ransom! \n", + "\n", + "We sailed off into the sunset, our pirate flag snappin' in the breeze, flush with coin and the thrill of victory. And that, me buckos, is a taste of the pirate life! Now who wants some grog?\n", + "\n", + "*laughs heartily*" ] } ], @@ -283,7 +323,7 @@ "source": [ "from llama_index.llms.anthropic import Anthropic\n", "\n", - "llm = Anthropic(model=\"claude-instant-1\")" + "llm = Anthropic(model=\"claude-3-sonnet-20240229\")" ] }, { @@ -306,23 +346,21 @@ "name": "stdout", "output_type": "stream", "text": [ - " Here are a few key facts about Paul Graham:\n", - "\n", - "- Paul Graham is an American computer scientist, venture capitalist, and essayist. He is known for co-founding Viaweb, one of the first web-based application companies, which was acquired by Yahoo in 1998.\n", + "Paul Graham is a computer scientist, entrepreneur, venture capitalist, and author. He is best known for the following:\n", "\n", - "- In 2005, Graham co-founded Y Combinator, a startup accelerator that provides seed funding and advice to startups. Y Combinator has backed over 3,000 startups including Dropbox, Airbnb, Stripe, and Reddit. \n", + "1. Co-founding Y Combinator: Y Combinator is a prominent startup accelerator based in Silicon Valley. It has funded and helped launch thousands of startups, including Airbnb, Dropbox, Stripe, and Reddit.\n", "\n", - "- Graham has written several influential essays on startups, programming languages, and other technology topics. Some of his most well-known essays include \"Beating the Averages\", \"The Refragmentation\", and \"How to Start a Startup\".\n", + "2. Writing essays on startups and technology: Graham has written numerous influential essays on topics related to startups, programming, and entrepreneurship. His essays are widely read and have helped shape the thinking of many entrepreneurs and technologists.\n", "\n", - "- He pioneered and popularized the idea of using Lisp as a web programming language via his company Viaweb. This helped inspire interest in functional programming languages for web development.\n", + "3. Developing the programming language Arc: In the early 2000s, Graham developed a new programming language called Arc, which was designed to be a more powerful and expressive dialect of Lisp.\n", "\n", - "- Graham has a Bachelor's degree in philosophy from Cornell University and a PhD in computer science from Harvard University. \n", + "4. Advocating for the use of Lisp and functional programming: Graham is a strong proponent of the Lisp programming language and functional programming paradigms. He has written extensively about the benefits of these approaches and has influenced many programmers to explore them.\n", "\n", - "- He was inducted into the American Academy of Arts and Sciences in 2020 for his contributions to computer science and entrepreneurship.\n", + "5. Authoring books: Graham has authored several books, including \"Hackers & Painters: Big Ideas from the Computer Age\" (2004), \"On Lisp\" (1993), and \"ANSI Common Lisp\" (1995).\n", "\n", - "- In addition to his work in technology and startups, Graham is also known for his essays on topics like education, productivity, and economics. Many consider him an influential writer and thinker in the tech industry.\n", + "6. Investing in startups: Through Y Combinator and his own investments, Graham has invested in and advised numerous successful startups, helping to shape the technology industry.\n", "\n", - "In summary, Paul Graham is a prominent computer scientist, entrepreneur, investor and writer who has made significant contributions to the web, startups and programming languages. He continues to share his insights through his writings and his work with Y Combinator." + "Overall, Paul Graham is widely respected in the technology and startup communities for his contributions as a programmer, writer, investor, and advocate for innovative ideas and approaches." ] } ], @@ -348,7 +386,7 @@ "source": [ "from llama_index.llms.anthropic import Anthropic\n", "\n", - "llm = Anthropic()\n", + "llm = Anthropic(\"claude-3-sonnet-20240229\")\n", "resp = await llm.acomplete(\"Paul Graham is \")" ] }, @@ -362,21 +400,19 @@ "name": "stdout", "output_type": "stream", "text": [ - " Here are some key facts about Paul Graham:\n", + "Paul Graham is a computer scientist, entrepreneur, venture capitalist, and author. He is best known for the following:\n", "\n", - "- Paul Graham is an American computer scientist, venture capitalist, and essayist. He is known for co-founding Viaweb, one of the first web-based application companies, which was acquired by Yahoo in 1998.\n", + "1. Co-founding Y Combinator: Y Combinator is a prominent startup accelerator based in Silicon Valley. It has funded and helped launch many successful startups, including Airbnb, Dropbox, Stripe, and Reddit.\n", "\n", - "- In 1995, Graham co-founded Viaweb with Robert Morris, Trevor Blackwell, and Jessica Livingston. The company helped popularize the business model of applying software as a service.\n", + "2. Writing essays on startups and technology: Graham has written numerous influential essays on topics related to startups, programming, and entrepreneurship. His essays are widely read and have helped shape the thinking of many entrepreneurs and technologists.\n", "\n", - "- After selling Viaweb to Yahoo, Graham became a venture capitalist. He co-founded Y Combinator in 2005 with Jessica Livingston, Trevor Blackwell, and Robert Morris. Y Combinator is an influential startup accelerator that provides seed funding and advice to startups.\n", + "3. Developing the programming language Arc: Graham designed and developed the programming language Arc, which was intended to be a more powerful and expressive dialect of Lisp.\n", "\n", - "- Graham has written several influential essays on startups, technology, and programming. Some of his most well-known essays include \"How to Start a Startup\", \"Do Things that Don't Scale\", and \"Beating the Averages\" about Lisp programming. \n", + "4. Authoring books: He has written several books, including \"Hackers & Painters: Big Ideas from the Computer Age,\" \"ANSI Common Lisp,\" and \"On Lisp.\"\n", "\n", - "- He pioneered the concept of using online essays to attract startup founders to apply to Y Combinator's program. His essays are often required reading in Silicon Valley.\n", + "5. Founding Viaweb: In the 1990s, Graham co-founded Viaweb, one of the earliest web-based application software companies. Viaweb was later acquired by Yahoo! in 1998.\n", "\n", - "- Graham has a Bachelor's degree in philosophy from Cornell University and a PhD in computer science from Harvard University. His doctoral thesis focused on Lisp compilers.\n", - "\n", - "- He is considered an influential figure in the tech and startup worlds, known for his insights on startups, programming languages, and technology trends. His writings have shaped the strategies of many founders building startups.\n" + "Graham is widely respected in the technology and startup communities for his insights, writings, and contributions to the field of computer science and entrepreneurship.\n" ] } ], @@ -401,6 +437,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" + }, + "vscode": { + "interpreter": { + "hash": "b0fa6594d8f4cbf19f97940f81e996739fb7646882a419484c72d19e05852a7e" + } } }, "nbformat": 4, diff --git a/docs/examples/multi_modal/anthropic_multi_modal.ipynb b/docs/examples/multi_modal/anthropic_multi_modal.ipynb new file mode 100644 index 0000000000000..21d78461bca1e --- /dev/null +++ b/docs/examples/multi_modal/anthropic_multi_modal.ipynb @@ -0,0 +1,621 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "368686b4-f487-4dd4-aeff-37823976529d", + "metadata": {}, + "source": [ + "\"Open\n", + "\n", + "# Multi-Modal LLM using Anthropic model for image reasoning\n", + "\n", + "Anthropic has recently released its latest Multi modal models: Claude 3 Opus, Claude 3 Sonnet.\n", + "\n", + "1. Claude 3 Opus - claude-3-opus-20240229\n", + "\n", + "2. Claude 3 Sonnet - claude-3-sonnet-20240229\n", + "\n", + "In this notebook, we show how to use Anthropic MultiModal LLM class/abstraction for image understanding/reasoning.\n", + "\n", + "We also show several functions we are now supporting for Anthropic MultiModal LLM:\n", + "* `complete` (both sync and async): for a single prompt and list of images\n", + "* `chat` (both sync and async): for multiple chat messages\n", + "* `stream complete` (both sync and async): for steaming output of complete\n", + "* `stream chat` (both sync and async): for steaming output of chat" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "396d319e", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install llama-index-multi-modal-llms-anthropic\n", + "!pip install llama-index-vector-stores-qdrant\n", + "!pip install matplotlib" + ] + }, + { + "cell_type": "markdown", + "id": "4479bf64", + "metadata": {}, + "source": [ + "## Use Anthropic to understand Images from Local directory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5455d8c6", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"ANTHROPIC_API_KEY\"] = \"\" # Your ANTHROPIC API key here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4990a807", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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zZrFP/3TPx3Sl/d/+5jdfPPNvOpVr2LSpEkIIbj66qsnvP9sjdEDDzzASSedRCqVYsqUKfzLv/zL099Z9SKzK664QiUSCfXd735XPfTQQ+rd7363am5uVn19fc/3rj0vduaZZ6rvfe97av369er+++9XZ599tpo6daoaGxuLtnnf+96npkyZoq677jq1du1addxxx6nVq1dH7/u+rxYtWqTWrFmj7rvvPvXb3/5Wtbe3q0996lPPxyE9p3b33Xer6dOnq6OPPlp96EMfil5vjNmhNjAwoKZNm6be/va3q7vuuks9/vjj6ve//73avHlztM0XvvAFVSgU1NVXX63WrVunzj33XDVjxgxVKpWibV7xileoJUuWqDvvvFPdcsstavbs2eqCCy54Pg7pL26f+9znVFtbm/r1r3+ttmzZoq688kqVy+XUV77ylWibxpgp9dvf/lZ9+tOfVj/72c8UoH7+859PeP/ZGKPh4WHV1dWlLrzwQrV+/Xp1+eWXq3Q6rb71rW89rX190TnNlStXqg984APR8yAI1KRJk9Sll176PO7VC8f27dunAHXTTTcppZQaGhpSnuepK6+8Mtrm4YcfVoC64447lFL6gpVSqr1790bbfPOb31RNTU2qUqk8twfwHNro6KiaM2eOuvbaa9Upp5wSOc3GmB3ePvGJT6gTTzzxCd8Pw1B1d3erL37xi9FrQ0NDKplMqssvv1wppdSGDRsUoO65555om2uuuUYJIdSuXbv+cjv/PNk555yj3vGOd0x47TWveY268MILlVKNMTucHew0n60x+sY3vqFaWlom3J+f+MQn1Lx5857W/r2o4Nlqtcq9997LmjVroteklKxZs4Y77rjjedyzF44NDw8DcSeYe++9l1qtNmHM5s+fz9SpU6Mxu+OOO1i8eDFdXV3RNmeeeSYjIyM89NBDz+HeP7f2gQ98gHPOOWfC2EBjzJ7IfvnLX3Lsscfy+te/ns7OTpYtW8Z3vvOd6P0tW7awd+/eCeNWKBRYtWrVhHFrbm7m2GOPjbZZs2YNUkruuuuu5+5gniNbvXo11113HY888ggA69at49Zbb+Wss84CGmN2JPZsjdEdd9zBySefTCKRiLY588wz2bRpE4ODg0e8Py+qLif9/f0EQTBhogLo6upi48aNz9NevXAsDEMuueQSTjjhBBYtWgTA3r17SSQSNDc3T9i2q6uLvXv3Rtscbkztey9Fu+KKK/jTn/7EPffcc8h7jTE7vD3++ON885vf5CMf+Qh/93d/xz333MPf/M3fkEgkuOiii6LjPty41I9bZ2fnhPdd16W1tfUlOW6f/OQnGRkZYf78+TiOQxAEfO5zn+PCCy8EaIzZEdizNUZ79+5lxowZh3yHfa+lpeWI9udF5TQb9uT2gQ98gPXr13Prrbc+37vygrYdO3bwoQ99iGuvvZZUKvV8786LxsIw5Nhjj+Xzn/88AMuWLWP9+vX8x3/8BxdddNHzvHcvTPvJT37CZZddxo9//GMWLlzI/fffzyWXXMKkSZMaY/YitRcVPNve3o7jOIewGPv6+uju7n6e9uqFYR/84Af59a9/zQ033MDkyZOj17u7u6lWqwwNDU3Yvn7Muru7Dzum9r2Xmt17773s27eP5cuX47ouruty00038dWvfhXXdenq6mqM2WGsp6eHo446asJrCxYsYPv27UB83E92f3Z3d7Nv374J7/u+z8DAwEty3D72sY/xyU9+kje96U0sXryYt771rXz4wx/m0ksvBRpjdiT2bI3Rs3XPvqicZiKR4JhjjuG6666LXgvDkOuuu47jjz/+edyz58+UUnzwgx/k5z//Oddff/0h8MMxxxyD53kTxmzTpk1s3749GrPjjz+eBx98cMJFd+2119LU1HTIJPlSsNNPP50HH3yQ+++/P/o79thjufDCC6P/G2N2qJ1wwgmHlDM98sgjTJs2DYAZM2bQ3d09YdxGRka46667Jozb0NAQ9957b7TN9ddfTxiGrFq16jk4iufWisUiUk6cZh3HIQxDoDFmR2LP1hgdf/zx3HzzzdRqtWiba6+9lnnz5h0xNAu8OEtOksmk+v73v682bNig3vOe96jm5uYJLMb/Tfb+979fFQoFdeONN6o9e/ZEf8ViMdrmfe97n5o6daq6/vrr1dq1a9Xxxx+vjj/++Oh9Wz7x8pe/XN1///3qd7/7nero6HhJl08cbPXsWaUaY3Y4u/vuu5Xruupzn/ucevTRR9Vll12mMpmM+u///u9omy984QuqublZ/eIXv1APPPCAevWrX33Y0oBly5apu+66S916661qzpw5L6nyiXq76KKLVG9vb1Ry8rOf/Uy1t7erj3/849E2jTHTTPb77rtP3XfffQpQX/7yl9V9992ntm3bppR6dsZoaGhIdXV1qbe+9a1q/fr16oorrlCZTOalX3KilFJf+9rX1NSpU1UikVArV65Ud9555/O9S8+bAYf9+973vhdtUyqV1F/91V+plpYWlclk1Pnnn6/27Nkz4Xu2bt2qzjrrLJVOp1V7e7v66Ec/qmq12nN8NM+fHew0G2N2ePvVr36lFi1apJLJpJo/f7769re/PeH9MAzVZz7zGdXV1aWSyaQ6/fTT1aZNmyZsc+DAAXXBBReoXC6nmpqa1MUXX6xGR0efy8N4zmxkZER96EMfUlOnTlWpVErNnDlTffrTn55Q9tAYM6VuuOGGw85jF110kVLq2RujdevWqRNPPFElk0nV29urvvCFLzztfW3002xYwxrWsIY17AjtRZXTbFjDGtawhjXs+bSG02xYwxrWsIY17Ait4TQb1rCGNaxhDTtCazjNhjWsYQ1rWMOO0BpOs2ENa1jDGtawI7SG02xYwxrWsIY17Ait4TQb1rCGNaxhDTtCazjNhjWsYQ1rWMOO0BpOs2ENa1jDGtawI7SG02xYwxrWsIY17Ait4TQb1rCGNaxhDTtC+/8BKpOWJscmv2UAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from PIL import Image\n", + "import matplotlib.pyplot as plt\n", + "\n", + "img = Image.open(\"../data/images/prometheus_paper_card.png\")\n", + "plt.imshow(img)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9fab9ec", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import SimpleDirectoryReader\n", + "from llama_index.multi_modal_llms.anthropic import AnthropicMultiModal\n", + "\n", + "# put your local directore here\n", + "image_documents = SimpleDirectoryReader(\n", + " input_files=[\"../data/images/prometheus_paper_card.png\"]\n", + ").load_data()\n", + "\n", + "# Initiated Anthropic MultiModal class\n", + "anthropic_mm_llm = AnthropicMultiModal(max_tokens=300)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f783f64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The image is a diagram titled \"Prometheus: Inducing Fine-Grained Evaluation Capability In Language Models\". It outlines the key components and workflow of the Prometheus system.\n", + "\n", + "The main sections are:\n", + "1. Contributions: Describes Prometheus as an open-source LLM evaluator using custom rubrics and a feedback collection dataset.\n", + "2. Results: States that Prometheus matches or outperforms GPT-4 on 3 evaluation datasets and can function as a reward model. It also enabled reference answers for LM evaluations.\n", + "3. Insights: Notes that strong LLMs show high agreement with human evaluations but their close-to-source nature and uncontrolled versioning make them a less than ideal choice for LLM evaluation.\n", + "4. Technical Bits: Diagrams the Feedback Collection pipeline which uses GPT-4 to generate score rubrics and instructions, then collects human feedback to train the final Prometheus model.\n", + "\n", + "The bottom includes logos, model details, and a small fire graphic. Overall, it provides a high-level technical overview of the Prometheus LLM evaluation system.\n" + ] + } + ], + "source": [ + "response = anthropic_mm_llm.complete(\n", + " prompt=\"Describe the images as an alternative text\",\n", + " image_documents=image_documents,\n", + ")\n", + "\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "id": "f43c0b06", + "metadata": {}, + "source": [ + "## Use `AnthropicMultiModal` to reason images from URLs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40d526d2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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GImPj0ej0bBixcqwaV4QTt2/rKyM3bt3kZ6ejkajpb29g+7uHqqrq/joo4/43Oc+d07T1oWg1+sYHBxEr9czbdo0ior2c/DgAaKjY7BarSQkJJCZmUlOTg5mszk8KBOEoBPRvn37OXmyglmzZjN58mQmTZqE0Whk9+5d9Pf3YzabycrK4tvf/jZRUVH8+te/pqWlZcTWnk/j9HoPFkvwWHAmFwjvuwLCa3gulwulUjli8DV88DJ0dUKm99A7G/o7xPn2fw2XE0YquuH3O/2dCK13hmQLze5Od7U/W1scLmuIc7XnseSiFZjNZqOqqir8uba2lsOHDxMZGUlqairf+MY3+I//+I+wiecHP/gBiYmJ4b1i+fn5rFixgscee4w//vGPeL1ennzySe69996zeiDKXDpn2ytis9nYsWMHvb29Qy7ybm6//Q5SU1MvuNENt4WPRkMd/kJ98slOOju78Ho9mM0WVq9efdb9NueS62x7ckLOBqMxKhw+QvX7/RQXH2TLli186UuPkpSUxAcffEB9fR1PPPHlcIcQUnRvvfUmCxfezPr163nkkUeIioo6q2ffaL78giCEZzfPPfccEydOoqGhAbPZgkqlIiUlhUWLFg/JGlRcoQ441EktWrSI+fPnh7cLrFixgsrKSjZs2MDy5csuy4QoCAIzZsxk7959vPLK30hLS6etrZ20tDSUSgW5ubksXrwkXHfDZzGh2dHdd98dXk/6xz9ep6enZ2h/HZSWltLQUI/FYuXQoUOkpaURCASwWMwXLee5qK6u4fjx49x2220IgsDu3btRKpXU1NQwefJkRFGkubmZRYsWjdhYPHzdafg9Lve9Ottvz/Ze7Ny5k/feexe1WoNCIbJgwUIWL148QgldjMfplVJaw7loBXbw4EEWLVoU/hxyrnjooYd44YUX+Nd//VfsdjuPP/44fX193HTTTWzatGnECO2VV17hySefZMmSJeGNzL/97W9H4XFkzkVojWD//qJwmYdetrffXs+KFSvw+wMMDg4SHR2NWq3G4/EQGxtLXV0d6enp9PT00NPTTXJyCjExMaO+SdHtdlNWVsbs2XPIzs6mubkZj8dDU1MTvb29pKamolKpaG9vx+v1EggEyM3NDXtz9ff3ExcXR3JyMs3NzbS3txMfH8fx46X4fD7mz5+P1WoFRudlUyqVZGZmceJExZCThoLk5GS8Xi8Wi4Wamhr6+/tJT09HFEWSk1OwWq2YTEYcDgctLS1oNGqCrsoJ1NbWkZmZGTZtjVbZ6vV6HnnkEUpKSqivrycnJye8NpSYmDjkun9qFhta7wutkQ0fbatUKrRaDbm5udxzzz1YrRGX3OGGOsjY2Fi+/vWvc+DAAXp7e1i8eDGTJ0+mt7cXYIR5FYIDkTVr1hAbG3fGzHrVqtXs3bsXp9PJ448HlWtERATp6elDpsh2Hn/8caKjY86Q51KQJInu7i7Ky8tYtWoVgiBQV1eLVqujs7OD5uZmamtrqampCc8me3t76evrC2+paG5uJjY2lsTEBCIiIq9IBAsIbrBOSUlh6dJbaG5uZsOGd0hKSsLv94flc7vd1NbWkp6eTkZGxqhaBkaDi1ZgN99886e6cwqCwE9+8hN+8pOfnPOcyMhIedPyVcDj8XDyZAWzZ88ODyjGjx9PUVERH330MXa7nWnTprF582YmT55MS0sLa9euZfPmzcycOYMjR44MeTLaiIiIuOiQOudDp9MxZ84cNm36kMOHD7F48RIOHTpEa2srRqOBTz75hKlTp7JhwzssW7aMo0eP4fG4KS8/QWtrKwUFBezYsYObb76Zw4cPExERwdatW7FaLRgMoxvlJdT5KhQibW2t7Ny5E5VKxcmTJ9Hr9XR0dLB79250Oh2HDh1iyZIlvP/++xQUFBAIBKirq6OxsYHk5BSOHDnMvfcG9wSpVCry8vJGXU6TycT8+fO56aabwiYxQRCIjIwcMfKHYKecnp5+xnWG/202m0esU16OfACxsbGsXLlyxHqq2Ww+67kAeXn5Z5U7KiqKW2+9Nby2G1pnEgSBhQsXjjg+uh2xcMbfkgROp5PW1la6urpoamqiuLiYI0eOUFhYSE9PDzt37iQnJ4eXX36Zu+++mxUrVlwxBSFJAcrLyxFFke7uHqKjY9i7dy8HDx5kwoQJDA4Osn37dtLT09m06QMefvgRxo8ff0Vku1Dk7f03EAqFiFKpoKenJzwI8Xq9GI0G4uPjycvLZeHChYwbl43L5UStVqPVajGbTej1BkwmU7iDHosNin6/n/T0DL785S+TkpLKunXrKCoqYty4ccydOw+VSkV/fz+pqanMmjWbrKyscEzBgoICZs2ahUKh4MiRI3g8nvB+w7i4OBITE8Md4mh2EJIUXDifMGECkyZNIiMjA4VCxGAwkJKSgs/npbe3B6PRGI600dPTy9atW5gzZw5paWloNFrUam3YG220CXXWoTh+w02pp//39L/P9vl8x0dDvvPd61xyD7/O6ecMPz6abUClUuN0OsJ7zPr6+oYi1TDkfbqAadOmsWzZMrRaLbfccgtf+tKX8Ho9jBuXzUMPPcSsWbPCe7+uHALR0TFkZGSyYMF8nnjiCXQ6HStWrOBLX/oSkiSRlZXJQw89RE5OHidOnABGZ9P5aCErsBsIpVLFnDlz2bdvH/v376eyspKioiImTZoc9vRqaGggEJBISUmht7eXqqoqmpqaaGlpIS4ujgkTxocVxGjjcDjYsGED9fX1xMTEYLVaiIqKGlrHaAgHDA6FjHI47DgcDhwOR3h/jtPpwGwOuglHRUUNBdxV0dbWyuDg4Ij9LJdD6Dr9/f04HA6MRiPR0dGARH//AIcPH+bAgQNERUXjcgXNmzabDZvNhlKpICcnh40b36Wjo4PBwQEqKytoamoMbzgdbUKd9rn+fdr557velZbvQuW+mOe9HLnT0tLw+/0899yfePHFF6mvr2P8+PGIYtAEa7FYqKqqYteuXXi93qH9VWqysrI5ebKKd955h+Lig6Mm04WiUAQDBSxfvpyFC28mOjo6bJJVqVSkpaVTXV3Dhg0bqKg4ccas/LPANbEPTObyCb20+fn5KJVKjh07Rnt7Ozk5OWRnZ7Nr1yf09vZQXV3NrFkzSUxMwul00drayrx584iPj6e7uwePx8PSpUvHZF+e0Whk6tSpNNTXo9PrufXWVZhMJoqK9lNfX88tt9xCIBCgsHACXq+X5ORkdDodRqOP0AbMwsKJFBQUEB8fR2VlJQUFBUybNpXi4mIGBgbOMEtdDn6/H5/PS25uDi6Xi0AggNFoIiMjneTkZAYG+lGplEyfPh273c6sWbPweDzk5xcwceJEbLZBRFHB7NlzaGtrZ9asWaMqn8zYEjLPRkZG8rWvPUlRURE+n49ly5aRmprKypUriY6OQq83cOedd+L3+1m4cCFGY3A7woQJhdx33300Njbw0EMPkZmZeUVjXoYcc0JriJIkMW9e0NKhUCjIy8vlgQceoLKykvvvv5/CwsLwc39WEKTP0nzwAhkYGAgGy+xoDr/wGoUatUL1mSrcsWSgtoLmLW+T9fnHUZusF5xOIzRzCIUTCqVDWL9+PQ6Hg89//vPhRX2/3z/CjTbk6TUWZpiQbKEQRSFTkCAI4VQOIQ+u0DrJ2Tykhn83fN/L8DQVoyH3cA/HUBmdXmaBQOCs9wp9P/zz8HI+r4xD5w9Ul9G8bQPZ934Vtdl62c8kc2mE6m94ipHhbRFOecKe7mYe+s1YzRDPJ3NIlpC8p0fbGC7faDpwSJKEx+/B7R8Z1kur1OCyO7FYLOEtEJ+GPAO7wQg1wOGeW36/n7lz5yJJUji6+nDPsxCn7yUZC9mG7yUJcbZ9LRfCcPlD1xhNk1eoszrXPeUI8jcGw9fYTj8W4vR2Evr+9DZyJQfgF7IOenq+us8a17UCuwYnlxeJdOr/L+NZFQrFUOQUidCu/POV3bVatteK3OeV87TO5Fp5rhuFS31/rkY9nu2eF3rs4hAu1FB0wVy3CiwYedmD1+u5nL79s4kALq8Hr1KNw+lEJSq53h5R5tMRAJfPi0+pxuFyoBTHPnCqjMylIAigVKqHIuiMrga7bhUYEE69cD3i9fmQRAVen4eA5/p8RplPx+vzExAVeD0eAkq5Dch8dgmaIi8uB9+FcN0qMEEIRmXXai8ubca1grKviwGPC7PRjMpkvdriyFxpJAmxu5UBjxOzyYLKZLnaEsnInJOxck65bhUYjF2hfSYYtvAqCsIFeyHKXAdIEhIgDAsCKzuMyNyIXNcKbDjnWoAcruBGI6DqWAVllZGRkZEZyQ2jwODMaOWhY+c6N8TwWGvDOdtepLNd43znDb+HjIyMjMyFcUPYHSRJYnBwkIMHD9LU1ERdXR0VFRXhzYUhPB4P27Zto7GxccSmvtPDDw3fuHg6fX19bN68GZvNNuL84X+73e7wb2X3ZxkZGZlL44aZgblcLl599RXuu+9+3G43XV2ddHZ24vV6MRgMZGdnc+LECTZs2MDtt9/O4OAAfX394TQXWq0WrVZLd3c3FosFr9eD3+/HZDLT09NDTs44QODIkSN8+OEmTCYjBoOBQCAYCXxgYIDBwUHi4+N55513WLJkCXq9nq6uLgoKCkZEBR8Lzjd7PJcp9dMYC3kv5N7nmhFfyG9Gi4sto8+arJ9W/+f6zdmuMZpc7D2vZple6P0vxPpytt+MJmPxTn1WLEY3jAJTq9Xo9Qb0ej0ajQa32817771LZmYmVVXVZGVlodfrMZvNNDU1sXfvXtRqFfHxCezYsZ2lS2+htLQUs9lMdHQUhw8fprBw4lDep2rKyoI5pzIyMlGpVFRWVtHd3Y3BoMdoNCJJUF5ezsSJE+nq6qKrq4uioiKMRgNOp5OlS5cCY/eiSZJEQAogDmXalYZ9F268wQOnzhVFBIbCMiGhGL7JWRBGeUfHSFlDhJJZhlKkD3/RQseHOzH4/f7weaeHxRlLWc/WAQzP3Hz6MwwPdQWEn2MsZR0eTiskZ0imT3uu02Udy3I9X/0Plykkf+i8c30eSweXUKil0+v19LBLw601w8M0Xcn6P1u5DpfnQtrpWL9TF8sNo8BCIYo6OjqQJAm1Wo3RaCQ5OYX6+nra29vJyMhApVIOzZYGWLx4CWaziZMnK5g2bSpVVVWoVComTCikvr6BSZMmUVJSgiTBwMAgfX195ObmIYoK1Go1LpcLtVqNzWZnYGAAkMLRqCVJore3l+nTp5OTkzNmzy1JEi6vm90VRZQ3nSQ1JplF42/CpDXQ3NPKscZy5uXOwqQNJlG0uexsK91NXWcj41NymZk9leKaI9R3NbF4/DyijJEUVR+iIDmHGFPUqDXk0ItRUlLCrl2f4PcH0Gg0LFu2jObmZvbv30dWVjbLly/HZDINZUMuZuvWrSQlJbFq1Sq6urr48MMPmThxInPmzKG0tBS/38+0adOA0XvpgpvkvWzevJmKigoAoqOjyc3NpaioCK/XS0xMNLfdtgar1YrX6+WTTz5h3769TJ48hXnz5rF3715qaqpZtWo10dHR4VxnsbGxZ12rvRxZ7XY7mzdvprS0lNzcXFasWIHRaKS6uprS0lKWLl2KwWAId1her5edO3fidDpZsmQJe/bsoaysjBUrVpCSksKuXbvIzc0lJSVlVOV0uVx88MEH1NbWAsEcYYsXL2b//n2UlZUze/ZsFixYEA6D1tDQwMaNG/H5fNx2222YzWbeffddtFotq1evpq+vj/LyMhYsWBjOjj1aSJJEV1cXb775Jj09PSxcuJDJkyeza9cuiouLycvLY/ny5ej1elpbW9mwYQN2ux1BEJg6dSper5cTJ8pZsWIlycnJfPLJJ4wfX0BiYtKo138gEGDPnj0cPHiQQCCATqdj2bJltLS04HA4WLx4MSqVCofDwebNmzl+/Bg5ObksW7aM48ePU1JSwtKlS8nKymLPnj2kpaWRmZn5mVBiN4wCMxiCEaHLy8uIiIgkJyeHpqYm9Ho9OTm5YRNiQkIieXm5REZG0NbWRkREBFlZWXR1deNwOBAEgc2bN5OcnIxKpSQ+Ph6z2YRCoUSn09HR0cH48QXk5eXhdDrRaNRYrVZ6e/UkJycTFRWFWq3CaDQyZ84cWltbwokBx4ry5kpe372em/Jms6nkY/RqHVMzJ/LCjteIt8ahHBanbU/lQT449DGzsqfx2q63cPs87DqxH6vewrslH5EcmUhNRz2T08cmsV0wBqOSxsY6ioqKMJlMbNiwgZkzZ7Ju3TokSeLOO++kpaWF//mf/2HChAm8++67uN1u2tra8Hg8vPbaawBs3LiRL3zhC2MiZ0hWpVLJgQMHwllsd+zYzqJFi9FqdeFRbkVFBX/4wx+YMWMGzz33p3Aiw6ioSN5++23i4+Opqqpk7ty5oypfaFBw6FAJL730IrfcsozXXnsNq9VKYWEhzzzzDOPHjx8xuvb7/ezevZv/+Z//JjY2lszMTNat+wdxcfG89tprFBYWUlJSwqRJk0ZV1hChLND79+9HqQzG3ly37h/MnDmL//3f/yUqKopJkybhcrn405/+yODgIAqFkt///vdMmTKZgwcPAAIWi4UjR46QmZlx1vxil0OonN555x0OHCgiNzeXZ555hieeeIINGzaQkZHBCy+8QHR0dDiBqEKhwOPxsHHjRjweNzU1tURHR7Nu3T+YMKGQgwcPMn369FGVM0RoZqdQKKirq6W0tBSADz54n7i4eObNm4tSqaSkpIS//vX/WL58Of/4x+t4vV727t1DXFwcr776KnPmzGH37l08+eRTYyLnpXBDKLCQiWnKlCnhF1ahUPC5z30OQRCYM2cOCoUi3IAUCgWzZs0emjqLFBZOoKenhylTpqDT6UhISCAvLw+1Wk1BwfhwRxGKRh66RkFBwYhIz4JwKmThcDNCaEQ5VkrMqNWjUqrosfciIaFT63i3+EMOVh4mIz6d7aV7WFq4AKVCiUUXTKrY5+hDrVQRbYrCoNbTY+tBo1ZTUnuElZOXEghISEggjY7coTKaMmUKubk5/PrXv2blyhVYrVY0Gg0rVqygq6uL5uZmAoEA3d3dKJUKvvSlL/Hmm29QX19HSkoq+/fvx2q1sHHjuxQWFhIRYcXv949q9uhQzqQVK1bQ0dHBsWPHWL16NYODg/T393PoUMmIQYnVauXxxx8nPz+fw4cPh5OEdnR0oNXqqK+vY+3au/B4PKM6+g5hMplRKpV0d3cBoNFoePnllykuPojNZiM1NZXly5cjCAIVFRW8+eabzJ07l/r6BjQaDREREbS2tpCYmMS2bVu5/fY7zmo+vVy0Wi2rVq2ipaWFo0ePcuedd1JfX09BwXgee+wx6upqaW5uZtKkSdjtdlpaWnnwwQfRarX8/ve/Q6834HQGrR6h2caECRNwu93hTAajGcw5KioKh8MZXhfPy8vjwQcf5KOPNiMIAnq9HoC4uDgeeughNm/eTGVlJatWrebVV1+hra2VpKRktm7dyu23347X6x31+g9da86cOUycOJFf/OLn3HbbbaxevRqr1crevXsJhXeyWCyo1Wo6OzsJBCRiYmKIjo6hsbGJ+Ph4PvzwQ1atWhXOtDCa0ekvlRtCgYWm0U6nE61WGx6RBWNznSKUWsTlcqFQKFCpVDidTlQqFbGxcaxatSpsfjzXqG74NURRPOMeHo8Hn8+HTqc768L5WDQIp8eFKIhY9GZUChUdg50cqj3Gw0vuw6DW8V7xZqZnTcKqt2B3O1Ar1Zh1ZgRBQKNU8cQtD1Lf1cQHh7YQYYzgzf0bMWqNPLLoPpIi4kdd3tLSMk6cOMH/9/99n8jISPbt28sPf/gDenp6+X//7/8NdZ4BgsFBBYIvoMDnP/955s2bx44dO2hqaqKkpJhPPtnJ3Xd/nltuuWXUXzhJkvj444/R6bTMnz+fhoYGnnrq68TFxfFf//Vf3HzzIiZMmEBCQgKSJPHrX/+awsJCli1bxsKFC6mqquLNN98kIiKS559/nqioKP7lX/4lfP5oyBrywA0lVlSplDQ2NlJSUsK//ut3cLlcrFu3junTp6PVannjjTc4caKc+vp6Wlqa2bx5M//8z9+gvr6et99eT0xMDH//+6vodDq+/vV/Jjs7exRK8tQARpIkNm/+EIvFzLx582hoaBiRZiZk4jw9zQfAzJkzmTp1KkePHmHLlq1ERFj55S9/yZQpU3n88cfR6UYvKk8gEKCnpweTyYTVGkFbWzsulwuNRkNcXBw6nTZsEVAqlfT19bFhwzusXLmCvLw8/uVfvkV1dTXr168nMjKSl156EZPJxDe/+TTp6elj0heUlJTQ0NDIo48+RmJiIkajMfwsXq93qJ0osFoj0Gq1SJLE17/+dWpra3j77XeIiYnmrbfeZMOGd3jyySfJzy8Ysz7rQrkh3OgBent7ee65P9Hc3Izb7cbtduPxeML/Qtl8PR4Pb7/9Njt37sDpdPLyyy9z9OhRPB5P2LwRGil5vV4cDkc45mLoWi6Xi/Xr17N16xZ8vuA5oUayb98+XnnlFdxuNw6HA5fLNZRd2BHOJTXaDDgG8Pl9xFvjEEURm8uOXqOnqrWWsqaTaFRaSmqP8fIn62jubkEpKkiIjMPn99PvGESn1nKsoYzkqERMOiMp0Ul4/F46B7pGTcbQc9tsNt588w1mz55NdnY2sbGxPPHEl4mPT+CLX/wiFouFZ555Brfbg8fjZt26dRw4cICkpCSMRiO9vb3U1FQzf/58/H4/WVlZlJeXn7FlYjRkbWpq4oMPPmD16tuwWq2Ul5ezbds2Dh06hCQFaG9v59e//jUHDx7kRz/6IWVlpcTGxtDQ0IDBYODAgSJycnIwmUykpaXi9Xppa2sbNTlDsvb09BAISCQnpyBJ4HQ6MRqNHDt2jOPHj2Mymdi1axd///vfueeee/jVr/6LW2+9lXHjcliyZAlms5ljx44RHR1DXFw8iYlJCIJAQ0PDqLfXuro6tm7dxtq1d2GxWEhJSaG8vJx169bR1taGWq3m2WefpaysjKioKDZt2sS7775LZGQkZrMZlUrF7t27ufXWWxkctFFYOJG6ujpcLteoyun3+2lubsJkMpKYmEh/fz8HDhzgL3/5CwqFkkAgQEdHO88++3v27t3L5s2bUSgU3HzzIpRKJSaTiaNHjxIfH09CQgJxcXGIooLW1tZRLdPQtQYGBnjrrTdZvHgRKSkpQ4OC4DmHDx/md7/7HbW1tQQCgaHvBTo7O9Hr9dTXN6DT6UhLSycqKhqdTk9tbe1nYgvQDTEDg2A+KJvNRmNjIwcOHGBwcBBBAI1Gi16vQ6VS09fXGz5Po9GE7dYnTpygoqICrVZLY2MjLpeTlStv5eTJk3R1dZGYmEh1dRXx8Qn4/X4MBj09Pd14vR62b99BY2MjOTnj6Orqorq6hsHBAbZv305jYwN+v5/c3FwaGhpISEhk0aJFqFSjm5hzckYhXYM9nGg6yaxx07hl4kLmjJvOlqOfEAgEeGTRfbj9Hjw+D9MyJ6JUKDnReJKVU5cyJaMQl9dNnDWWmVlTcHpcbDq0hSnpExiXkDVqMoZwuVxMmDCBW265BZVKhSRJOBwOFi5cONQpDZCQkEBWVhZf/erX2LZtG3PmzGHt2rUAuN1uvvjFL5KVlU1/fz/NzU2sWbNmVE2IEOwYbDYba9bcxty5cxFFkaVLl2K322hpaeUrX/kq2dnZdHYGnYby8vLJyMikvb2Dnp4e4uLiSE1NZf78BeHOJScnZ9QdekRRZMGCBfT393HkyBGWL1/OmjVrmDFjBh988AFarZYvfOEL9Pf3IwgCaWlpqFSqoZlELGlpaTidTiIjo1i9ejWBQIB169aRnp7GtGnTRn307XQ6uOOOO5g+ffpQh38zAwMDVFRU8PDDjzBt2lS6u7uJjY3l8ccf5+233yYQCHDfffdiNptpbm5i6dJbWLBgASaTkR07dnL77bePeqZrjUbDww8/whtvrKOurpavfOXLzJw5C4VC5Nix46xYsYJly5axY8cO9Ho9er2ef/qnLxEZGTn0nE6io6NZs2YNbrebN98cxGqNGLP1cJfLxbRp01mxYkX4vcrOHkcgIBERYSU+Pp6FCxcSCAQ4cuQIixcvZsWKFXi9XlQqFQ8++CBqtZrBwUFUKiVz5sy96uZDuEEyMkuSRH9/P7/97W+JiYlmcNBGVFQUVquVw4cPU1BQQF1dHVarFb1eh91uJy4ujlWrVvOHP/wvVVVVPPDAF2hvb6exsRGbzYbBYMButzN9+nSKivYPmQpU9Pb2sHDhzXR2dmK322hv72DChAkMDPTT19fHtGnT2bdvLxqNFqVSiUajobOzg4yMTHQ6HatXr0av15+3cVxMRmZJkvAF/PgDARSiiEJUABK+0HqdGFrED9q1/QE//oCEcujckFu9QlQgIeH1+xAFAaWoHDV3+uGbxn0+XzjrM5zKehxaxwi5g4dmwcPTovt8vrDHqc/nC68xjqaL8nAX7lCG6qFv8Hp9SFIAhUI5tCbqQxQV4WcILaYLghBemws+hwdBEC9sPVS68IzMIVl9Pl+43EJlF8rKHerQhj9L0EQXfI7hZQ7g9XrD64DnlfUiCN0nVNchQrIrlYpwWYbMhqFnUCqVZ6xBh6weSqUyXOajRcjcGWpjw+8/vJxDsobWjIZvrbgSZTpc1tPXgkPHgnIHUCoVYflFUQyfOxZyStLoZGS+YUyIITOdVhv0FDQajcycOROTyYTZbCY5OZnm5mZiYmKQJHC5gtEy3G4P8fHxbN78YdgF3+/3o9VqcblcNDc3M27cOGbOmInZbCImJpaIiIghhw1pSHn2kZaWTnt7O2VlZdjtQW/GxsYG1Go1Go2W1tZWEhMTR2RKHi0EAZSiAo1ShVJUDK0YCagUSlQKJaIQDAisEBUIBBWTRqkKKzZh6DuGfqdWqILKi9HL7hNSMKGXY7jCUSgUIzIqh5SbKIpoNJoRZRbqSEJ/q9XqUd9fE7rW8Jd86BtUKhVqtSbcYSqVqrCCVavVIzJeD38mtVozJp1X6FqhwdLwslOr1eHyOf1ZRFFEoTizzIOyqsdM1uF1HSIku0KhDMsSUgahZwjV+fCs4aH2MdrKKyRrqK0Ov8fp5RySdXi7DP3+SpTpcFlPL9fh8qlUp+QNyTL8d1dCzkvhhjEh6nQ67rvvPmJiYpg6dSoKhYLY2FgeffRLqNUaJEmivLx8yG0+Ozxqu+2229DpdPj9ftxud3hUZTKZ8Hq9DAwMMG7cOBQKBfNuuims3BITE4ZeSBXt7W2MGzeOjIwM7HYbkkR4309jYyOrVq3G4XCQnp4+6qauIKcyoYaUqs1mQ5IkjEbjiFFhV1cnarUas9l6xqRu+ILtWDXe4Zsqg443DrxeL0aj6TTHmeA+OkmCiMjIMxTpWMs6/JohWW22oLOEwWAc8b3f76ezowOLxYJuyDPtasgamo15vR7sdjtarQ6tVjviXKfTSV9fL7Gxced1Px9rWb1eL3a7DY1Ge4YDhtvtpru7k9jY+LO+M1ejrQbrXxHeUxfC7/fT2dmB2WwJeyZeDVlD9e/xeHA4zl3/vb09xMXFf2r9fxaUF9wgCizk0jphwgQEQSAuLg5gaOQRET5n6tSpIypGFEXy8vLCn09vaCFlEKponU4XPiczMyt8XkpKMgqFAqvVGm7sFouFrq4uFixYSFJSUngUPNqzhbPR19fHtm0fo9VqWbhwEVqtju7uLlQqFcUHD6DV6SgoGE9UVDQ22yBOpxOz2UJp6TESE5PCi/hjjcfjYeeObTgcTm5etJjIyCgCgQC9vT1IksTJkycYGBhg8uSpREVF4/F4sA0OYDJbOHmyAovFQlpa+pi6+4Y6he7uLrZt/ZiYmFjmzpuPRqPB4/HQ092NSq1mx46t5OUXkJqahtlsoa+vN+jRqlJTXl5KTm4eERGRYyLj6fKWlZVSWnqcyZOnkJ8f3M9nt9ux2wZxulx88skOFixYSHR0LGq1mu7uLvR6A729PfR0d5NfMP6Mjm+0ZZQkierqSkpKisnLy2fSpCkoFAocdjsDg/34fX62bPmIeTfNJy4uHq1WS09PDxqNdmgdspkJEyaOqZzD6ezsYMeOrcTExDFv3k2oVGq8Xi+9Pd2o1Rp2fbKDrOxxpKamYbVGhOtfqVRSXl5GXl7+Fal/gIqKco4dO8qUyVPJLxgPSNhsdux2Gw6Hg927drBw4WKioqPDyyJ6vZ7e3l56errJzy9Ao9F+JpTYDaHAQni9XlwuF2azeWh0Z0en0+F2uzGbzUOjKBsKhQKn00lERARut3uoAzcPrX3p0emCo6izhagZbvYKMdy8AcEXNCsri4yMjBF28SvVIJxOB06nk/i4eJRKFfV1tRw9doT0tHQEAVpbW2ior2P69JlU11QhCsHkoJWVJ9FotCQkJF4ROX0+L4M2G2azhUBAoqW5CafLyZEjh0lKSkapVNHd1cWHH77P+PGFdHZ0ICqCm4fb29vIzx9PSkrqFcmVNTDQj9vjwWK14nDY6e7uorGxga7OTlJSUpGQaGiop7T0GIWFk2lqbMDpchIREcnJinISk5KvmALr7+9HqVCg1xvoaG8nIAU4fKgEvV5PQkIiLpeLffv2YjAYMJsteDxu+vr6MJlM9Pf3kz0uZ8wVQyAQoK+vD1EQMJst9PR0EwgE5VQoFcTFxePxejl86BBarZao6GjsNhu9fb2YTCba29rIzc27YgpscHAAj8eDxWLFbrfjdHTR3NxIV1cnaWkZSEBNTTWlx48xafIUGhsbcLlcREZEcuJEGSkpaWNe/4IgEAgEGOgfQKlQBAMvtLfh8/k5dLgYo8FIXHw8TpeLffuD9W8wGPF6PPQP9KPX67HZBsnOzkGjGVNRL5gbZg0MoLGxkT/84Q/09PTw3nvvsWnTJpqamvjDH/6Xnp4eNm3axIYNGzh+/DivvfZ3XC4XJSUlvP766/T29vLBBx/Q2dl12e6jw9ccQrbzK6G8QnIrFArGjy+kqqqSlpZm9AYDDoeD6upqfD4/8fHxGE0mmpob8ft8JCen0tkVdKmNsFpHmCPGEp/PR05OLv39fZSWHqO6ugq/PxhTrqamGrfLRVxcPDExsXR3ddE3tNbY19uLXqcnOjpmjEyyZ6LRaMnLy+fY0SPU1tZQW1uDRqOlvb2dlpZmREEkIyMTURRpbKhHp9MRERFBb08PJpM5vFg91uUaCPiJjY0lJjaWAwf2U1tbQ1dnJwajkZqaavr6+zDo9aSmpjE4MEh9fS3JySlDW0cgMjJqVPdTfRoREREkJiZx4MB+6uvqaG5uwmAwUFdby0B/P1qthozMLBwOO42NDcQnJADBmbvFag1nYx/LMg3NFrVaHXl5BRw9epiammrq6mpQazR0dLTT1NSIAKSmpKJUKamtrUGr1WG1RtDb14vZbBl1L8lzyerz+YiOiSY2No6DB4uoq6ulq7sTk8lMdXUV/f39GAxG0tMzsNvtNDU1kJySit/nRyBY/2czg14tbggFFjLbDQ4O4vF4KCsLbpSNjY3F4XDg9fqorKykvLyM6OhonE4n6enpGI1GnE4n2dnZaLVaEhISiI+PHxWFc6WU1umERuBtba2kpWcQGRGJz+sjKTGJ6JgYFEoFkgSiIBIVFY1Wq6Wzs51JkyYTGRVFS2sLfr/visjqdDhobm4iPj6BnJxc5sy9iYiICGJiYomLi8MfCOAP+GFoLS8yMpLGhnoKxk8gKjqapsYGPB73mMoYqsOe7m76+noZl5NLbm4eM2fORqvVkJ6RgdFkCnr6BSREUUFiUhJ2hx2fz0dh4UTUGg2tra1nTc8z2vj9flpbmhEEgfy8AqZNn05WdjYajYbUtLSgx9mQZ6ROpyM7O4e62lqSEpNJT0tnoL+P/v6+MZUxNFNobWkhIEnk5xUwcdJkxo8vRKvVkZGZGXaW8XmDG4XT0tKpr68jJiaWzIws3G43fX29YyrncHp6uunp6SYrK5vc3Hymz5iFSqUmJTUdnV6PQqnE5/MjCgpSU9NwDu37nFg4CY1GS1tb6xWRMxDw09LcjCCK5OUXMGXqdMaNy0GjDg4GBIJOM36/D5VKRWZmNjU1VcTFx5Oams5Af/8VLdfzccO40dvtdoqKimhubsbhsCOKIjk5Ofj9gaFjDkRRJD096C2YlZVJZmYWmzZtYvz48RgMBrq6OpkzZ+6YeDVdLBfjRj+ckDK32+2oVCq0Wm3YdBpyp0YKholSKlVIUgCfz4deb8Dr9QwFAz2/m/9oEAgEcDgcAOGtBcG6DJp5BQT8gQAgDdWJiMfjRq834Pf78Hp94d+Npbwhz1SH3Y5aq0GjDtpXfD4fdrsNrVaLzxd0A/d5faiH1scEQUCn0+F0OlEoFBcecPYi3OhPlxMIb7Y3GAxhs7bL5cLj9aBRa/B6PWE3dLVag8vlRKPRhk3rOp0OpVLB6Pmgnl3WoJxO9HpDeCYdDELgQqvVDQUXCLqoazSaodijQQ9Ah8OBTqtHoRz7dzVU/3a7HY1Gg0YzvP6Dx/x+H4IgEgj4h9ZHvZde/5chJwTr3+1yoT+t/r1eb3jtNliu0lBQ8uH170Cn0192HzhabvQ3zBpYe3s7/f395OXlYbPZqKqqoqure2iTaS4DA4PU1tbi8Xjo7u5Gp9PR2tpKQkICx44dJSoqihkzZo56YNArTch8aTKZwsdEUbwgE4ZGc2XWE0IIgoDBYBjxGcBoNJ3rJ+HOQ6FQoB5SJGPdgYXcv03DyjDkfmyxWIfkYsR/h7v+h0wyV0LOUCi0UIiz0D1D+e6CMqoZrpyGyxoKPzSWyisk1+lySpI0QkGcHqbNZBoupyn8u7EmVP/ms9a/5bSzJYLbLU7JfqVMcueq/6AJ9FT9n16uI+v/ypXrhXDDKLDk5GTi4uLCM45Q4F5BEMIvxMyZMxEEgfnz54dHdaHIHCqV6op1NGPNtSL/2eT8rMp+rch6LplGHv9syH26rJ/F8gxx4fV/dZ/hWmmnF8oNocBOH82FAvWezqnR5UgsFsuY79OQkZGRkbk4bggFBpeneGSlJSMjI/PZ44bwQpSRue6QB1UyMtfvDOxUoM0r4/J9pfH6vAQEMZgE0e262qZ1mSuNBF6fj4AY9LyU3M6rLZGMzDlRKJThWJajyXWrwAA8HhdO5/X5Yjs9HjwaHXanA4WsvW5IHD4fXrUem9OBUm4DMp9hdDo9ev3oq5vrWoHp9UZ0OsP5T7wGsQ32MOiyYTVbUJmsV1scmSuOhLK3HZvLRoTZisp0uru2jMxnh7HyI7huFViwwK5uuuuxRQj+TwimQpEH4DcQwb3mhDKxBff2yQ1A5rPO6LfR61aBBRlZYKcHHTmXcht+3sUowAv93aVe/9ORO7Abh7MFz5HrX+bG4zpXYKc4W8SsT4uiFUqLcqmRti70d8NTtMjIyMjIXDgX5Ub/i1/8ghkzZmAymYiNjeWOO+6goqJixDkul4uvfe1rREVFYTQaueuuu2hvbx9xTkNDA6tWrUKv1xMbG8u3v/3tcGrwsSCkTFwuF/X19TQ3N4VTj59+DgTzZR0+fAi3+9yBYEOBV0PRqCGYU6mkpASXyzXi+Nl+W1paGi6XazAcpYyMjMxV56JmYDt27OBrX/saM2bMwOfz8W//9m8sW7aMsrKycMy6b37zm7z33nusW7cOi8XCk08+ydq1a9m9ezcQjIa9atUq4uPj2bNnD62trTz44IOoVCp+/vOfj/4TDuF0OvnHP/6BRqOhr6+X+fMXoFarCQT86HTBZG3p6ekEAgGKi4vZtGkTDz30IBaLBZ/Pj8ViYWBgAIfDgdVqZdu2bcyZMwetVkt/fz+ZmZkMDg7y1ltvEhMTg8vlGoqq7R8KihuMmN7Z2YnFYmbbtq2MHz8+nFxzLLkQBXn6LPBSfjNanO/ep2dDvhDGQtYLLaMLeZ7QOVdLztPluNDzx4rRKtvQeTLXJxelwDZt2jTi8wsvvEBsbCzFxcUsWLCA/v5+nn/+eV599VUWL14MwF//+lfy8/PZt28fs2fPZvPmzZSVlfHxxx8TFxfH5MmT+elPf8p3vvMdfvzjH58RSHK0aGhooKysjKeeeoqOjg5cLhd/+tOfyM7OJiYmhpaWlnB6lWDm5AB79+7F6XQBkJWVRV9fL9XV1YwfP4GDBw+QmppKSUkJbreLWbNmMX78BJRKFfv376e5uZnBwQHUag0KhYKEhIShpHdenE4Her2Bqz3xkpDCjgDBEKNDx89mbh12LsPOG+3O4Vwd0tk6+E8zC59NIY+mrKH7nH7dT1NEZzv3SuVWu5B118+KJWB42cLZ6/xc5Xa2epbN9Ncvl7UG1t/fD0BkZDCTaHFxMV6vl6VLl4bPycvLIzU1lb179zJ79mz27t1LYWHhiJnH8uXL+cpXvkJpaSlTpkw54z7BFAqnzHkDAwMXJackSahUKtxuNwMDA7z77rsUFhZitVqYP38+5eXleDxumpub6e3tYdWq1ahUahQKJR6PB61Wy+DgIDabDaczmHbAbLbg9XppaWlhxYoVZGRkAMEZZk1NDSqVkptvXoTZbOH//u95UlNT6OrqJi0tlWnTpnLkyBH8fv+Yv1ySJNFt66W86SQenweNUk1h2njcPg+H646hUWmYml6IUWsIuwYEJIn6rkbsLge5iVk0drdS01FPYWo+0aZIajrqiTJGEGmwjlB8oyErgM1m4+jRo2RkZBAXF0d9fT0lJSXExsYyY8YMtNpT6cx9Ph9lZWWoVEqys8cNmWbbmD17NjqdnhMnTpCenj6qCQNDcnZ3d7N//35AYubMWRiNRoqLi2lra2PSpElkZWUBwTZx8uRJjh8/TkpKCpMnT6axsZETJ04wdepU4uLiqKysJDIyMvxejFabCJmyGxsbKSkpwWQyMWvWLLq6ujhxohyv14fVamXmzJmoVCoqKiqoqakBglHSc3NzKS8vR6vVMn36dJxOJw0NDeTn56NSnT190eXKC9DT08P+/fsRRZFZs2ahUqk4cKCIzs4uCgoKyM/PRxRFXC4XZWVlREZGotVqOXjwINnZ2YwbN47m5mZ8Pl/43ZSV2PXHJSuwQCDAN77xDebNm8eECRMAaGtrQ61WY7VaR5wbFxdHW1tb+JzTzWahz6FzTucXv/gF//7v/36poiIIAikpySxbdgv79u0lPj6OlJQUTp48ic/nxW63YzZbUKmUxMTEcPDgASIiIsjKyqKrqzO8Pme320lISEClUhIZGYnP5yM1NYWyslKysrLw+/2YTCamTZtGUdF+mpqaWLasgOXLl5OTk0NSUjJbt27Bao1Ao9EQCPgJBAJjnqLleOMJXtj2dwpT8zDpTCREJvD33W/Sb+/H4XZS1VrDF+bfjVqpQgJqO+r5rw3PolNp+PrqJ/jbJ+tQK9VUtFQxL3cG7x/ewsML7xlVGUMdl9vt5tVXX+X555/nu9/9LrNmzeKnP/0parWKjo5O7r//fu68804UCgWSJHHgwAF+9KMfMW3aVL7whS/y3HPPodVqaGpqIikpmb179/KNb3xjVGWFYE6lF154gRMnTuBwOCguLmbSpMm8+eabRERY2bhxIz//+c9JSEigvr6e3/zm11gsFl5++WUefvhhdu36BI1Gy+HDh1m6dClvv72eJ598atTlhOCa7jPPPIPL5aKlpYWGhgbsdhtbt26loKCA5OSgUlUoFFRXV7N9+3ba29tpb2/jttvWUFFRgc/nxefzcvToMcxmM/n5eWMiKwTXqp977jmOHz+Oz+fj+PHjxMXFsWHDBrKyMlm3bh3/8R//QXJyMn/5y194/fXXuffee7HZbPT39/Hxxx/x8MMP8/rrr7N27dqwApO5/rhkBfa1r32N48ePs2vXrtGU56x873vf4+mnnw5/HhgYICUl5YJ/LwjB/DvLl6/A6XSiVCpRqVRMmFCIQiEyefIUJCkAQ/uq/H4/CoUCURSZM2dOOIdWIOAP7r8ZMl+IosiiRYvCqVckSeLrX/86CoWCm26ahyCIqNVqli9fjiAIZGRkMHXqVERRHPq9cEWSYwakQDCJpdtJQkQ8Xr+X9r5OHlv6IJ0DnWw59glurxuVUkW3rZe3it5jXEIm7X0dCAiolSrcXhdKUcH7hz5mbs4MrPrgxtnRlNzv97N161ZOnDhBQUEBfr+fxsZG3G433//+99m8eTNHjx5l9erViKJITU0N69atY8qUKYiiIlxPbrebxsYmSktLueeee9HpdKMoZRCFQsGaNWvIy8tj586dBAIBJk2ahMlkoqysjP3794dn17GxsXzpS49SV1dLQ0NDuD06nU4EQeCNN95gyZLFREZGjsmMXK/X8+CDD1JTU817772H1+vF5wu2ZYfDSWJiYjh10MqVK1mwYAG//e1vKSgoIDMzk9LSUgKBAPv27WNgYJDbbrsNQRi7MKqSJJGQkMDKlSvZt28vJ06cICYmhoiICDIzM2loaCQQCCAIAjfddBMNDQ1IUgCNRo3T6SIQCLB+/dtkZGSSnT1ONiFex1ySAnvyySd599132blzJ8nJyeHj8fHxeDwe+vr6RszC2tvbiY+PD59TVFQ04nohb7zQOaczPInd5aBQKDAYDOHGfK6Zz/BUK8PPkSQFfr8/nMDudEK5xYLXP3UNURTD34eSxl1JEiPiWFw4n4SION7c9y56bbAMNEoVaqUGAQgg4fP7eK94M4drjhEfEUtdez37Kov53Ow1NPe0cqS+FFGhYE/FQUpqjnHvvDtIjIi/7M4hNPtqamrij3/8I2aziYaGRt59910WLVqEKIrhdDh+vz+cqfkvf/kzJ09WoNXq6OvrY+rUKTzxxBPU19ezY8cOIiOjeO2119ixYwePPvookZGRo9aRiaJIZGQkDQ31NDY2kJaWjslkwul0Ulpaisfjwev14Pf70Wq16HQ6jhw5is1mQ6fT8ZWvfIWqqir27duHQqFg27Zt7Nu3j0cffYzU1NRR7XRVKhURERGcOFFBT08vPp+PgoICzGYTKpWKl19+mcLCQlJTUxFFkYqKCsrKyvjBD35AenoaUVFR1NXVsXv3LuLjE/jv//5v5s2bx9q1a1Gr1aOuHHQ6Hffeey979uxh167dPProl+jr66ezs5OSkkMIAuH2MH78eIxGIxqNlvvuW0tpaSnl5WVUVVXR2NjIT3/6U+677z6mTZuGKIqyIrvOuKhhlCRJPPnkk6xfv56tW7eeMTWfNm0aKpWKLVu2hI9VVFTQ0NDAnDlzAJgzZw7Hjh2jo6MjfM5HH32E2WymoKDgcp7lvAQCgfA/n88XXh8ImQj9fn/4uN/vD69R+f3+oVGrj927d1NWVhY+NxAIhK8Ruk4ohf2F/hsrQs/X1t9Jv2MArUqLQhTRqTQICByqO8aRuuNolBrKmyt579DHTMko5IlbHiQ3MRurwUJGTArJkQkEAn7cXjdJEfGolSqcXicnW4NrJaO19G+xWPjmN7/J2rVriYuLJSMjg9TUVBwOB3v37uXIkSNYLBZ27drF1q1buf322/na177GuHHjiI+PJzMzi6ysLDo6OoiJiUGjURMbG0tDQwONjQ2jJGUQh8PBK6+8gsViZcGChTQ2NrBx40ZOnjzJrbfeisvl4vjx4/ztb39j165PWL9+PTffvJDExCTq6uqIj4/H5/PhcDhITk5GrVbj9Xo5fPjwqDtTtLe38/LLLzNhwgQmTZpIdXU1VVVVOBzOoSzbElVVVbz66qu0tLSwbt065s6dQ3Z2NlqtjnHjgmuLc+fOpb29nfz8PPbs2YPNZhtVOUOEZuL/+Z//yZQpU9Dr9Rw5coSsrCzuuusu3G4PR44c4aWXXqKlpWWovCSioqKIiorixIkKZsyYSWdnJ0lJSXzyySdjuk3nbITevXP9+yxxrch5Ni5qBva1r32NV199lXfeeQeTyRRes7JYLOh0OiwWC1/60pd4+umniYyMxGw289RTTzFnzhxmz54NwLJlyygoKOCLX/wiv/zlL2lra+P73/8+X/va10ZllnU2JElicHCQ999/H0EQSEpKorW1hbS0dBwOB62trUyaNJGmpia8Xh/5+fkcPXqUQCDAuHHjKCkpwefzkZubS1dXJwqFgvXr38Lt9qDX6xg/fgLHjx+jr6+fW265JWze/CyM9gRBIC8xm5Lqo6wveo85OTOYNW4aGrWW9w5+iFqp4p55d9LvGKSzv4ulhQvQJWuIscSgUqoYn5KHL+Cjrb+LO2beilap4e+djWhUGnITs0dNRkmSsFqtLFiwAKfTSXd3D/n5+UydOpWWlhY2bNhAfHw8d955J0eOHMbpdHHLLbegVCrR6fQ0NjaSn59Pb28vbrebBx54gPb2Nl588UXS0tJIS0sfFVlD6HQ6Jk6cyN///io+n48vfOGLZGRk8Ne//pXi4oPccstSUlPTOHz4MFOmTCEiwsqLL75ESkoKK1aswOfz0dTUxP3334/RaORPf/ojgiAyZcqUUW83ofXcN95Yh8Fg4JFHHkGn0/P883/hwIEi7r33XsxmMwcOHKClpQWr1crtt9+BShXc/lFXV0tOzjhWrVqNWq3ho48+YunSpZhMplGVM4TH46G8vJzY2FiOHj1Kd3c3y5cvY/36t/nb3/7G3LlzKSgoYN26f+B2u8nLyw2vSVdVVXHnnXdSWFhIU1Mj9fX1PPTQQ2dNYDvWhPaKnk7IIvNZ4NOU1bWQxFeQLkLNnutB/vrXv/Lwww8DwQXYf/mXf+Hvf/87breb5cuX87//+78jzIP19fV85StfYfv27RgMBh566CH+8z//E6XywvTpwMAAFouFxo7msHeZRqFGrTi7V5QkSfT19fGTn/w7M2bMpKKiguTkZCRJIj4+jkOHDhMVFUVzcxOZmVn4fEHPrI6OdnQ6PZWVJ8nPL6CzsxO9Xk98fDwlJcWMHz+BI0eOMG5cNoODgzgcTh5//HFiY2M/tbxGg4HaCpq3vE3W5x9HbbKeMz+URLBxev0+/H4/SoUSlUKJXwrg9XkQEFAp1UiSREDyo1IEX/SAFMAfCKBSBOvE6/eiFJUggNsb/J1apQrHYxwNhjdFr9eLKIooFAp8Ph8ejwdRFNFoNOHRdKhTCpkVQx2u1+sN/+1yuVAoFOHB0WjIGpLT7/cH09lIUth07PF4wrIoFIqwLD6fD6/XO0IWr9cbbvNutxtBAI1Ge/6Z+VCHM1BdRvO2DWTf+1XUZuunyhoqw5CZGwjLqlarEUUxLI/P5xthGhxevj6fD7fbHV4zG60yPV1ej8cTrmdRFFGpVCPKVqlUhss2ZNYPHRNFEVEMphoKmXCvlPkwJH9DQwMff/wxXq93xPdpaWksXrx4TEyvF0vIcrR3717KykoZvqIdWv8fP378mFiKJEnC4/fg9o8sH61Sg8vuxGKx0N/ff17v4YuagV2IrtNqtTz77LM8++yz5zwnLS2N999//2JufdkEXenVJCUlcfLkSUwmIwkJiWzdupVAIIDf72fSpMkcOnQIi8UMSCiVKgwGA6IootVqwi+u2+1GkkCn04Y3QtfW1oVdiz8LjNj3g4BaoQLFsLU9QUShGrYeJwgoOLVWJyIiKk6NFFXDfqtVjZwpX25sx7O1q+HlqFQqRzi7nF7GCoUivCYpCEJ4L6EgCOj1+jGTVaFQnOEgotVqR6xfhZRFaM1mOMM/n36dC43beSFyQrAMTx8gnlqvPfUZRq77htZ7Q8fOdp3RlhXOXl6nl1FI3uEzmuFt43SLzuXKeTFoNBri4uKGlPCp+0ZERHymZmCCIGCxmM/wPxBF8Yx357PIDRMLUaVSccstt5CUlMT999/H8eOlxMbGsmTJkvBIDiTS0tJIT0/nxIkTGAwGoqKiiI6OJioqkri4eBQKBVqtlri4WKKiojEYjFRVVRITE0NZWRkZGRnMmzfvqo+uIDhyDindkAIYvknUZrOhVqvP+6JLkhQezep02rAHWuh4IBAASUKlVqNUXlrSupDDQ3gGwqnX3u/3Y7fbMBiM591yEAgEzph1hY673S5Co0ytVgNc3Kg8ZGoJXf9sm+5dLhc+nw+j0Xje651tNhM87sXj8SIIIIqKSzatX1D9q1RoPsWxKPTMwfr3odXqwh1w8LibgD+ARFDpXKgV5XSGz5ZCI/7hM92Lr39xaH3v1PFg/QMEZ6FjMSsLXS82NpYVK1ac9q005Ck79p7HF0JocDJhQiHjx0844/tQ+XwWZD0XN4QCEwQBo9HI4sWLwy9GZmYWgiCQk5NzRkQEURRHmAHT09PPuGboN4FAgOTkZNra2sKOKBdT6WMRQihoGvBSUlJMX18vFouVadNmoFQqwx58ggCffLKdmJhYCgrGo9cbcLlc+P0+VCoVjY0NREfHYrFYaG5uorKyYmhTaCapqWnhjvrAgSJ6e3vQ63QkJ6cyecrUC+4Yhpu39u/bi8PpIDt7HJkZWUiCgNfrxeV04va42bx5EzfdtIC4uDhUKjUOhwOFQoHb7aazs4PU1FSUShWlx4/R09uDz+dl+oxZaNRqAgGJzs4OysuO09fXh8VqpbBwEikpqRdVppIkUV9fR0VFOQpRwfQZM7FaI4BgqDK/309tbTU11dXcvGgxRqMp2KE6nWi0Wvr7+/B6PcTHJ+ByuigpOYjP50OlVjNt2gy8Xi+CIHD8+FHa2lrx+XxEREQwZ8489HrDRbWpUP339/dhsViZOnX6sPq3I4oiO3duIyoyigmFEzEYjLjdwTpVKlU0NtQTExuLxWKlpaWZysoKvF4faWnppKWl43a78ft9HByqf61WR3JKClOnTr+oMgUYGOinaP8+/IEA47LHkZmZhUTQxOpyOXG7PWz5+EPmzL1pWP3bw4EGOjraSE1NR6FQDK1F9+Dz+Zg2bQYajZZAIEBXZwfHS48x2N+PxRrBhMJCUlPTL1jWi+VcnsqfNUJbTz7t+88yN4QCCyEIAm63G6/Xi06nC48q1WoNKpUKlyu4h0Sv14dt6CqVasSIM9jJ+1GpVKhUKux2O+PGjSM3N3dEY7jQpUW73Y7f7w/bekerwfT19VNWVsrChYvYsWMrSUnJmExmuro6qKqqJDo6BiSJ6upKKk9WMHv2XGprqnG53VgjIjh0qJibb16CRqNh3749jB8/gfj4BBwOB3v27MLlcqFWqZkwoZC9e3aRkJBIckrKJZmPjh8/xqBtkKSkZIqLDxIVFQ1Aaelx7LZBUlLTcLtdFBXtQ6vRkpGZRWtLMw6nA4vZwsnKCu644y4kSeLI0cOsWHErgUCAnu5uqior8Pq8pKamk5uXz+5dOyksnHTGZvsLwe/3U1x8gMyMLBoa6qg4UU5+wQQ8HjclxQfQ6Q0YDAY6Otr54P13ycnNx263YbfbUSqVdHa0YzJbWLxkKWVlxxkYHGTBgoUMDAxQXl5KZ0cHHo+b8RMmEggE6O7uoqBgAgrFxb+mvb29lJeVcvOiJWzftoXExCRMJjOdncH6j4uLw+/zU11TRU1NNbPnzKOmpgq3y01EZCQlJQdZtGgJOp2OPbt3kZ9fQFJyMna7jT17duFxu1FrNIyfMIF9e/eQkpJCUvKF780MIUkSJ06U43A6SE/PoKTkIDGxsQQCAcrKjmOz2UlLS8fr9XLgwD60Gh2paWm0trTg9ngwmUycrCjnzrWfR5ICHD92hJUrV+MP+Onr66OysgK3y016RiZ5ufns3vMJhRMnYjaPbQLQz3rHP5xrSdbTuSEU2PDR8/HjpbjdbpKSEqmrq8PvDy5iZ2dnU1NTg9vtprCwkIyMDN544w0WLVpEWloaEJwpvP/+++GF4mnTplJefoJZs2aFTR+hCBGhGVpo1BtaGA+53mu1WrxeL7t27cLv97NixYpRtY0Ho3z4gwGL/QE62tuprKwgOjqGpqYmRCE4S0pNSaO5uYmqqkr8fj+xcXHU1FRjtVqJjY1FkiTsNhtKhRK9Xk9XVyetra1Mnz6DA0X7mTV7DgqlEp3eQExM7CWZHJxOB6IgoFKp8Hq9VFdX4fV68Xo91NXVERUdg1qlJi01jZMnKzh+7Ai5efnU1tbgcDiIi4vHYrHS2RlUAEHzkILy8lIEUcRqiaCrs5P4uAQQRCwWCyaT+RJe3CEHEbUaUVQwaLNx6FAx0dHRDA4O0t3Tw7jsHKKjY4iIjKS1tYX+vl5mzZrDnj270Gi1pKamolVrcDgcQNBpQ6Nxc/JkBYWFkzh27PDQmqsWpUJJbGzcJW12DzleBANWB+js7KDyZAVR0dE0NTUGZwcCJCYm09PTTXV1FR6Ph9i4OKqrq7BarcTExAECDqcdpSro7dnV1UnbUP0XHdjPtGnTEUQRnV5PTEzsRZZnEK/Xi0JUoFIGw71VVVXicbvxen3U1dUQExODWqMhMyObkydPUFp6jPy8Auob6nE67MTFxRMREUFrawtujxu1Jlg/FSdOoFQoMUQa6OnuIr9gPKIgYrVGXGL9y3zWuCEUGATj623e/BE333wzJpMJj8fDkSNHyc7O5uDBg5SXl3PvvffS39/PwYMHh0bAwRc7JSUFURTx+/0MDAwwefJkWltb2bTpQ3Q6LSdPnqS1tZWBgX5MJjM2W9AjUavVkpiYgMvlHgoSbKGpqRm/38fEiZPo7e3l5MkK5s4d/TUzs9lCcnIK5WWlJCUlk5efj0KhpKmpkdSUVLxeL26PG81QnEmj0UhLawuBjgCZmVlUVZ6ktbWFgoIJTJw4mfITZTQ3N6HV6dBqtdTWVJM2ZFq12+3Y7bZL2nwrCAITCiey65MdVFdVkZubR2HhJLxeD8eOHSUhIZG+3l48Xg8erxef309iUjLNTU1oNBpSUlI5fOQQPT3dREVFkRCfSEnJQbxeHxaLhYb6Onw+Pzk5uTicTpxOB06n85JG4AqFkry8fJqbG/H7/eTnFxATE4Pdbqerq4uB/j4GBvpxe9xD6y0SRpOJkycrSEhIRKFQ0NLcTHbWOPLyC9i+fRv79+3F7XYRHRVNdXUlZrMFi8VCXW0NNtsgviHPxYvFao0gITGJo0cPEx8fT25uXrD+GxtITU3D6wnWu07nxe12odcnMDjQT1dXF9nZ46g8WUFLSxMREREUFk7iZEUF7e1taNSaYP3X1pCamoYoijgcDgZtl17/WVnZ7N+/l9q6GgrGT2DChIl4vV6OHz9KYmIS/f19uN0unC4nbo+HlJRU6urrUCqVJCWncOzoEXp6uomJiSE+PoGDB4vw+XxYzBbaO9ow6A3k5uXhcrlwOB3Y7XZMptGLjSlz9bgoN/rPCpfiRt/c3MQbb7zBF7/4IBs2bCAqKory8nImTpyIWq3mwIEDPProoxw+fJi6urpwmhSdTssXvvBFlEolNpuNl19+mTVr1rB//35aW1sxmYx4vT7i4uLo6+sbcnTQ0dHRgcFgIBAIYDAY8Pl8DA4OYDAYsNsdGAwG4uJiKS8vZ+3au0hLS7uol//T3OhDVepyOnE4Hej1erTaoAeXx+NhcHAAjUYbdj8OLs5rww4ZJpNpaJ1MxGg04vf7GRwYCD6L0YjfH3Q+MBiMKJVK+vv7UavVYceFi3WMABgcHMTr9WA2W1AqlUiShMNhx+1yo9PrcLlc4RmaTqfH6XCgUqvQanUMDATLVaPR4HI5sdlsKJVKDAYjdrst/Bw+nxebzYbJZL4kN+aQ27HNNogoihiNpqEQYwH6+/sRBMIyhtYBFQoFTqcTg8GIIATXykwmM6IoYrPZcLlc6HRaVCo1NpsNrVaLXq8PKq+hND5nrCmex40+VKZOpxOHw45ebwh78IXqX6vV4vP5EUUBn88/tD3BS8AfwGgy4XDYEQQRk8mE3+9jYKj+g+U4sv4HBoL1bzBcWv0HHUoG8Xp9mM3mcP3b7fYh5ao/o/4dDjtqlRqtTsvgwCD6EfVvR6FQYDQasNvtCIKAyWTG6/Vgs9kwGk1DzkIXVf0yo8hVcaO/lrFYrFgslnAYK4UiGBZq2rRp6HQ6mpoa2bLlY9xuDxqNmnHjxuH1etmzZw/vvfce06ZNY2BggP7+fiorK4Ojv6Qkenp6sNlseL0edDo9CoUCk8mEWq2itbWV2Ni4IYWhGZoVJXHkyFHcbhft7e24XK7zSH7xhBxStDod2mGuxyEX89Aa0+lotbpw5zN8hqJUKomIjGQoECTACKeCUDaCS5lFhmQ1GU0jAisKgoDBYAx3ijrdSJfe4SG5QvcPPUPIkxEIO1kAQ56J2suSValUjrhmaN0zIiLinL/T6fTD3OpPyW0ymTAajWe43Ae/O/XiXsqsRpIkdEOz5eG//7T6H87w+lcolEREnIrTqNEQrheAiIhT5X8psgJnzIhCjleh0G+fVv8Rp9V/aLAGYLWe8hQNmmtD9X9RYsp8RrlhFFgwO/TnGBwcZObMmSgUCiZPnozVakGlUnPHHXeyfv1bpKamheOr+f1+UlJSaG9vx2KxYLVa+eIXv4hGo8ZoNIU3p4Y2qAqCgEqlHFrLEnA6neh0OhwOR/iFU6lUFBRMGNow6g1HoBhtztWRfFoHc97OZ9j3o2nyFARhVKMCj+Xaxtmufb77XVaZXyJjEUVhrGW9kveUuT64IRRYeJRvMo0If2OxnBplms1mPve5u/F4PFit1vCLYzQaSUpKCq9DXEz4nJBJ7fQ9QWeLji6/qDIyMjIXxw2hwOD8CkKSJAyGoBv06ecPj/IgIyMjI/PZ4IZRYOfjUkxuMjIyMjJXj+tWgYW8m84VEfpaJyAFw/f4AwF8fv/VFkfmCiMAAUlCEgT8Af8VTxciI3OhCIAwRmGprlsFBmC3D+Jw2K+2GGOC027HpTPR29eLwu2+2uLIXAWcTqfcBmSuCfR6I0bj6Kffua4VmNFowmAYm5xFV5tBez9OxwBREZGoTBZG1Y1P5rOPJDHY343D3j/UBqxXWyIZmXMyVksx160CC3oeXr/7PQRBBCGU1fmzk55B5goiCENt4MrkupKRuRzGoo1etwoMbhwHDAGuX00tcyaSxOnhc26Uti4jMxx56C4jIyMjc00iKzAZGRkZmWuS69qEKHOKETGbheH5jocODXMCGX7u8ESfp/9WGCPHkU+7/3BT2cXEoR4LE9vZUtSfK239p5bpWc4fbS60TM937nCuhKyXwnCZx9K0er66/rRyPf13n1Yfo8HZrn++Y+fjs2C2vqEU2Lka3KeddyERPC7k3Eu95oX+5uKuJ53xp4R01s7qrL8NHRqD9nu++5+ePftqcbb7n0um85fpyO9Gu2O40DK9kHOvBYbLPBblefo9Pu3Y5X43GlxInY5VOY01N4wCC21s9vv9iKKIKIrnbDh+vx+v1zsiOvi5CAQCeDye854bSmSpVqsvuMGe7TeXkgJEkiS6bD209XUQa44m2hRF52AXA04bkgQRBgsx5kiQghukW/s66LH1khyViEVnpmuwh157H2nRyaiVajoHuzHrjOhU2lFr9KHnCwQCtLS00N7eTkpKCjExMfT391NXV0tkZNSIuJR2u53q6mpUKhWZmZkEAgGqqqqIjo4mPj6ewcEBnE4XsbGxl1R255PV5XJRU1ONy+UmKysLk8lEa2srzc3NxMXFhfPISZJEZ2cnDQ0NxMXFkZiYSG9vLy0tLWRkZGA0Guno6ECr1WKxWEa1MwnXf1cXjY0NxMTEkpSUNJQ4sgqtVkt6ejoqlQoItv36+nr6+vrIzMzEbDbT3NzMwMAAWVlZqFQq2tvbiYiIQKfTjYmy9fl8NDY2DqX0gZiYWAYHB3E6neEo9UlJSfj9flpbW4mMjMTtdtPc3ExmZiYGg4HOzs4xKc+QjBBM61RdXY3FYiE1NRWlUonf76elpQW9Xk9UVNSI37lcLlpbW4mPj0cQBCorK4M57BISGBwcHErOGgeMbluVJIn29nYaGxuJjIwkLS0NpVKJy+Wira2NhIQENBrNUAojB42Njfh8PpRKJcnJyfT399Pb23vW+h9NWS+FG0KBhSJynDhxghMnTuD3+7n55ptHpOEIZUyWJIm6ulreffc9HnzwQQwGA6IohnMUeTweVCpV+EVrbm7mvffe44EHHggHAXa73ahUKkRRxOPxIIoixcXFnDxZwQMPfAFBEAgEAgQCgfDudKVSGT7X5XIxMDBAU1MT5eXl3H333eHO4mJexpCarO6o53fv/xlRFPH6vDy69Au8XfQBvYO9WPQmZuZMZ/mkRagUSg7XH+e5zS+hUqgw6Y18YcHnWV/0Hk63kykZheQmZvPBka08OP9udBbtp97/Ujh8+DC/+tWvwh3q008/zSuv/I2GhkZ8Pi/f+c53mDFjJm63m2ef/T1HjhzF6XRyxx23EwhI7NmzB7PZzFe/+lXefPMNxo+fwIoVK0b9JfP7/fztb3/j448/Rq/XERsbx9q1a/nd736HyWSip6eHb33rW0ybNo3a2lp++tOfAmCzDfL440+wc+cO+vr6yM4exy233MIrr/yNhx9+eESA6csl1NHW19fz05/+FJ/Pi8Ph4Otf/2cOHjzInj178Hq9PPLII6xZswaAHTt28Lvf/Ra93kBsbCz33XcfL730IpIEixYtIiEhgc2bP+TJJ586a1Dq0aCtrY3vfe+76HQ6TCYTy5Yt59ChEurq6mhqaiY3N5fvfe97vPrqq3zwwQc8+eRTHDx4gJ6eHnJycli5ciUvvvjiqJcnnCrT7u4ufvazn4dTIj3yyCOsXLmSvXv38h//8R/ccccdPProoyiVwS7W5XLx17/+lTfffJOf/exnVFVVsWPHDkwmI1/96td45513yM7O5rbbbht1WRsaGvjBD36AwWCgq6uTJ574MgsWLOD111+nqqqKb3/72+EB+MGDB/nP//xP0tLSsFgsLFu2jHfeeQePx8PixYtITk7hgw8+4Kmnxq7+L4YbQoEBdHZ28vrrr7F27V04nU7a29tZt24doigSFxdHV1cnU6dOw+PxUF5eTk1NNZWVlVRXV6PValm2bBknT56ktLSUrKxMnE4XtbW1xMbGUllZyQsvvEBhYSGRkZEcP36MiIhIMjMzKSkpRqlUEhkZRUdHBz6fj5KSEg4dKsHpdBITE4vdbmfBggUcOHAAg8GA2+2ivPwES5YsobS0lO7ubhYtWsTkyZMvfgYGVLRUYjWY+cryR/jLlr9xvKEcj89DUnQiadHJTM0oRKVQIgFH68rISx7H2lmr+fW7f6SyrQaH20GE0UpdVyMn22pYmD+HaFNU+PqjpRokSeLAgSKysjL58pe/zM9+9jOKivZjNlv4/vcf4s9/fo4TJ04E09gLAvPm3cTs2XPYtGkTdXX1aLUaoqOj6e7uZuPGDXg8HubNmzeU3mZ0CQQC1NbWMmHCeLKzx7Fx4wYaGhrw+XzcddddvPrqqzQ3NzNlyhTKy8tRKhX8+Mf/zv/+7/+yb98+ent7iY9PoKWlhVdeeYUpU6aSkZE56nJKkkR5eTkA//EfP+Mvf/kzO3fuRK1W8e1vf4tt27Zz+PAhbr31VgD27dvL7NlzWLt2LT/+8Y8oLy/H6/USExPDiRMn2LdvL2vXriUqKjJ8/dEeHAQCAfx+P9nZ40hLS2PWrFksWLCArq4ufvazn7Fw4UJUKhVJSYlER0fhcNjp7+8jNjaWpqZGXn/9dSZNmkhGRgYwNjOE+voGamtr+H//75ds2rSJTz75hOzsbP7xj3+QmJiIy+Uc8Tzbt2+jrKwUi8WCx+Ohs7OTyMhI+vp6ef/99xgcHGThwoUjTLmjJXdfXx8Oh4OHHnqIdevW0dTUyIEDRbzwwl9ZsGAhbW1t4cSRXm/QmpSfn09WVhZWqxWbzUZCQjwnT1ZSVHSANWvWhGeXV9vseEN4IYZMOA6Hg6ioKE6ePElrayu1tbXk5ORgMhnp6Ohk+/btfPzxx+Tm5mI0mti9ezednZ309HRjt9uoq6vl4MGD1Nc3UFJSwuzZs7FarRgMepKTkykuPsgHH7xPVlYW+/bt5dVXXyU6Oob6+gbq6mqB4GwrOMMaxG63EwgEaGxsoLe3F1EU2bNnNy6Xm8TERCIiIoiJicFsNnPs2NFLiusoAMlRiXT0d/HCtteoaKokMTKBrPgMIgxWSpsqeGPfRlxeNyDh9fvQa/SYdSbUShURBgu3TV9BanQKClGJVqXhYM0R3ix6F5vbEUxyOYqMG5fDiRMV/PrXv6GmppaJEyfx9a8/RW1tLV1d3UydOg1RFFGr1eTk5PDJJ59w6FAJFouFz33ubvLz85k8eTJVVcGBx+9+91vKysrCppTRQhRFMjIy2LdvP2+99RZxcfFkZGTgdDp4+eWXaW1tJSEhAUEQSE1NZWBggN///vcUFRUxfvx4Hvr/2Xvv+LirK+///Z3em3qXLNmS3DvuNgYMxoRe0oANySYhhU2yv+xu9tknz7NkN22zm/AklIRkgYQEErJ0A8Y2bljuVc3qXRqNZkbT+8z398doBlmWcUGyDdbn9ZItfevne8+999x77rnnPPg35OfnYTKZiMfjNDc38+tf/xqn0wlM3LpI6v1+v59f/vL/sX//fiorK/nqVx8GBOrqalm5ciUymYxEIkE0GsNoNGI0GlEolEyfPp177rmXsrJphEJBjEYT27Zt449//BOBQGBCOI6FWq1m1qzZ6HQ6tmzZwuuvv45CoeDgwQMolcq09eSGGzag1eqwWCx84QsPUVRUSFZWNj6fj/b2dp566knsdvuEyx4gJycHg8HIk08+ydat72I2m3j88cdxuVyEw2Fqa2tpbGwkkUjQ0NDA448/jlKZzPJeU1PDtddey+zZs5k/fz4tLa1otVoee+wX1NXVTThXs9mMRCLw+9//nvb2dsxmC3/96/+wYMFCFAo5P/vZf+BwOBBFkYyMTCorZwAiv/vd7/D7fdx///1p+ev1enbs2MELL7xAMBi87GukV4UCEwSBgoICDAYjR44coaurk/hIAFy1Ws2BAwdJJBIEg0F8Pi8NDfV4vR5kMhku1zAlJSUolSrMZjOlpaV0dnYyOGjl+PHjhMOhdGr2cDiMQqEYqSRmcnNz6OzsHMkErCQQCKSTYI4eYcXjcY4ePUpT0yni8QSiKOJyDTM4OEgsFkUQhIuqLKmx0azCKh65+W9xeJ3MK5vDjLxypIKEa6YvpKpgOkMeB419LTT0NiOXyXB4HPQPW/GHAhjUehaVzSXHmEE4GkatVKOUK2nobaKpv3UixYQgCKxatYrvfve79Pb2cP311zN79mx27NjBM888w913301WVhb79++jvr6OLVveYf369dxxx52cPHkCk8nE+vXX0tHRwaJFC2lv70CpVPL6669NeLDbYDDIkSNH2LRpE1/96lfp7u5i7969GI0m/v7v/56ysmkcPHiQmpoatFot3//+/8Hr9TBnzhyuvfZaFi5cyMyZs3A4HOTk5BAIBOjr62Xfvn0TqrwAqqqq+P73v4/P56Oqqpp169ZRW1vLT37yE1atWkV19UxOnDjBqVONKBQKent76erqwu/3o9PpWLNmDRaLBUGQEIvFUCpVHDp0kJaWlknpwPx+H5mZmVx33XWUlZUxMDBAf38/mzdv5vbbbyMSibB37/uEw2EEITmYmDdvHnPnzsNms1FWVorD4WRgYID9+/dPKMdUmebl5fHoo4+iVquxWCzcdtttLFiwgLlz5xAOhwgEgthsNnbt2oXH4+GGG27AbDYTjUbxej1kZmZyww030NXVzdy5c+noaEet1vDaa6+m+6aJgCiKtLe3k0iIfOtb32L+/PmcOHECn8/H/PnzWbx4CcPDLpqamti7dy9ut5uKiulcf/0NaDQa/P4AK1euJCMjI+07IJVKOHDgAC0tLel3XC5cNSZEk8nEV77yFTo7OykrKyM/P5+cnBzy8vK45557CAYDyGRyVColTucw11yzjNzcXDo6OsjIsKDVaqmsrMJgMJKXl08g4GdoaIiCggIyM7PQ6XRUVVVhNlvo7+/nuuuuR6vV0t7ehsFgRK1WMzw8jFQqpbq6Oj06VyjkzJs3D71ej8fjQSIRsFgsDA3Z0ev1FBQUoFAo0pXnYiCVSAhFw2QYMvjs6ruwaE1IJVJ+/e5zSCVS7l5xK6f6WghEAqyoXMpzO17k/23+DVWFM5iWXYI35KOxr5VbF9+EPxzglQOb0ao05JtzJ0w+qfW95KDBRVVVNQ8++CDDw8O8/vobRCIRXnnlFXp6uunvH2Dp0qUEgyF+8YtfAHD33Xeh0+nYs2cPs2fPZsOGDXR1ddHc3Mytt9424WZEtVrN+vXree21V3n//fdZunQp69evp6Wlhf/4j5+iVKpYtGgRr7/+OqtWrSInJwdRhK9//euYTCb8fj/79+/njjvuQK/X8/TTv0EqlTJjxowJN8uk1mK1Wi1f/vJXkMlkvPHGG/h8Pnbt2s3g4CAKhRKLxcyGDRv45S//Hz/96U9YuvQapk2bht1u59SpU3z+85+nq6uTl176a9oZZTJMSEajCZfLxaOPPopOp+Mb3/g69fX1VFVVc801y2hoaODll1/hkUceobi4GL1eTzAYZP/+/dx+++2YzWbq6xsIh8NMmzZtwjkKgjDSkUuJRqN861vfprKyioqK6cRiMTIzs5DJZBQXF/P444/z0EMP8dWvPozH4yEajXLrrbeSm5vLli1bmDFjBjfffDMDAwM0NZ1i06ZNE1pXBUGgoqKCwsIC/uu//gupVMptt92Gw+HghRdeQBQTbNp0M8PDw+zZs4e77rqLI0eOsHXru5SVlTF//nycTidNTaf43Oc+R09PLy+++AI5OTnk5k5c+7/o7xMv9xzwIuDxeDAajfTY+tK2W6VUgUIq/1DX+NRPynEi5URxNoy+JtXBno9tevQ7zmev0Nhnjr5m9LnR6Qg8HU30bX+V8nu/jEJvOmsoqdRzApEgCVFEq9QgANF4jOGAC7lEhkFtIBwLI4oiGoUab9iPPxTArDWikiuJxmOEoiG0Sg2iCE7fMAqZAoNGh8DEpUhIla/X60UikaDT6YjFYrhcLuLxOIIgoFKpSCQSKJVKZDIZDocDiUSCxWJBKpXi9XpRKpUoFAq8Xi8+n4+srCzk8rPXjYvlGovFcDgc6TUihUKBz+djeNiJwWBEr9en+SQSCWKxGAaDAUEQiMfj+Hw+dDodgiDgcDgQBIGMjIxzp50YKSdPWwN9O16n4tNfQ2EwnZUnQCAQIBqNYjDoEUVwu90jMxgBhUKBTCZFECRoNBrcbjeBQIDMzEyUSiWRSIRQKIROpyORSGCz2dBqtelvmQxPxFAohMPhQKvVotfrRzwSkx6IkUgEv9+PwWDA5/OhVqvTstfr9QiCgNPpRBRFMjMzJ4UjQCQSwefzYTQaT1M6fn8yA4ZSqcTj8aDX69OOXx6PB41Gg1wux+v1IpfLUSqV+Hw+PB4P2dnZE1pXU23K7/fjdDrR6XSYTCZEUcTpdJJIJLBYLMTjcUKhEHq9Hr/fj9frwWy2oNFoiEajBIPBtPyHhobQaDRpB5mL4SqKIpF4hHA8etpxlUxJyB/EaDTidrvT/fvZcNUpMI/HQzAYxGKxIIoibrcLvd6QdrHX6XSEw2HC4XC6AhoMBqLRKKFQCIVCQSwWG7ErX3oL7IUosHOJVkQ862bksUp1tLPGh913sQ3vfPfJjPXEHLu94AzeZxlwTBTP8d4/dgAzltt4g6FzbZM47fh5KrAL2Xs03gbxs5Xjhw3iJqLjHT14O9szzzWQHH3vZG64Ph8e5zsAHjtYnUiewGmD8dHHx5PruXiOvu9yKrCrwoSYcqOvq6ujtTW5bpOTk8PwsJNoNIpGo8VkMnHgwAEefPBBXn/9tfQittfrQaPRolAoaG1tobCwCJVKxbp165BIruwo4KnvTvEc2yFFI1GkUglS6ZnVYOx3iYkECVFEKpWeprwSiUS6Ql9seYiieNrzR78/9exIJIJCIQeEsw4cRq8ppsw8H/ARR7gyzrkLQ8qZZjyzbjweJx6Po1Ao0td8WEec2pc4VjajrQMfZaA0XlmkyzQcRqFUnsFrbMc0ms9YrsnnM8JTOiHec+d6b4p/OBxCoTiT/3jfMFkY/b2ptpZ6b3IfZxy5/PR9nMlvGskogUg8nkjX+3g8jlQqSdfTieY52roz+vnRaDRtFk2dO3MwlmzrEok03e4vxyB+NK4KBQbJWduePbv51KeS+11EMUFbWxsajYbZs2fT0tKC1+uhpaWF1tZWrr/+BpqaTuHz+Zg7dy5utwePx0NJSQlVVVXpfUpXIlINv7m5icFBKzk5ucyYUZmubNFIBBHYs2cnebn5zKisQiaTEYvFEMUEEokUu30Io9GISqUesYE3EolEKC8vJy8vuYk0Eg5z7NgRPB4PcoWcwoIiqmfOuqBRb4rrsaNH8Pm8VFZVk5ublz4ei0Xx+fzs2vUeq1evxWy2pNceJBJJelHcYkma3ro6OxgYGEBEZP68BShG9rfYbIM0nWokEAyg1Wqprp5Fbm7eBfFMPae5uQmlQsGs2XPRaDQAI2Un0traQl9vD6vXrBvZV5ggGo0ik8kJBPzE43EMBiPRaJTGhnrcHjd6vYHZs+ek39HYUM+gzUo8HkevN7B48VJUqgvbNC6KIr29PRzYX0PZtHLmz1+IRCJJmzJ9Pi+7d+1k7bprMRiMI2UaQSJJlq3H4yYzMwuAjo52rNYBEokE8+cvRK1WIYowMNBPS3MToXAIlUrN3LnzyMrK/shKLFUOvT3dHDi4n4qK6cydO/80/h6Pmz17drFu3XVpE15kZEAWDofx+bxkZmZN6iDT4bBz+NBBVq5aPeJo0seyZSvSA5u+vl4GBvrJy8snGAwwbVoFDocdj8cNCGRnZeP1eTl+/Cjl5dMREwmam0+xaPFSiouLJ0SJpdrRyZPHiYTDzF+wiEOHDmA2m6msrCIeTyrdEyeOYbFkEAoFyczIxGgyMzDQRyQcRiZXkJOTQ0tLM7093SxYuJi21hb8AT+rV69Dp9MBl8el/qpRYH6/n0gkikaj4ZVXXiEnJ5tFixaxa9dOTpw4gVKpxGQy09TURG5uLn6/H4VCiUIRxePxMjAwQCAQxG4fQiqdBVz+PRAfBrfbzcGD+1i+fCX7avZiMVswmow4HA5ampswmc0j7r4naWltZunSZbS1teL3ecnJzeP993ezetUaqqpn8v6enRQVl1BeXkEg4OfokUO4XMMYjWby8gsYsA5QnFGMRqu9oM4r1VE1N5/COmglJzuHA/v3ceNNGwGB5qZTOIcdFBQU4XK52L17JxmWDEpKy2hvawUBVCo1dXUnufXWO1AoFNTs28t1191AMBjEOeykra2VcChEQWEhefkFHDq4n8rKKmTjzDrPhVgsxr59eykqLKKntweZTM7MWbMJh8OcPHEMQSKg0Wjp6urE+9YbzJo1m0AggM02iMWSQUd7G0qlkuuvv5G2tla6u7tYtXotNtsgra3N9Pb2IpNJyc3NR6VSM+x0klOec8HbJ1KdlsNhR28wkp2dk1awTU2NOJ1OsrOzGXY52bVzBxmZmRQWFtHR3oYgCMgVChrqa7njjrtBEDiwv4YbNtyE2+1maMhGd1cXkUiI3Lx88vLyOHrsCIsWLWUi89LF43FsQzaMRhNZWdnEYlFEERob63G7hsnIzMbpdLBz53by8/PJysqmo6MjPYNobWnmzrvuwWLJmJBZ4XgQBIH6hjoyszLp6uoiFosxZBvk1KlGlEolRpOJUChET083QzYbKpWagYF+lEolfr8fh32IwqJiVEoltkErVdUzUfdqaGyop7CwCIlkYjgLQnK2vX//PrKyczh65BDLlq2gvr6Ovt5eCgoK8LjdqNVqamtPMGvWHKyDVsLhECDgcrnw+XyYTGZaRwb6JaXT2LVzO7ZBKzpdxYTwvBhcFW70ABkZGRQVFbFnzx60Wg05ObkcO3aM7OwcIpEIwWCQ6dOnU1hYSDQaS3v+ZWdnodVq8fm83HDD9bS0tGC324ErO05cJBImFo2h0+mJxqJ0dXeyZ/dO+vv7aGtvxev1IiBQWFREJBymvq4Wr8eNwWiiqekUJpOJ3Lx84vE4TqcTg8GAxZIBCLS3t1FcXEprazO5uXnIZXJ0Oj3FxSUXbFIQxWSYI5lUisFowOfzUld7kmPHDmMdHKC7u4tYLIpcLic/v4De3h4OHz6AJSMDt8uF2+0iJycXs9mcHKSEw2i1OvLzC+jp6SISiaBWqxmy2cjMyEAiEcjOziFjZHH/QhEIBFCp1cikMuz2IWr27qGjvY2urk6GbDYSiQRmixmTyURLSzONjfWUlpbR0FCPIAgUl5SiVKmwO+wolEoMBj15efnU19eRl5fPoNWKVqtFp9OhUCgom1aenuVdKPQ6PW63i/37ajh69DBHjx6mv6+Pnp5u4rE4CrmCadPKGejv48Txo1gyMnA47CNlmofZkoHX6yUSiaDT6SksLKKvrxcEkMnlDA8Pk52Ti1QiJT8//4zQSR8FgiCg1xtw2Ic4cGAfR48c5vixI/T2dNPb10cikTTTFheX0NPdzfHjx8jNzcU2OIjX4yF7ZJ/WZEEUk+vAlTMqqas9iVarHdmAnUAmk9Hc3ITX68XtcmE0GEEAq3WA4WEnfX19DA3ZiI9E4wmGwvh8PpQqFUqliqrqmRNsPgS5QkFWdhZHjx4mLy8fn9+PIEgIhUN0dHYQCAaIRWPodcn1frfbjdVqxWodIBwOJbcLRaN4fV6ikSgSiUB+fgFZI2HaLheumhmYSqXi1ltvJRQKIZfLUSgUzJ49GyA9aktt5ly8eDFyuTy9X0sul7NixQoUCgWLFi1CLldczk85L+j1ekwmE21trVjMGVRVVSOXy7FarUyfXolt0AqCgFanIzwSHsvldiGVycnJzqG7pwuHw47FYmFaeTlNp07hcrkIBYOAwKBtkMzMLAQhuUctHAld1LqDIAhUV83kwIEauru7KC4uZc7c+cRiMVqam4jH4yNKLIKYSBCLx8jQZWIbHEQqk5KZmUVz8ym8Xi9mswWtVkfTqYb0vjmf14soJigqKiYaixEMBolEIhfVQUgkEoqKiunv7yUUClJdPZOCwkICgSChcJDenl5cw8PEY8l1sEQigUwqw2odwGw2p5VeNBph2rRyavbuoaGhHofdjkIuZ9A6gFqjQavV0tvTTSAYOKdzx9kQjUZpbWuhqKgIj8fD7NlzkUgkNDc1IiJiHRwgGosSjUaIRqPoDQasVisyuRyT0URHZzter4eszCxUKjX19bX4fD4YcXxSKJWUlOQRCoXwB/zEYrEJXXeKxWJ0dLRRWjYNn9fLnLnzAZHmplNIpAMMDlpJxOPEYlEi0QgmrZn+/j6UKiUGo4He3h58Pi8mk/kjcxmL1NrQsGuYjIxMXG4XJpOZ3p4eTp1qwOUaBiEZ1QIBvD4vwZFN37FYDI1Gi1qtxmAwUFd7gqysTKxWK7t37cDv86NUKigqKp4wJRaNRnG5hqmomEFLSzNZmVnYbIMMDg4gk8mJRaMoVSriiTh+vw+/z5fcNymKGIxGpFIZCoWStvZW8vLysA5aaWysRyqTMWgdRK/XMymRvc8DV5UX4kTicpsPP8wLMfWtbreLIZuNrOxsjEYTkDSlDvT3YTSZCIaCSCVSQqEQFouFYDBAJBIlNzcPu30IQYD8/EKi0Sh9/b3EolHy8goIhYK4XC7y8vKQyxX09HSh0WhHgpRe2JpDiqvVOkAgEKCgoAClUoUoijgcdlxuF2aTGZdrGI1Gm3TvzsjE7kjukzMYjPT0dJOdnYPRaMTr9TIw0IdKlYxPaLMNIgiQl1eA3+9jcNBKXl5B2t36QnmGw2H6+3pRqZPPl0qlxGIx+vv7EAQBlVKJz+9DJkvGwlSr1Dic9pH1NgmD1gGKipPBVIeGhhgedpKZmYFarWFgoB+TyYzZbBkZ+YYpKio+0636HF6I6Q522Jk2FxoMBkQR7PYhPB43ZrOF4WEnGo2WYDBIZmYmNpsNg8GAXp9UADk5uSNbATxYrQMolSpycnJHylQgLzcPr8/HkG2QvPyCkY5sYtZtUvyHh51kZyd5iKLI0JANny85WHE6nWg0GkKhEBkZGdhsgxiNJrRaHb29PeTnF0zK+kySXwKbbRBRZMTVPIbDYcdoNOLzeREECf39fSgUCnJz8wiHQxiNJjo62pDL5CiUSgoLi4hEIni9XiwWCw67nVAoRHZODtnZExPUVxST8Vv7+3sxGk1IpVJ8Xi/hSASZXE40GiEei9Pa0kxVVTWxeHKjuiAI9PR0odXqUKs1FBUVYR8aQkREo9Gm60BxcSlarfaCuU650V+gAvuk4Vxu9KNdZ8ea9cYT+VjvpLO5h3/gVSem1zw+qsty6tmj3zf2ueO50n/we8pj70z34LO5Cl8sz7HPH+2tNR5SXmcpGYiJBILkg3I7m3fYh5bpebjRf8A1AYzvOn3uMv2g3ozdD5m6LvXMifb6m5g6IQFhcrLWjeWXahPJOigiiuDxuFGrNWmPVEjOhuLxGIKQDIc2utzG1oeJLsuxdSCFWCyG3+fDYDSedk0kEkEUEyMzsNOzaIxtRxfKdcqNfgofilSFSplHxzv3YcfP5lp9rnMXg7M11vPnI/2QcxPH9WyN9VydzWgZCKN/v4AyvniuF1c2Y++TnifvicJE14mJxlh+Y7mIonia+TKloJLKTDHufZM1+D4XV7lcjslsPu2cKIqoVKdnm7jcLvPjYUqBTWEKU5jCBONcyvdKwseJ61hceSp1ClOYwhSmMIXzwJQCm8IUpjCFKXws8Yk1IY5dcP/EQfwgOHFCFCc8L9cUrnCknAhGPOIuJlfcFKZwKSBA2slsok2Tn1gFBuD3eQkGJyfp3uVGMOAnpNHjdA0jC4e5XPswpnC5IOIPhQhpjSN1IHK5CU1hCmeFWqNFq9VN+HMvSIE9+eSTPPnkk3R2dgIwa9Ysvv/977Nx40YAQqEQf//3f8+LL75IOBzmxhtv5IknniAnJyf9jO7ubh5++GF27NiBTqfjwQcf5Ec/+hEy2cTrUrVGk46F90mD1OPAG/Jj1OuR6SYv4sAUrlxIhm34Al4MegNy3Ye7G09hCpcT43lDTwQuSGsUFhby4x//mOnTpyOKIs899xy33XYbx44dY9asWXz7299m8+bNvPTSSxiNRr7xjW9w5513snfvXiAZ32zTpk3k5uZSU1PDwMAADzzwAHK5nB/+8IcT+mGCICCRSJFIJted9nJBJpUhEZMJIJOBhadmYFcVRBGZRIoEEblM9rGIDjOFqxuT4tkofkSYzWbxt7/9rehyuUS5XC6+9NJL6XONjY0iIO7bt08URVF86623RIlEIlqt1vQ1Tz75pGgwGMRwOHze73S73SIg9tj6RHfIK7pDXjEUDYuJROKjfs7HBu72U2LD0z8Ww26nKF5F3z0FURQTCTERj4uu5lqx/tf/Lobdw5eb0RSmcEFIJBJiKBpK99+pn3Asku7f3W73OZ9z0V6I8XicF198Eb/fz/Llyzly5AjRaJTrr78+fU1VVRXFxcXs27cPgH379jFnzpzTTIo33ngjHo+H+vr6s74rHA7j8XhO+7lQiKOcHkb/nO2aKUxhClOYwpWNC1ZgtbW16HQ6lEolX/3qV3nllVeYOXMmVqsVhUKByWQ67fqcnBysVisAVqv1NOWVOp86dzb86Ec/wmg0pn+KiooulDaQDOOS8taKxWLE4/EzlFYwGMTr9Z6WqHEKU5jCFKZw5eGCFVhlZSXHjx/nwIEDPPzwwzz44IM0NDRMBrc0vve97+F2u9M/PT09F3S/KIqEw2FeeOEFtm59F4/HzW9+82vq6+vw+Xz4fD4cDgd1dXXs3LmTF198EZfLlVZwU5jCFKYwhSsPF+z6p1AoqKhIJjBbtGgRhw4d4rHHHuO+++4jEongcrlOm4UNDg6Sm5sLQG5uLgcPHjzteYODg+lzZ4NSqUT5Eb0JBUEgEgnz9tvvoNXq2Lcvmen16NFjI8kZTRw9epTVq1fT1NTEY4/9gptu2sg111yTvv/jjtHKeGww1PSx5IXjP0BIBioFmJwQqR/gXAOH8fh/2LUTjQt594UMgi4X17MFej2feyYa49bL8zh2NkzJ/8LK6XKW64XiI0fiSCQShMPhkTxZcrZv354+19TURHd3N8uXLwdg+fLl1NbWYrPZ0tds3boVg8HAzJkzPyqVD0U0GsVsNqPX6zh06CBlZaX4fD4MBgOdncnEh+Xl08jKyqSgoIDCwqILnuldyUhVSW/ITzASQhRFYok4dq8Du9dJPBFPb4yNJ+K4g16i8Rj+SBCbx040nszo6w8FiIzkSZs0riMmXbfbjd/vB5Km3b6+PhwOx2nm3VAolJ4t2+127HZ7Ou282+2eVFNwPB7HZrMxMDAwErlbTFsIXC7XaaZpn89Hb28vHo8HURTx+XwMDAykc855vV7C4fCkrcF+kKDQmi4Xp9N5GndRFInFYthsNqxWK5FIJP2Nw8PDiKJINBrF43FPGs8Uh8HBQaxWa7p8IFkHxpbraPk7HI60/OPxOG63e1KsKKnnBQKBdJk6nc70O3t7e9P1djT3/v5+3G53Wv79/f3psp8s+Y8up9HtJyXXwcHBZO4vPmh3iUQimfsvFBrJ7J0s15RsJrtdXQguaAb2ve99j40bN1JcXIzX6+VPf/oTO3fuZMuWLRiNRr74xS/yne98B4vFgsFg4Jvf/CbLly9n2bJlAGzYsIGZM2dy//3389Of/hSr1cq//Mu/8PWvf/0jz7DOBa/Xi8PhYN68+QwMDKDT6Tl+/BiBQACpVEIkEsZud9DR0Uk4HEalUqWTIl4JI42PAlEUEYE+5wDP7nyRTYs2MKewkjePbeWtI9sQxQR3LfsUN8xdSyQW5c2jW9lZ9z4PXfdZapoO0W3vZe3MFSycNo//OfAmtyzcQFnWxa1DnhdXUaS5uZl///d/Z+XKFXzmM5/l8ccf5+DBA6jVGr71rW+xePFient7eeqpJ/H7A3zmM5/hueeeQxQTPPzww7hcbo4dO8bf/u3fpvMVTSTHRCLBO++8w+9//xyhUJjbbruNZcuW8fOf/xdutxuTycw//dM/UVZWRl9fHz/72X/Q1dVNTk4OX/7yl3nzzTdpa2tl06ZNLF16DX/60x/5zGc+S1lZ2YRzDYfD/OxnP+PkyZOo1WrWr19PdfVMnnzyCbxeL7fccgt/8zd/g1Qq5Y033uAPf/gDoihyyy23UFlZyXPPPYtOp+Ob33yEuro67PYhPve5z094m011kG+88QZ//OPzJBIJ7rjjTj796U8TDAZ57LHHsFoH+NGPfozBYKCnp5snn3wKn8/Lffd9mueffx4QefjhrzE8PMyxY0f527/98oTLH5KDl9dee40//OEPGAwG5s6dy4033shTTz2F3T7E9Okz+O53v0tWVhZer5f/9/8eY9++/WRnZ/P1r3+dd955m5aWFm66aSMrVqzg+ef/wL333pe2bk0kfD4fjz/+OPv27UOtVvHII4/g9fr4zW9+Qzwe5/777+f2229Pp0qqq6vj97//PV/60peIRqM88cTjSCQSvv71b2C1Wqmrq+NLX/rSRWcKn0hckAKz2Ww88MADDAwMYDQamTt3Llu2bOGGG24A4Oc//zkSiYS77rrrtI3MKUilUt58800efvhhli9fjlar5cEHH+TRRx+d2K8aB3q9nvXrryMzMxNBAIfDCSRnkKkMzV6vF4kkmZBRKpV+pM3VY811o49dDoXoC/n5w66/4PK7UUhlDAfc7Krfy70rbiUUDbOz/n2Wz1iMLxzA4XMSjoQY9ruxeR1UFsygrreJPqeVAnMOBeacc7/wI8DhsPP73/+eUCiI0zlMJBJh+vTprF27lj//+UWOHj3KvHnzaG9vBwSGh500NzdhMOiRSmXs2rWbzs4OPv/5z6NWqyeFoyiKqNVqvvzlL9Pe3s6BAwdQKBTE4wkeeeTv+N3vfkdTUxPFxcUcOLCfQCDIf/7nz3jsscd45523sVoHWLBgISdPnqSpqYmysmkUFhZOCtfUiHrRokXMnDmTpUuX8vrrr7NixQpKS0v4059e4Pbbb0ehUPDWW5u57777yMzM5Omnn8bhsFNUVMzQ0BDbtm3j1KlTPPzww6fluJpornq9nq9//Ru0tLRQU1PDpz71KV5//XXa2toIh8PER6wBHR2dAAwPD9Pc3IRWq0Wr1bJr1046Ojr5/Oc/N6mdrM/nY/r0CpYvX8GCBQtIJOJs3LgRi8XCE088Tn9/P5mZyWzLfX19/Mu//C/++7+fYefOnfT19TFv3nxOnDhBZ2cnhYVFlJSUTArPWCxGeXk5a9eu4c9//jMHDx7C5/Nxxx13EAwG2bZtGzfeeCNarZbBQSu/+MXPMRiMiKJIS0sLer0BUUywY8cO2traeOCBB85ItXK5cEEmxN/97nd0diZnKDabjW3btqWVF4BKpeLxxx/H6XTi9/t5+eWXz1jbKikp4a233iIQCDA0NMTPfvazSYnCMRqCIKDVaikvL8dkMmE0migrK6OsrIxp06ZRXFxMbm4u5eXlTJs2jbKyMoqLi8nPz/8gEeGHuOGfr3v+2L/Pdd9EIPXcw+3HaextIsuQwa/e/i213adIxONkGTLINWUTT8RJiAlyjVncvngjerUWk8bAmqplBEJ+9CotDt8wkViU3af2p82QE80zFArx7LPP4na7yM/PT5vd5syZwzPPPMOxY8cpKipCLpezcuVKbrzxRmQyOStWrKS0tBTjSDr5vLx8jhw5yvHjxyfc3JEa4CxevJgDBw7y/PN/JC8vj/nz52G3D/HjH/+YoaEhysvLkUgkZGRkYrfb2bLlXTo7O5kxo5Ibb7wRn8+HVqvF7w8QCoXYuvVdQqFQujwmChKJBIvFzODgIK+99hp//OPz3Hvvvdx1110cPXqMiooKdDodcrkcs9lCTU0NO3a8h1QqZf3669BqNRQXF1NfX09ZWRk7d+6kqal5wuusIAgoFAquueYa9uzZw5/+lCzXmpoa3nzzTWbMmIHH46ahoZFYLMby5cu5/vrrkcvlLF+eVMYajYaenh5yc3M5fPgIx44dmzRzl16vx+PxsnPnDp544glyc3ORy2X853/+jFgshsViAaCsrIxHH/3BSPZlD6tWreLmmzfh9/sxGAw4HHbC4TDvvPM2wWAQmDj5C4KAyWRi0aJFPPPMsxw6dJiysjK+9rWvsWrVKurq6pg5sxqlUkksFuOdd7YwOGhDpVLygx88Sm5uLiUlJWRmZtHe3k5hYQEHDx6gtrb2ithydFVFox+b1C0ZrSNZBLFYjLq6OpzO5MystbWVnp7ucwpovPOptZBjx44SCoXSJqe2tjb6+/svudATooh1eJAZBRXcv+ZejBoDdq8DQSLgCwXwhQOAgDvoo8fZj0QQQBCQy+RcO3Ml189di9PnIseURdtgF7sb91Pb25j81gnmmloHUCiUtLS00N7exqFDB2ltbeWb3/wmN964gZqaGlpbWxkaGhqRI5hMJr7whYcoLCxCoVAyODjI4KCV5557jqGhoQnlKIrJbLXHjh3jhhtu4Lvf/S6NjY1s3bqN3Nw8fvCDRykpKebw4cO0trZSWVnJV77yZfbs2UNubi5r165l06Zb2LRpE0NDdkwmI42NjbzzzhaOHj064fUjFosxf/58HnnkEe68807a2toIBoP87ne/xeGw8+CDDxIKhRgcHOSrX/0KRUVF7N69h7vuuov58+fzla98FbVaTV5eLk1NpxgctPLss89c1H7MD0OqXI8ePcrNN9/MN77xTRobG+nu7qagoIDW1hZsNhuHDx+mra2NoaGhdPvNyMjgoYe+SFFREUqlEpstuYb2+9//HrvdPqE8ITlTzMjI4Gtf+xr33/8AVquVo0ePodPp+cEPfoBarebEiRO0tLTg9/tpbGzkqad+zZ133sWsWbO46aab+NSnPsXQ0BDZ2dnU1dWyfft2jhw5MuEDQ6czaaH45je/yaZNmzh48AAul4tf/vKXGAwG7rjjThwOB+3t7XR1dbJ69Wq+/OWvAALRaJSHHnqI3NwcDAYDvb19DA7aeO6553A4HBPG82LxiQ7mm0KqYXR0dOLzecnMzEQURQKBAHq9HqfTiclkYvPmzZSWlrBo0SL27duPQqFAJpPjcrnIysrC6XQSCgXR6fQEAgEqKytRKpW4XC46OzspKCggFAoxPDyMXC5Pm2by8/MJh8PU1dWRn5+Pz+cjGAxisVhQqVT09vaiVCqpqKiYlLVAqUTCnOKZ7Gnczw9f+TlyqZwFZXPwhwI8t+NF4mKC6+auoXmgjfcb9/HQtZ9DrVAjlUgJRsPsbNjL6qplKOUKWvrbAAGDamIDc6YGF0ajkb/7u28Ri8X4/e9/j8/nY+XKVSNrpgP4/QE++9nP8sILL5Cbm0t1dTUajRaJREJnZycNDfU88MCDvP322zQ2NpCdnTMp5g6JREJrayubN29GEGDmzFnMnTuXmpq9/OpXj+N2u8nOzuaxxx7j1ltvRa/XI5FI+MY3vklGRgZ+v5933nmbjRtvIpEQeemlv6BQKDCOSus+kVxPnWri+ef/SDwe55ZbbuHtt9/ilVdeIT8/n5/97D9YuHAhp0418Z3vfIdYLMbtt9/GTTfdhFQqTTtePfDAAzz//B/o7OyisrJyJITZxEIURerr69i2bTsgMmfOXD7zmc+gVCqpra3lt799mnvvvZff/va3ZGZmMmvWzLT8u7q6qK+v44EHHuSdd96mvr6erKysSWlTgiAwPDzMr371SyQSKYsWLUIikfDUU08iCMllCLlcxs9+9h9s2HAjzz//PDabjf/5n//B6XRyzz338Pbbb3PDDTegVCppbDyFXC7HYDBMuPzj8TjvvruVnp4eAoEA9913Hy+88ALbt2+ntLSE//zP/6SwsJDh4WFWrFjBr3/9FEePHiEjI5Pp06fT2dlJU1Mzf/M3f8Nrr71Kc3MLBQX5k2ZGvhAI4uWeA14EPB4PRqORHlsfBkMyiKlSqkAhlY8rfFEUsdls/OQnP6GiooLOzk6kUikSiQSlUsm0aWW0t3cQCATIycmht7eXnJxkxxeNRonFosjlCvr7+zCbzQiCwNDQELfddjtqtZrjx48RDkcoLi6ms7MDhUJJOBzCarViMBixWMxoNFr6+vooKiri6NGjFBYW4na70el0aDRa/H4f3/jGN9BoNOdVgT0dTfRtf5Xye7+MQm9KpysYD6IoEhcT2D0OXAEPGTozFp2JcDRC/7AVQRDIM+eQSCTwhwOYtSaG/S6MGgNSiQSnz41JY0AQoN85iEQqId+Ug1QinfDGNro6Dg8Pk0gkMJvNuFwuBgYGUKlUFBQU4PV6kUqlqFQqPB43WVnZBINBgsEgGRkZeL1e+vv7ycrKJDMza0J5jvbs6unpIRqNUlBQgFarZWBggOHhYcxmMzk5OdjtdvR6PbFYjGAwQE5OLlKplGg0it1uJyMjA4Curi6USiUFBQVIpeco1xHTjaetgb4dr1Px6a+hMJg+lGvKC1IqlVJYWIjP58PlGiYeTyCVSjGbzUQiETIzMxkaGsJoNKLTJQcpKS8/s9nM8PAwg4OD5Ofnp7fLTHTZ+v1++vr6iMfjFBQUpNt4anCYnZ2N0+lMyz81WAgEAgSDQTIzM/H5fPT19ZGVlUVGRkba4jKRPMPhcFr+hYWFqNVqrNYBXC43GRkZmM1m7HY7Op0Op9OZ9qg0Go1kZGTgcDjS8u/u7kImk1NYWIhMJpswrikz3/DwMAMDAyN1LB+Xy50O2KBQKNDr9YiiiMlkYmBgAJ/PR25uLhaLhUAgQCAQICMjA4/HQ39/H9nZOWRmZgIXJ39RFInEI4Tj0dOOq2RKQv4gRqMRt9udlv3ZcNUoMLfbzc9//nNuuukm3nnnHUQxwaJFizl8+DA33HAD27dvw+12c9NNG6mtrSUYDCKVSvB4vNx0002Ew2GOHj3KjBnTcbs99PT0sGDBgnRl27NnD5FIhIyMDBYsWMDevXuJRCLceOONvPLKK9x8880MDAygVqupr69j+fLl7Nu3P20nX7JkCStXrkw7kZyzDM5DgX3gNPLB1q6xXpXiWfZ1iYjj7vUaff1pzxl5/sW0u/PZ45NqiON1RGfzFE0kEmkT03jP/iidxOj1z9HPGuu8M5rbaP6jv2fsc87J6wIU2MVwHc3rbLIYfX4icTau4717vGNn+32y9lddSJmOPj4e19S5S1Wm43Ea/V1j204KiUQizfNiuU6UArsqTIgpeL0eTpw4zpIlS+jt7UEikbBmzVoaGhqYN28+8Xiczs5OyspKCQZDJBJxvF4ftbW1VFRUoFarCYcjyGRSTCYj2dnZLFy4kP379zNr1iwsFgtWq5Xe3l5WrFhBXV0dpaWlbNiwgaqqKpxOJ+FwGIPBCAhotZqR9RmRbdu2pp8xETjdaST5/9jKNroijsUZCm2cDmX89b+L7yzGdqBjHV/O1qDO9r7xrv+o47Wx96dcj8/Gaey66+hnjP7Wc5XrR+nUzqaMRr/3w5TAeM+bLE/aD1Oc4ymusfVivPKeTK5j5TaW/9h3j8dvsrmOffZ4yvJ82lrqm853kH0pcNUosHA4TE5OLqtXr2H69OnE43EEQUAmk7FixXLk8uTsLRQKoVAoTmsc8XgcqVTKunXrThN4KpXJihUrCIVCqFSq9CZamUzGunXrUCgUlJSUIJFIyM/PT8dilEgElixZwtat72I0mgAmzFaf4t7f38fg4CB5eXnk5eWnK2kikczge/DgfvLzCygpKf1gVkAyMYvf70OpVCGXy/H7/XR2thMOhSgtm4bZbEmbUJqaGkdmq1Ly8vIoKio564zobFwBWlua8fn9TJs2Le3Cm+SaHEQcPnyQxYuXYDAk14hSyjcejxMKhdBoNEgkEoaGbPT1JU1lM2ZUolAoEUURl2uY9vY2otEocrmc8vLpaXPwhfD0er20t7ehVCgor5ieXgdKlXlHRzs22yBLly5L50BKzQRjsSixWByVSkU8HqenuwuH04nZbKKkpGyEi0hHRwcO+xAioNVqqaysTtfPiYAoigwM9COTycjO/mBLRCKRIBQKsn9/DfPnL8RstpxW1olEgmAwmC7rS9GJRWNRuru6yMvLQ6PRpvmn6ndHextLr1mebjsprpFIhEQijkqV3EYx2VxFUcTj8eBw2Ckt/WAvX8oD8vjxo2g0Wiorq9KDnlQ7CQaDyOXySVlTPBv6+/uQSqXk5HzgJZ6S/4ED+5k7dx4WS8YZbS0QCKDRaM5t5r5EuGoUWMpjSKFQpCtLqgKN3iuS+n20cFIV62ydiEwmS68XAGdd3ByroLKylNx1190Eg8G0G/NEVQq3283One9RXT2TXTvfY+PNt6DT6XG5XHR0tKHT6XHYhxgcHKS7u4u5c+fR2dlBIOAnJzuXXbt3sHz5SsrLK9j7/i4MRhMmkwmn04nNZsNuH8JkNJFIJGhtbSE/vwC9Xj+u2e5sSCmFnu5umpoa0esN1NS8z/XXbwAEuru7sA/ZyMzKpqe7i1gsSm5uHvn5BbS3t6XXMetqT3LTxk2oVCp27NjO/PkLCQT82O12BgeTESUMBiPxeJyGhjrmz19IMBDAbDZfUJnG43H21byPSq1i0GolnohTVTWTaDRKc/Mp4vEYiNDYUE8gEKCqqppQKIR1YIDMrCza21oRBIG1666lt7eH+rpa5s1fyPCwE5msl76+XhQKBYIg4HQ68XjcTCuvIBIOT0jnlipvp9PBu1veZvqMSrKyshBFkb6+PgYG+jGZTPT0dBMOR8jKyqK8vILW1hZEUUSr1XLo4H42bNhIXn4BMLmKQRQTdLS3sW3bu9x2250olUnF39rSTCAYQKvV0dzcRDgcprxiOlKplJ7uLkwmM/0D/QT8Pq6/4aZJ2ch8Ok+RcCjEwYP7GLLZKCoqQiJJLj+0tjan1+laW5uxWvuZN28BAwPJqBy5ObnU1OxNOgHNmz+h61/j8QRwOp1s27qFsmnlZGVlpwcDA/19WDIy6O/vJRwOkZGRSUXFdFpbklsmtDod+/fXcNNNt1BQUDApHC8UV4UCE4Tkvh2/38+xY8dYunQpVquVgYEBFi5cSHNzMxqNZiRcUS+JhIhcLicSiVBaWopUKqW7u4uVK1ehVqsntIKpVCqUSuWEV9pAwE8kEqGgoJBjx47Q3t6G3+9HLlfQdKqB2bPnIggSDAYtPT3d6dG1SqXi+IljSAQJJpOFaDRKX38flZXVFBYVY7UOsPf9PUyfMYP6+lpuueU2Ojrayc/Pp7Ky+ryVVwqiKNLR2Y5MriA3L58DB/ZRV1dHPB5jYKCfYDCI2WxBKpOiUCg5efJE8n15+bS3taI3JJ0N9HoDdvsQgYCf/PwCVCoVR48ewel0IJPJiEYjVFVW09x0ioqK6SNm3AuDKCZwOOzMn78Qr8dLf18fHo8bnU7PiRPHyc/Lx5KRgUajIRQKcfLkcTweN7NmzuHAgX0olUrKyyuQyxX09vag0+kpLi4hLy+PLVvepriohMbGetasXQciJMQE8+cvnNB9kgG/n1ONDWi1WoKBAE1NTXg8btxuF06Hg1mz5yCVStFptTQ01DHsdKDV6hi0DaJUKlGp1RgmwVNyND6YYfXT19uLWqXG5XLR09ONVqul9uQJTGYzpaVaFEolkWiEI0cOEYvFmDlzFkePHiYjIwNLRiYq1eRG+BFFkUQiTvNIJy8CAwMDdHV1oNFoqa09SXV1NRJBwGgwMmi1ckI8hsvloqCgkMNHDiFIIDMra1L3w6aUVyDgp6GhDrVaTSgYpLn5FG63G4/bjcNhR6fXI5FI0Wg0NDedwjU8jEqtwm63I5PJUCqUGAz6SeN5obgqFFgKTqeTN998g7y8PF577TVKS0uprKxk166dWCwWyssrGBgYQCaTkZeXz4kTJ8jPz08ru4lOiz2Zdm+tVodKqaSnuwutRkthQREiyZhr9sxMWlpOIZPJMZlNSfNALEYg4Ecul6HRaPB43IRGXP2zMrPo6GwnEAjgHHYSjUYIh8NodToQIBwKEw6HL+p7BEFg2rRyjh8/Sn9fLznZOZSWlhKNRpEIAk3NTbS2NhOLxtKmN0QIBAMoVUr0ej1u9/DILMuAVJIcbASDATweF4GAH4PegFarIxaPE46EicXiF7WOIwgSsrKzsduHCIaClJaWkZObSyKRoHxaOe0d7YiISKUyVEol/oCfWDSGz+9Do9Egk8kIhYKIYoL8/EKOHD5IZ2cHg4NWopEIPp8XpUqFXK4gFAoRCn6wWXyi6khCFNHpdARDQQS3iwULF5GRkYHVasXj8dDW1koikUiaw+MJEqKI15eMUKNSqnBE7IRCIXS6ye/EBAQ0Wi1ut5tQKEh5+XRAxO/30dzcjGIkC7VSocTj9gDiiDk5qdiCwQCxWBypdHK7uURCRKFIWnScjuSAqXJGNaFQkOLiYtra2jCbzCiUSqRSGfFEnFA4SDgcQqvR4vYkPVQvxHrxUbjqdXq6IhG8Xi/Z2TlYLJlYrQO4PW6am5uIxaLJtjYS3MDv8yOTydBqtTgddsKh0IjX4uUP6HtVKDBRTMZYGx4eprCwkJMnTyKXy8nKyuL48WMYDAas1kFuvnkTTU2nmDlzFrm5uezdu5fe3l4aGhpYuXLFJbVRf1QYDAauXX89g4NWrl1/PZlZWUDSvFlcXILRaCIQ8KNQKDEaTWRmZuN2J92qi4uL6e3tRSKVIJfLWXftdXR2dBCJRqiqqqagoBCPx82qVWuQSmXMnjMHk+nCzHGjUVBQSDQaxe/3UVJSik6nH5kRBpitUGCxZFBg/8C1Ozc3j4H+fkqKS8nKzkleH49jtli48aab6e/rQ6PRMH36jOR3SARKS8vw+/0sXLgYmeziBiJSqZTly1fR2dFOXn4+paXT0i7xlowMMrOyUatUeLxeFAoFEomAUqnEYbezZs1aBCQMDlpJJEQqKiqQyWTpiCMzplfS199LWVk5GRkZyUDTxg/WAicKOp2OWbPnoNZoEUY2gAuChGg0yvTplWRmZuJwODDo9ShVKnJz87ANWlGrNeTk5NLW3ppe15nMzksQBPILCjAYjaiUKnLz8sjIyCAWi5GRkcXSpWb0egO5eXmo1WoKi4rR6/XYbIOsWLkKuVxOX18vopiYNI4pnjKZjOnTKzEaTeTk5GE2m1EqVfh8XrKzcykvn04sFkNMJMjLS24/KCubRjAYZO68BdiHbCQm2Rk8tcadlP9s1BpNclZoTA5gY7EoglCFxZKB0+lEr9ejUqnJzc1jcNCKWqUmNy+fzMysEaewy7/+BVeRG73NZuPgwYMjUZgHicXi6PV6dDodmZkZbNu2jXvvvY/Nm9/knnvuxW63c/jwYW6//XZeeOFPbNx4M1VVVcDlH3XAud3oU2aY0R5FqUqcWpQdD6nrx3O5HX1u9HNFUUxW54uY1Zzu4isiCJLTzp2toxzrkj6W5wfnEsD43nYXy3WsG/Hosj4bV4lEAD6c6+jnfqhbtXhhbvTjfcN4M7vx6sXYMpxs1/Tz4ZqqK+N1omfUy0n0mBzLc/Q7Uzh7mYrj/n45y/RC2tpH5TnlRn+BSCTi5OfnUVBQSCwWo6OjY2SNqACLxZI2Ky1cuAiVSoVKpWLBggUAzJ8//4IX/C83xnYyoyvch5kpzlY5Rx8fe81H6dBO7xTP7no+3n1ne//p587cB/ZRuY4eEIw+91G5ns0dfCIxVgmNPnb2bQpn8roUg7jxuH7w7vMv68nG2QZH57P141IPhs8m/w/jcjnK9Hxx1Siw3Nw8cnJy0wLMy8sDPuh41qxZgyAIVFZWIggCOTk56fPZ2dnjdlpXOs6miC723snC+TScj/qsicJHKdOJuu+jYiK/YbJxpfIaD+Mr2gu/b7LxcZL/uXBVKLDxTB9jBfZhCurjqLymMIUpTOGTjqtCgcGU8pnCFKYwhU8aPrEK7GPom3LhSC3Ijvp9ClcJUgOypPCvjvo+hY89Jnoi8YlVYADBYIBwOAQTnrXqckMgEAwSVmlwez3I4gk+ed84hQ+FCP5wiLBai8vrQR6PX25GU5jCWaFUqlGrJz479idagSkUyknP9ny5ICoUyKMRNGo1cu3E5uaawscAIiTkcuSREFq1Brn2yomOMIUpjIVEMrFBIFL4ZPbufBA+aqKjZ1wpkEmlSBJx5FIZcvnlTyw3hUsMUUQmGakDsqk6MIUrH5Phh/CJVWBwlThuCEJ6E/EUrhKI4hiD8eRvgJ3CFK5ETG7grSlMYQpTmMIUJglTCmwKU5jCFKbwscSUApvCFKYwhSl8LPGJXgO7EIxNtT722GhcbBDYC7n3Yu4567NG/ZtePBE4zfP+XN88HiZj3eV83j86AOr54nJxTb37fL9rMnAh777c8v+4YKJlP/r6ycBktKsrQf5XjQI7X8GMd10qDNXo/yebx3jv/wgvJS4mCEVDyCVyFDI5ogiRWIRYPI5aoUQiJL0142KCWDwGI++USmTEEjFEUUQpVyIAsXgsmVJ8klIqhEIhIpEIarUauVxOPB5Pp10fndU6Fosl01SIIlKpFLlcTjAYRCaToVAoSCQSxOPxSUmDk4reHYlE0tHaJRIJEonkNE4KheK0a+VyGYmESDgcRqVSIZVKicViCIIwaVs+EonECE8x/R6ZTEYikSAWi52WCTwejxONRtOBaBUKBeFwGFEUUavViKJINBqd0OzhH1dEIhHi8TiCkHQTTyXBjUajqNXq0zygI5FkDj21Wj2SYDWaruOpOpOqPxNdrqk+J1kvE8jlyToZDAYBTkvSO7qupOQfjUaJxWKo1er0cyYzc/SF4KpRYPF4nI6ODoxGIxaLha6uToxGExaLJa2U6urqiEajzJo1C4lEQlNTExKJhIGBAcrLy+nv72fhwoWoVKoz0mLAmSOSVMd14sQJ5HIZs2bNPu2asc8QBIFYLEYikaCxsRFBgDlz5p41Kvf5QBRFgpEQrxx6iyOtx7HozXx29V3EEwn+tOevuP0e1sxawcb516GQyTnYdoy3Dr9LLJ7MX3Tjouup7WogEAlx97JPIZVIqWk+yC0LNmDU6CesEqfKsLm5maeeegqXa5jZs2fz2c9+jjfffJO9e98nMzOTr371YcrLyxFFkRdffJGdO3egUCiYNWs2CxYs4OWX/4fs7Bweeugh6upqsdls3HnnXZPS4FwuF0899RSdncnMBjNmVFJSUsz27dtRKlVMnz6dL33pSyiVSvbt28df//oSDzzwIA0NDRw9eoSbbtrI8uXLeOmlv7JixQpmzZoFTOzIVhRFWlqa+fWvf43P5ycSCXPrrbexadPNvPPOFt5//33+/u//ntzcXAB2797NH//4PAqFkry8PDZt2sSrr75KPB7jC194iEgkwvvvv89nPvMZ9PqJk//HCaIo4vf7+d3vfkd9fR3RaIyCggJuvfVT/PnPf8bhcLBq1So++9nPolZrsFqtPPnkE3R0dLB48RLuu+8+Xn75ZU6dauSOO+6kurqal19+mZtvvpmSkpJJybUWCoV44YUXMBj0bNp0C/v27ePFF19AFOGzn/0sa9euBeDQoUM8/fTTKBQKMjIyuPPOO9myZQtOp4MHHngArVbHli1buO+++9IZOi5nHbgqFJgoisTjcd5443XC4TB3330PP/7xj3nggQdQq9VkZ2djtztoa2vDZhvk6NEjFBYWEY/HUSgU7Nq1C5lMSl9fPzk5OQwNDREIBJg1ayYul5u+kQSKqSj3DoeD7OxsOjo6yMvL49SpRhKJBJWVVbS3t2G3O4jFoigUSrRaLZmZmbS0NJOXl8/JkydJJOKEQmFstkG8Xh9z584dyYB6cRW7aaCNPY37+dzqu6k5dYA3Dm8hGo+hUapZXrmEVw5sZm7JLMqyipiROw3Nittp7m/l3ePvYXXZcAe8ZBjM7D11ALvPyfyS2ehU2kmR0+7du4hGI3z+8/fzq1/9kjlz5uL1ern33vt46aWXeP/99yktLUUQBNra2rBYMli2bBnTp09n9+5dmM1m+vv7ee+97ezfv5+HHvripO0F1Ol03HbbbQwPO3nmmWeRyWT09fWjVmu49tprKS8vR6VSYbVaeeeddzh5spbm5mYOHjzAnDlz2bZtGx0dHQSDQcrKyiaFI0B+fgGf//z99PX18pvfPI3JZOTo0aP89a8vYbc78Pv96Wv7+/tJJBKsX7+ewsJC+vr6CAQCaLVadu7cSUdHO2vXrkWrnXj5f5ygUqnYuHEjS5Ys5i9/+QvRaIS3334HmUzG5z9/P08++QQLFixk7tyknPv6+rn//gd4+umnMRqNnDhxnJkzZ/Lee+9RV1eHRCJJDyImEqm+76233mLz5jf59re/QzQapbu7m+uvv4GWlhZeffUVrrnmmnRd9fl83HnnneTn5+Nyuejr66WwsIgdO3bgcrmZOXMmen1y4/zlHsBcNU4c8XicnJxcBgasHDlyBKVSid/v5/XXX6e2tpbXXnuFRCKOVCqlv78fURRpb2/HarWiUCRTvDc0NHD8+HFefvllDh8+xLvvvsuLL77AoUOHOHLkMK+99ioNDQ28/PLL1NbWUl9fz5///OJImEKBeDzGe++9x759+/jLX17iwIED/OUvf+Ho0aPU1dXx17++REdHB+Fw0rQQjcbYuXMnDQ0NH8lsKZNKSSQSeIM+AuEgEkGCPxxgWk4pswqrkElleEM+RMCiM1GeU0KbtYOVVdewbuZKDBodQ24HTr8LuVSOTCqjz9mfnmFOFFLpbNra2vjNb36DWq1h9uxZ3HjjBt555x26urooLi5OZwdQqVQ0NZ3iD3/4A2+88QaLFy9hcNCGWq3mwIEDzJgxA6vVit1uByY2PqYgCMjlciorKxFFCAQC3HbbbWg0atra2vjjH//ISy/9hVAoRH5+Pt/61rcoKiokMzOTxYsXc/ToEUwmM01NzUybVkZDQwORSGRSeOr1embNmkVvbx/V1dVkZmbx7LPPsnjxYlQqJUNDNqLRZGJBhUJBf/8AL730F/7wh99TVFSESqXE5XLR1dWJVqslHk/Q1dWVzsx8tSEVJKG8vHxkhjXI3Xffg1arIRAI4Ha7iUaTJsFEIoHT6aSsrIyFCxdiMplQq9XMnTuXhoZGFAoFnZ2dlJSUcOpUI7FYbMJ4pmTT3d3Ns88+g1yu4Oc//zk1NTVs2rSJ9vZ2du7cSXFxSdokrFAocDodvPLKK/z3f/8Ok8lIXl4+PT09OBzJgbdGo6a9vf2KkP9Vo8BSdvvy8nLq6+spKSkZWVeREYmEAYjHk5VHpVKj0+lQq1UkEgkSiQRyuRypVIJUKsFsNpGXl48ogtlsxmg0sHjxEpTK5PUSiYShoSFCoRDBYHI9J5FIrpOo1Zp0Es38/HykUgmDg4P4fH5CoRByuRytVosgCGRlZZGZmUk4HP5I3z4jr5y7lt3Cuyd20NzfxtpZK1DI5ETjsfT6FoAv5CeeSFDXcwqr28Z1c9aQa8ziK9c/yI3z1uENeglFQ+xuqOHZXS8y5HV8JF5jkUgkOHmylhkzZvDFL36RWCxKa2sbUqmM+++/n/nz53Pw4EGGh4cJBAKsW7eOn/zkp3znO9+hrq6O4uJifvCDH1BRUY5Wq6Wuro433nidp59+mnA4MqFcU2UWDAZ57bXXWLNmDSUlJaxYsZL/+I+f8k//9E+0tLTQ29uL3+9HoVAgCAJKpZLPfvZz/OM//hMej4v8/Fy2bt3KM888w4EDBya8Q0gNMrq6uti9eze33XYbra0tWK1Wtm9/j6amJp599jn6+vrwer3MnFnND3/47zz66A9wOJyIYoLvfe+fufnmjQSDQTweL2+//RaPPfYLnE7nhHL9OEEQBKLRKK+99hrz589jzpw53HvvfRQWFvHYY49RVVVFQUEBgUAAiURCNJpcG0skEmi1Wh566Is88sgjOBwOTCYjr7/+Gk888QQNDQ3AxA1iRFGkr68Pvd7AD3/4QxYvXsy+ffvweDzcdNNN3H33XdTV1WGz2fB6vZSXl/Ov//ooP/zhDwmHw7jdHr797W/z6U/fh8fjRRRh8+bNPPbYL7BarRPK9WJwVZgQIdnRSCTJxJXhcIienh6USgXz5s1neHiYsrJpGAxGdDodCkUpVusAZrNlpONJLtKWlpah0+lH/tcxPDycFqLTOczMmdXY7XbKy8sxmUzE4zGMRgM6nQ6dLhmvMDMzE4CKigrMZvPIew1IJBKk0lzy8/MZGBjAaDSRn58/sniuuujvFgQBhVTO7OJqdtS/zwPXfprKvAraC7p568i7nOioQ6tMmj9/8Nf/5NOr7+TdEztYUbmUfHMyAag74GFnQw0bF1zP8c46bDE70VhkwiuuIAgYjQb6+vrZs2cP8XiCQCDAT37yE0wmE11dXcyaNYtf/OIXTJs2DavVyqlTp5BIJJSUlKDT6Th16hQtLS088MCDPP300wAjg4eJb2SiKHLo0CGcTiff+MY3kEgkHDx4kD179qDTacnNzaOlpYUnnniCRx55BKlUhkSSXM986623qK6eSW5uLr29vSQS4oSOvkcjGo3yxhtvMHPmTBYuXIhcLmfjxpvp6uriBz/4AY888k127HiPvr5+SktLeeONN8jOzkajUZOTk4vD4WDbtu18+tOfYdu2bYTDYeLx2Ei5Xjqcq75dSnOWKIp0dHTQ0tLM//7f30epVJKfn8+MGTNobW3h4Ye/yubNm+nv72PJkqVs3bqVH/7w3/H5fMyYMYNYLMYbb7zO6tWrAOjt7ZtwiwYky6SgoIB4PM7Pf/5zenq6ueeee3j++efp7u5GFEUsFgvbtm2joaGBJUuW8Oc//3mk74GioiK8Xi+bN7/FnXfeyYkTx0dmmNFLLv9xv0+83HPAi4DH48FoNNJj68NgMACglCpQSMf3jEpVjFgsll4PiY9E7xYEgUQicUba7LHHUs8ZfczhsLN79x50Oh05OTnMmjULqVSCIEhGnhEnlfo85f2V8lAb/cyUqUEQkp5sSc8mYSQ6lJA2WYx+t6ejib7tr1J+75dR6E0fGkpKFEXsvmH6h61U5pWjlCkIRcPU9Z7C7Xczq7AKg0ZP80A7BZZc+ocHmZZdgn5knWvY72bI62BadgmeoJeTXQ0UZeRTll2MRJgYr6lUmQQCAY4ePcrg4CAzZ86koqKCvr5eTpw4SVZWFnPnzqWzsxOdTofRmFzLicViLF68eMQ5pwuA4uIiOjo6OXXqFIsWLUqvL0y000lbWxvRaJQZM2YgkUjw+bwcPnyEYDDAokWLgaQJp7q6mubmZoqLi9FqtTQ1NTFt2jTkcjkHDx5EKpWwaNFiVCrVuXmO1GdPWwN9O16n4tNfQ2EwnZVnNBqlrq6OvLy809ZZgsEgjY2NVFVVMTg4SCAQoLS0lNrakwwN2Zk3bx6FhYXY7XbsdgczZkzH6XRy6NAhZsyYQXl5+aR4zZ3tOxKJBD6fL912U5BIJOh0unTbvlR8bDYbAwMDzJo1C5lMRjwep7GxAa1WR3FxMV1dXQSDQSoqKmhsbKSzs5NZs2ZSXl5BOBympaWFiooKRFFk//796PV6FixYMKEenqly6+rq4uTJk+Tn5zNnzhwCgQCHDx9CFGHhwoWEQiGcTmeaa19fH3PmzKG0tBSPx0Nvby8zZlTi9/s4cOAARUVFVFdXX7T8RVEkEo8QjkdPO66SKQn5gxiNRtxud7p/PxuuCgUGHyixlMJICVY6sj6U+n308VTRjL1+NMLhMIlEAqVSmV6bmSxcrAIDSKS+fdR1CTEBIggSAQGBhJhAECSIYgKJ8EEW6lS5pV1txaSyFSYhBl+qnFNySr1/tIJP/S4IQrozS8lltEdo6r7UcyaD63geqClOqfowdpCSGiClzqdGsued+fsCFFjq/aPfPdrzNcVjdNsYW/6pa8fWhcko0w/7BqfTye9+91v6+/tHHQeLxcyXv/yVCR+knIsPcFr9GlvO5yrTs9WByaqno9sQfHg9nWyuE6XArhoTIiQ9rI4ePcL1199AR0cHvb29zJs3j8bGRrxeL5WVlUQiEbq6usjPz0epVNLX18e6devSI/3ly5efVmFH70u63B45HwaJIMCYPWwSQYIg+aCD+kBpfaCIx+ugpZOUGiH1nvGU0ejR9eiBglQqYfQsd/Rzxl47GVzH2xc4erY8WvGP5jLe75NVf8aW6Wi+owdkqfePV24fdu5SwWQy8bd/+2VisSipTxCE5DcYDMZLyiVVHmPr6lhZjif7VJ0Z3Y9M5kx2dFsYK/tz1dMUJBJJetfnlZTh46pw4kg1Wo/Hw759++nq6uLNN9/E4/Hw1ltvYTAYmDt3LgA1NTXo9XoUCgV2u52amho6Ozt58803z9hLNHoUeiUrrxRS5eBwOGhtbcHn853RocViUWpPnsDpHN9BI2UCneyJe8r9t6enm66uzrSX3Oj3ejxujh8/Nq73XmqT7mRzHe3M0d7eytDQYPp46pwoinR1dtDS0jTuukE8Hk+Pei8FV7fbRVtbKx6P64x3er0ejh8/mt7kOvb+ePzSyH8sUh2wyWQiMzPp3JT6MZst6c74crRDUUxuTO/s7MBqHTijfOLxOPX1JxkcPNPpIbW0canaFCT3L7a1teJ2u894r8/n5fjxY4RCoTPuT4gi0UvE9Xxx1czAotEoDoedoqJCTpw4gUajIRKJ4HQ6KSkpYffuXWRmZjJv3jzee287N9xwA+FwGIvFQkNDAxkZFgwGw8dCUX0YXK5hdry3lcLCIhob6thw480olUq8Xg89PT0YDAZa21qwO+zk5xdQXFxCX18vsWgUvcHA/v17WbZsJfn5BcDkhmhqOtVIW1srSpUSu32IhQuTa0oDA/24XMPI5XKOHj1MOBymqCi5ttTT3Y3eYMDlGmagv481a9eh0ejOWL+cSMTjcfbs3olGo2HQNsiqVWvIycklGo3Q3dVFQhQZHnbS1dmB1+tl+vQZ+Hw+7PYhsrNzOHbsCJmZWcyfv3DSE7AmNyLvwWg00NXZweo165DJZAwN2bDbh9BotBw7doRwOExhQSEmk5nuni7UajXBYJC2tlbWrr32ks944MwZ9pWAVF2tqzuJw24nGo2yfMVKLJYMfD4fvb3dqFRqmpqa6O7upqSklGll5fQP9BMKBTGbLdTUvM/ixUspLi6Z9O8Kh8PU7N2N2Wyhu7uT1avXIZVKsdkGcTjsGAxGThw/SiQcIi8/H5PJTE9P8htCoRCtrc2sXXstRqNpUnmeL64aBeZwOHA4HJSWlmK32xFFEZ1Oi0wmo729HZst2XgdDiclJaU0Np7CYrGQkZFBKBSaNA+xSw27fYhwOExxSSn1DXV0d3USiUZwOOz09vaycMEipBIJiXicQwf3MzDQTzAYQEwkkCsU+Hy+dEiZyUQikaCntwej0YhOr6e9vQ2tRotUJuXE8WPo9HqKi4oRBAGvx8OhQwdQKJSYzWZOnjyOwWgkHo8jk018GKmxCAYD9A/0s3799QzaBmlvT26Il0ikHDq4n+kzKlHIFSQSIs1Np3ANDzM87KSoqIS9e/cQi0bJzyu4JKY5qVSKmEiwr6aGxUuW0tXZkYz8cqoBiURCZWUVgiDg83rZu3cPRpM5WaYnjmM0mYhFY5ekTD9uUCqV1NaeICcnF6/XS19fH36fj46ONqpnzkIiCEglUg4fPoTH48blcgHQ1tqK1+NJb7OYbCQHSALv793NokVL6OxoJxqL0dzUiEQiZdbsOQgSAa/PR/v772MwGDBbLJw8eQKL2UI0GkWhuHKSp141CkypVDJr1iyys7OJxeJYrVYyMjJYsmQpNpuNG2+8kczMTLq6upBKZajVyXBRxpGO0GodID8//3J/xkeGRqNFFEXcbjcKuRK9wZBey7MPDXHqVEP6uwcHrXi9HmRSGRqtjkgkPNLJTr6pRiKRYDAYcLtcCBIJBoMRk9mMIAhMK6+gob4OmUyGVCrFaDIx7BomFAqRm5uLVCZDECRIJJfGzCGXK9BqNLiGh4lGo5jN5vQMpaCwkObmUxQWFqPRqEkkRAKBAIFAALVGk1yfksnSa5GTjUAgQDwRZ82adXR0tFFeXoFCoaSkuJQTJ47R2dGBRCJBrzdgtQ7g8bjJzMxEKpN9sL4zSTEwP66Ix+M4nU6Wr1hJZ2cnwWAAi8WCVqtlcNDKqVONSKXSpKfkkBSf10c0GsVkNOEMO5GMmD/HW2+eaPj9fiKRCKtXr6Ojo52K8hlodXqKS0qpq6uls7MdQZBgNBoZGOjH6/WSlZ2NXC5HIpUiucKWS64KBSYIAhaLBfNIBwic9vto76WZM2emfx+NjIyMj81a14chJyeX+QsWYrcPsWp10tQF0NPdRW5ePiaTCYfDjlyhIDc3j7KyaQwM9COTy5kzZx71dSdxOu2YzabTnD0mEqnGPGfOPBrq60iICebMmZfeW2ezDTJ9RiVmsxmJIEGpVJKbk0tefgG9vT3MnjUHo8lEU1PjyCZi5blf+hGgVCpZuWoN7W2tVFfPpLx8OnK5HLfbhcFgZN68TKRSGW73MDKZHJ1OR2FREW73MEuXLmN42InP5yMWi0366Faj0VBRMR2v18vixUspLCwkkRBxOuzMqKzCYslAKpOi0WooLCyiuLiE3t4eqiqryc7JofbkSXx+L2rN5M/CPy6QSiWUl1fQ1dVJdfVMSkunoVQqGejvJzsnB4PBgMvlQqvVkV+QT2lJGYO25Frp8uUraWluwuVykZ2dM+n9i1arZfqMGXjcHhYtWkJhURGJRAKHY4iKiulkZGQgiqBUKCgsLKSoqITenm5mTK8kJzeX2pMn8Hq9qFRXhvyvGjf6Txou1I0+hdHbCca6VI92lR77P3CGK/hkl3WSUwJRPN0z6lxcx3NVn0yuo8sUOM27bPR+wtFNbTTX1Lnz9kS7QDf6s3H9UPmT3Po9XpleTk/EKxWjwyqNJ/9kGSbd/i9/m/ro7f+jyn/KjX4KF4Wx7r3jLYx/2DWXQiGMRnJT+On8Pozj5eA63rvHchjv/OVwShivvMZ2Zun/x+E2pbjGx9mUz5lu9Zz29+hrLgUuSP4f0qauFHwkNj/+8Y8RBIFvfetb6WOhUIivf/3rZGRkoNPpuOuuuxgcHDztvu7ubjZt2oRGoyE7O5vvfve7nxgniY8Dxhvtjd0ScK6fS83z48j1QnleDq5XMs+PE6bkf3lw0Qrs0KFD/PrXv07vn0rh29/+Nm+88QYvvfQSu3btor+/nzvvvDN9Ph6Ps2nTJiKRCDU1NTz33HM8++yzfP/737/4r5jCFKYwhSlcdbgoBebz+fjc5z7H008/nU5qBuB2u/nd737Hf/3Xf7F+/XoWLVrEM888Q01NDfv37wfg3XffpaGhgeeff5758+ezceNGfvCDH/D444+nN6ROYQpTmMIUpnAuXNQa2Ne//nU2bdrE9ddfz7/927+ljx85coRoNMr111+fPlZVVUVxcTH79u1j2bJl7Nu3jzlz5pCTk5O+5sYbb+Thhx+mvr6eBQsWnPG+cDh8WkoRj8dzXjw/hv4pFwZRHFnQh8mItj6FjwFGZP+Jr+tT+NhjMkyPF6zAXnzxRY4ePcqhQ4fOOJdK/mgymU47npOTk047YrVaT1NeqfOpc+PhRz/6Ef/6r/96QTyT4V1CI7O6T1rjFgiEw0QVSnyBALJJcmefwpWNQCQyUgf8yKb2Zk3hCoZCoUSpVE24ErsgBdbT08Pf/d3fsXXr1nTah0uB733ve3znO99J/+3xeCgqKjrnfYKQ8vr5JDbu5Owr+Y2fxO+bwnlhxOV9qg5M4UrGZNXPC1JgR44cwWazsXDhwvSxeDzO7t27+dWvfsWWLVuIRCK4XK7TZmGDg4PpzcK5ubkcPHjwtOemvBRH5yoaDaVSeVrU9/OBIAgoFCoUikunaC8lRKUKeSyKRqNFodWf9z6wKXxCIIokFArksQhajRa57sP3y0xhCpcbl92EeN1111FbW3vasS984QtUVVXxj//4jxQVFSGXy9m+fTt33XUXQDqI5fLlywFYvnw5//7v/47NZiM7OxuArVu3YjAY0lEwJgpXw6g0Pfq+Cr51CiMQxTFG8SvLtXkKU7hUuCAFptfrmT179mnHtFotGRkZ6eNf/OIX+c53voPFkoze/s1vfpPly5ezbNkyADZs2MDMmTO5//77+elPf4rVauVf/uVf+PrXv37Bs6wpTGEKU5jC1YsJj8Tx85//HIlEwl133UU4HObGG2/kiSeeSJ+XSqW8+eabPPzwwyxfvhytVsuDDz7Io48+OtFUpjCFKUxhCp9gfGQFtnPnztP+VqlUPP744zz++ONnvaekpIS33nrro756ClOYwhSmcBXjqouFeClSFlxpGG+PUCoG2ui/z7hWEEAkHdh15Gln3DPZXMdiLPfzuX6ice73C+llyfPlOtl18lw8LqRcL0X7OR++F3LdRONC3nullOvo/u98+oWz4UrpP68aBTZWKJer0l9qiOn/R6JNjyz4i2POJ3dDj4pMzQdhWhJi8n7JqA4jVdEnqpxS8vjg/9Q2COGM6NmjZTc6kvd4f4+npCcKozmfyS1ZnqN5jL0uFYF+snmOx3Vs/R997HKW6dn4pn4/Wz1IHb9UXEdnHxhdV0dzGVvWYwfPo7MljCePicaZbezMujr6u0BEECSXvK5eCK4aBQbJdOqDg1bkcgXZ2dlnRFa22Wz4fF5KS8vOP7XFlQ5RJBSN8F79Ho511JJryubWxTeRqbfgC/l56+hWirMKuaZiIRJBgifo4+1j22i1drC4fD7XTF/Ee/XvY3MNccuiDehVWvY2HeKa6QvJ0JnP/f4LQCwW4+233+LgwUMIAhiNJm655RZ2795Nc3Mzq1at4qabbkKpVCKKIkePHuWVV15Br9fx6U9/hng8zp///GdKS0u57bbbaG5uxmazcd111yGVSieMZypNxubNb3Lo0CFEEUwmE9dccw27du0iHA5hMpm4//4HyMrKYnh4mL/+9a80Njaydu1aVq1axebNmxkYGODTn/40Op2WHTt2sn79eiwWy4QODFJ8RVGktbWVmpoaNm7cyO7duzlx4gQAmZmZ3H///RiNRg4dOsRrr72KxZLBfffdRzgc5qWXXqKsrCxdpna7nXXr1o1k9514pDrIjo4O3nzzTW6++WZyc3N59dVXOXbsGGVlZdx3331kZmbi8/nYunUrkUiEa65Zyl//+j9YLGbuvvse+vv7aW5u5qabbpqwPGspbi6Xi+ef/wM2mw1RhNmzZ3PHHXfQ29vLm2++yac+9SnKysrS18fjcd5//31aWlq49957OXHiBO+9t531669jyZIl7Nq1i/z8PGbPnjOpyquvr493332XDRs2UFtbS03NXkQRDAYDn/vc5/B4PLz00ktEIhGkUgnXXnstQ0N2mpubuOeee8nJyWHr1q2sWrWKnJycCa+rF4qrQoGJokgoFOKvf/0rCoUCl8vFddddh0KhIBwOU1xcjNvtZs+ePbS3t/HFL34Jh8OB2WwmMzPzY63IRERaBzt47eBb3LpkI7vqa9ihfp9bFt7Aa4ff5tX9m7lxwXqWTJuPIBHYVruLg63HWDtzOa8c3Iwr4OFUXzO5phzePbkTmSSpCLRKzYRzlUgklJaWIQgCBw8e4tChQ5SXl9PZ2cHs2bP57//+b6ZNm8bs2bMZGhril7/8JYsWLaSnp49nnnkGszmZ8HLfvhoyMjLYunUr99xzz6SkgBAEgdLSUgRBQk1NDQcPHsBsNlNbe5Lbb7+D7OxsNBoNsViMV199hX379nHbbbfxhz/8gYGBAWpra8nKymTz5jdJJETk8mSiy9SzJxo2m43/+q//Yu7cuWg0GsrLy1GplOzcuYvjx49x//33Y7fb+eUvf8myZdfQ0dHJ00//BoPBgFKp5P3392AwGHjvvff49Kc/PelpNYaGhnjyySepqalJy/uVV17m/vsf4K9//SvZ2dncddddvPXWW+l64fG4cTgc9PX1kZmZxYED+7nuuusmRdEqlUpmz56D0+nkhRdewGg04nDYeeKJJ6ip2cu8efNG6kdyVnP8+HF+9atfEo1GWblyJa+++ioVFRW8/PLLOJ0O9u7dy7e//Z1J7Ws8Hg+//OUv0el06HQ6iouLicViHD16lH379vG5z30Ok8nE4sWLsVqt/OlPfyQjI5O6ujpmzJjBa6+9islkxuv1oNdvBC7/DOyqiEEkiiLd3d3U1tayfv167rvvPoaGhnjttdd4/fXXeOWVV/jzn18kEPATjyfYsmULW7Zs4fnnn8fn832s48wJCFh0JpRyJQeaD+Pyu8g1ZbO36RCdtm4Wls8jHo8TS8RAAH84gElroDirCLlUjkapRqVQ023vxRcO0DdsZXZRFb6QP22WnBCeQjJJ3uzZs7nmmmUMDQ1x5513cOONN3Lddddx8OBBVCpVeoO8z+cjEolwww0bWLduLUNDQxQVFdPd3Y1cLmfHjvcoKytFo9EQDAaBiYuNmTJRzZo1m2XLluFwOLjnnnvIzMxkcNDG5s2beeeddwgEAiQSCbxeLyaTiZKSYmQyKVqtFo1GTV9fH0NDdnp6epgzZw7Dw8NjTDgfHaIoEolE+OMf/8jJkyfZs2cPf/nLX6iurmbRosUMDzu5++67MZmSCQTj8Tg333wzq1evZnjYRVVVNc3NTSgUCnbt2sWMGTNQq9WnxSadSIiiiN/v5w9/+ANGo4GKigqCwWBa7tu3b8Pj8VBQUIAgCFx//fV87nOfQyqVUlExHafTSSKR4PDhQ5hMJjIyMvD7/elnf1SkZK9Wq1mzZg25ubnI5XJuuOEGnn/+jxiNRsrKphEKBYnH4+m+57nnnmXlylVotdoRrhUcP34MvV7Pzp07WbNmLcFg8p6JRspi8MYbr7N79y7q6mr57W9/S3FxMcuXL8dut3PHHbeTk5NDbm4u1113HYlEglmzZrNx40Zyc3Npbm4mFArT0NDAokWLcbvdE15XLwZXhQITBAGZTEY4HCYQCDA8PExDQwNyuZyiomJqa08yNGSnsLAQgNbWFvLyclm1ahVyufwys//ocPqGSYgJ5pfOJkNvodXawTvHttNt6+V4ey07at9n96kD+MNBVlcvRyqR8tNXf4mAwLLpi/jahi9w9/JbcXqH0SjV/LnmVZ549xmsrqEJ5yqKIjt27CCRiHPdddcRCoUwmcysW7eWeDxOb28vgUAg3TkkEgkSiQQA69at4//+339l7tx5iCK0tLTyn//5M55//vlJyze3fft2pFIJ69ZdS3V1Nf/8z//MP//zP9Pb28uJEyeIRqNs3HgzgiDwD//wD8hkcjZs2MD/+l//whe/+CXcbjc6nZbf/OY3/OQnP8Fms6XLYaLg8/k4fvwY/9//9/d86UtfYsuWLQwN2XjnnbcxGIysWLGSYDA0KlsviGICQYBrr72W73///zB37jwkEoFTpxr5+c9/zp/+9CcikciEd2CiKNLU1MR7723nxImT1Nae5LnnnmP37t1IpTKWLr0Gs9mE1WolFAqh1WpRKBQIgsCSJUt49NFHWbNmNYFAALvdwS9+8Ri/+c2vCYVCE8oTIBgM8sorr7BmzRrC4TA7d+6gtraW+vp6nnnmGerr6/B6vWzfvo3GxlPs3r2L2to6nn32Ge6++27+4R/+AblchtFo4rXXXuPHP/4xNTU1k6IYQqEQ+/cf4G/+5gt897v/wL59NfT29vD++3vw+/1s2HAj4XCYWCxGb2/vSDCKO5k2bRrf/e53+fKXv4zD4SAjI4NnnnmGH/3oR/T09ACXN2j6VaHAAIqKirj++uvZunUr27dvp7q6mng8jsfj4c4778JkMnLixAmUSiULFiyktrYOt9s9oWsnlwuxeIxINEIoFiUcjSARJHz7Uw/zv+7+e66pXMzy6iWUZRfz2Fu/JhQJsWnRBnJMWXzhus+SqbMAsPfUARZPm4dOqaE0u4h4Io435JuwMMmpRmC1Wtm8eTO33XY7JpOZmpoaHn/8V/T09BKJhOnr6+Xf/u3fGBgYQKfT8dxzz/Hyyy9TUFCARqPBbrdz8uQJbrvtNqLRKJWVVQwOWkkkJm5km+I6MDDA22+/za233obFYqGlpYU//elPvPHGG4RCIaLRKI8++iiJRJz77rsXg8HIN7/5TbKyspBIJLz77rusWrUKrVZHeXk50WiUQCAwYTxTUKlU5ObmsnPnLt59910yMizY7Q62bt3GnXfeQSAQ4Ac/eJTh4WHUajXPPPMMmze/RUlJKQqFgsHBQWprT3LzzZuIRmNUVVVhtQ5MyqBAEASqq6v51a8e5//+3//LnDlzueuuOyktLUlnpQiFQgwNDfHYY79g584dI7OipAna6/VSU1PDHXfcSTQaYebMmdjt9gmd2aTkX19fh9VqZdOmm5k9ezaPP/4E//t//29mzZrJ3Xffg8fj4ac//Snr1l3Lb37zax5++GGqq6v47Gc/i9Fo5Nix40gkUhYuXIhWqyU3NxerdWBSFIJcLic/P5+DBw/yxhtvoFKpCYXCvP76G9xyyy3I5XJ++MMfcvjwYV5//XXKyspYtGgxEokEiUTC9u3bWbx4ERaLmdLSEiQSAZfLNeE8LxSCeLnngBcBj8eD0Wikx9aHwZCMAaeUKlBI5ePaZEcvpMZisfSMLNUAZTJZekSfMmXFYjFkMllagV2orXeyPXU8HU30bX+V8nu/jEJvOmsoKVEUicSjHG47TlNfC5mGTFZXXYNRa0AURRr6mhEQKM4s4EDrUeYUV2N1DRGLx5hfMgupRIrT76J5oJ15xTPxBL3srK+hMCOPpRULkUtlE/J9qfLq7e2lrq6W1avXoNVq8fl87Nixg46ODubMmc3MmbM4dOgQixYtwufzsmPHTtRqNRs2bCA7O5v6+noAKisrOXjwIPX19axffy3l5RUTlk02xbWnp4eGhgZWr16NRqMhEAjw3nvb6ezsZNGixUyfXsG+fftZunQpXV1dxONxlixZgkwmw+FwUF9fx5IlS7DbHWzZ8g6lpWWsXbs2Pes/K9eREbqnrYG+Ha9T8emvoTCYzspVFEX6+vrYtm0b8Xic9evXI4oibW2trF69mkgkyvvvv8+SJUtwOOzs2LETo9HIhg0bRtb1apHL5ekyra2t5YYbrqesbNqkZOhNcY5Go+zbt4/y8nIyMizs3r2HxsZGiouLWbVqFSdPnqSoqAipVMrAQD/Ll6+gvb0dn8/LnDlzqa2t5dChQ6xcuZKZM2dOmGNWit/JkyeJRCIsWrQIiUSSNtfu3fs+lZVVJBIJ6uvrWbNmDSqViuHhYY4fP8by5SuQSqUcOnSQadPKMRqNbNmyhVAoxKZNmzAajcDE9Rup+jo0NMS2bdtwu12sWbMGo9HEyZMnWb16NVKplF27dlFZWUlbWysVFdMpKSlBEJKK6vjx4yxevBi/388777xDdnY269evT0dPupj+MRKPEI5HTzuukikJ+YMYjUmTdqp/PxuuCgUG47vlXgjGcz3+sGelzFtSqfSyKrAUF1EUSYhJF2PJqPQrqah6AgIJMekqP/pYCqlzAPFE0rwkESbOU3Osa/xoJ4FEInGG+3Sqw0iZD1MDjdGuyinzYkoGk+HyP9q9OMU1xV8ikaR/T/Ece23qO+LxePqec/K8AAU2mm9qFjLaHTr1+2guKVf00edS5ZeSRYrnZC7ip7iM5jGa29j2N7qNpn6Px+MTLv/R3FLPHe1CP7osR5fVeN+T+j0lm4sdMJ8P39Ht5Wx1YHT7SvEYyzsej6dlcLE8J0qBXTVeiMkRZxt9fX3MnDmT2tpaysrK0p5Co69NYfS+ktHnbDYbtbUn0el0KJUq5syZk24kqesbGxsYHBxk7dp1l90lP/VuqXCmOXS0kkopqNHH0hV6FH9punJPPMexCgE44+/UtYIgnGbiHVvGKYUw3rmPynW0Qj3bO0dzH31svE5islzSR79vrDl8NPfRXMZeN973jL1/sjD23ed6/1iFlirXyeA6tpMf7/jYvuNsZflh9XgikO4DzrMOnO3c6Gdcbg9EuEoUWAo9PT10d3dRVVXF8ePHAPB6velGa7fbiY2kKJFKpeTm5tLe3o5UKkUmkyGKIrFYDJPJxMmTtWzcuJHOzk4EQSAQCIx4oQ2O2OmDXElz29GNLBAIEAj40esNZwRQjsfj9PR0kZGRhV6vP2PWefrsaPIaWmrU5/V4SIgiRqPxDEUWDAawWgcoKipJyyeF8WZHk8UTIBqN4vG4UalUaDTa02aLoigyNGQjFouRl5d/xrNEMTHyzMlbkh7NNxgM4Pf7MRiMaQeI0bO07u4uMjOT8h+LsbPjyUJa/l4PiUQCg8F4RucbCPgZtFopLCo+w9nqUsk/9a6U/JVKVXo7xOjzQ0M24vE4ubl5Z3T8o2eNl0IpBINB/H5fWv6j8YH8M9Hrz5z9XCr5ny+uGgWWSCQYGhoiFouzf/9+EgmR4WEnLpeL3t4eKiqm09bWRjgcIiMjk3g8xqlTycrocrnSHk+hUIiKigpEMUFdXS0FBYWcOnUKpVLJoUOHmD59Ot3d3Wi1WubOnXtFCRuSCnvnju2o1SpEEdauW49CoRhZGLehVqs5cuQwRYVFFBWXkJWVhcPuIJ6Io1SqOH78KHPnziMzMwuY3IgBnZ0dnDxxHLlcTtm0aVRXzwQE3G43Ho8LUYTdu3ZyzbLl5OXmo1KrGBwcRKfT4nQ6GRwcZNGixahU6gnnOBqJRIKDB/bh8/sI+AOsW7cek9lMPB5naMiGKIp0dXVgsw0yZ8588vMLCIWCuFzDmM0ZNDbWYzAYmTGjclKdhpIu6j52vLcdtVqNiMjatdcilyflb7MNolKqOHhgP6VlZRQVFZOdnY3D4SAej6NWqTl27Ciz58whKyuZCmky5d/d1cnxE0eRyeRMm1ZOdfUsANxuFx63GxHY8/5uli5dRm5uHmq1msFBK1qtFpdrGOvAAAsXLUGtnjz5p8yUo+W/dl1yQ3o8HmfINkhCFOnq6sQ2aGXuvAUj8g+NyN9CY2M9RqOR6dMrJ23JIVWmPq+X7e9tRafVgiBhzZq1Z8j/wP4aSsumUVJcQlZ2zogTTBS1RsPJ48eZOWs22dmXfxMzXEUKzO/309/fz3XXXYfNZsNoNDI4OIhKpRqJ0DGYbqyimLS1+/0+vF4PJpMZn8+HTqcFoLu7i9mz55CdnU0wGKSvr5f58xek93xAspFptdrL+cnjwjZoxePxsHDhIrZseZuBgX4A+vp6cNgdzKisRCaV0j/QT0NjPVVVM7EP2YjGYhgMBtpaW6murp50nolEgra2VswWCzqdjlONDeh0eiQSCUcOH0Kv15Odk0M0FuXUqUZaW1rS5W2zDaLT6/H5fCxYsPAcb/roCAQCtLW1smbtOg4dOkhbeyuZmZlEozGOHT1Madk0JBIpLpeLnTu2M31GJQ67HZ1eR8B/HI/HQ3nF9EvijjxkszHsGmbuvPls3foOfX19CIJAX18vdvsQ5dMqkMtl2Aat1NfXMWfOXGy2QSLhCEajkdbWFiqrqiadZyIRp7WtFYs5A51ePyJ/HRKJlKNHD4947eUTi0VpbKynrbUFnV5HIp5gwGrFYNDj9XqZv2DRpHMNBpPyX71mHUeOHKKzsx2320U0GuXYsSOUTytHJpXi9frYufM9Zkyfgd0+hE6nJxAI4PN5qZg+45LI3+Gw4/V6WLhwMVvffYfeacm6OTDQx+DgYDoKkW3QyqnGBubOnU9vXw/xWAyDwUhHRxsVI3X1cisvuIoUWCQSYcaM6VgsFgKBALNmzUIqlSCXywmFwuj1ekwmE6FQiGAwSCwWxePxoFAoUavVqNVqlEoFAwPWkeNy3G435eXTMBhuori4GIVCgVQqTUf4ONcC5OWAXC5HJEEoHEYqlRKJRAgEAlgsGXR1dtHR3o4gCBQWFhEKBrHZBlEqVRiMJnxe78gufsOkV15BEFAoFEQiEaKRCAqFEq/Xg1QqJSsri4bGeqQyGSqVisLCIrq6OpPeZ3PnMWS3ISZE9Ho9CkXSRDqZDU4qlSKTywmHw2kTi2t4GIPRhFqj4dSpBoqLSrBYMkjEEzidDrxeD1XV1Zw4cQyVSo3JaLokWzZkcjmMeMtJBAnhUIhQOITFkkFHRzsdne2AQGFRMR6PhyHbIAqFEq1Wh8/nRavTotdPvvxBQDliGZArFMgVCnw+HxKJhIyMTBrq65BKpWn59/R047P6mD17DoO2QeKxODqdLm0im0z5SyTSkX2mIeLx2IiL+TBGowm9Xk9jYwMlpaVkZGQQjUVxuVx4fV4qq6o5ceI4SpUKo/FME+lkQCqTkUiIhMNhJFIpkUiUUNCDwWCkpaWF7u4uBEFCQWERHo8H29AgSoUStcmE2+VGq9Wh0+mvCOUFV4kCEwSBzMxMbrppIxKJhLy8vPTxs2E8N3hBECgpKT2tMUgkEgoLk3+vXLnytLWG8RZDLzfy8guYXlFJR0c7CxctpqxsGhKJhNbWFvILCtBqtdjtQ4iJBBqNhoqK6XR3dyEmEsyZO48Tx48x0N83Mhqe3PWF2bPncPz4UfwBP0uWXkNWVjaxWAyP28P0ihkYjUaGh5PrdFqNhpxp5fT0dFNcXIJlxDTn8bixWDImhWcKKpWKJYuX0t3dRUFBIdXVM1EqVTidDoxGEyaTGZlURigUQqqUYjAYMRpN9HR3M3/+IoadDgYHrUwrr5iwmH1nQ3ZODtNnVNLZ0c7iJUupmD4dQZDQ3t5GYWEhWk1S/vFYHJ1eT0lpGb09PYgyKXPnzufEiWP09/ei01VNqnlcIpEwc9Zsjh8/ht/vY/GiJeTk5hGNRvH5fEyfMQODwYjH7UYA1GoN5eX59PR0U1pahsViob6+Do/HTUZG5qTxBFCr1SxZcg2dnR3k5RVQOaMapUrF8LATvd6AXm9ALpcRVUZRKBXo9QYMBgM93d3MnTsfj9vFwEA/paXTJk3+qXXO7OwcKisr6epsZ/HipUwfmfl1dLRTXFyS3kspiiI6nW4kuk0X0WiUOXPnUXvyOAPWfgzjrElfDlw1bvSfNFyIG/1oJG32MaLRKAqF8jQX2nA4jFwuH9n0m6zwMpmUeDwBiMjlCqLRKBJBSI7kmfxo5KloDylnA4BYNEosHkMmk4+4nwuICRGpTEY0GkEmk4/MLsPI5YpJ9wJNOQyEw2FkMhkymQxBgERCJBIJI4xsN0gk4iML9cm/Y7EYCoUi7RykVCrPj+cFutGP5gnJoMmx2IfLP8lXHNkjmdwvmZa/RDLyjZNbpnAW+ceiRKMx5HJ52qUb8QP5y+VyBEFCJBJJW0UmE+PJP3U8Kf+kzJPOOh+43X8g/wSxWPz85f8ReELSUSMajXyI/BPp+pvaIzu6/QuCMFLGF891yo1+CheFpMelDKn0dPdiiUQyarH79Goxuv2PHiFOZmNLjRjHvk8URWRyeVqBjnU/l0o/WLBXKlWTznP081Uq1eijCMIHHJI43VNuNPdLYT5Kl9+oTvb85P8Bt8meIY7lOq78ZXJksnPL/3R5TC7Xse9LHTtd/qfjA+6SdHucTIzeTpEqp/HlfzrGk/+VMlGYUmBXIc61f+ZKwceFJ3x8uJ6N08eF65XIEz4+XD9O8j8fXH4j5hSmMIUpTGEKF4EpBTaFKUxhClP4WGJKgU1hClOYwhQ+lvjEroElPbuixGMTnyDuSkAkFiUuSeY4S8iDTEZYpylcyRCJxGLEpTLCkTCJkaSdU5jClQhp2jt3YvupT6wCg6S7cDg88YnsrgREYnFicjnhSIR4KDSlv642iBBJxInLFck6IJtSYFO4cqFCPSkBqz/RCkylUk96HLzLBcnwEO5QAINOh9xgvtx0pnCpIYoIdiXuoD9ZB/Smy81oClO45PjEKrCPq1voBUEQQBhJfnI1fO8UkhDFDzJhCwKpzbFTmMLVhiknjilMYQpTmMLHElMKbApTmMIUpvCxxJQCm8IUpjCFKXws8YldA5vCh+O0GM6p9ZNxIvAD6fWWS73Kcj5xpsdmRh57/FLgQniOd8+lXr8ar6zOVn6Xk+dojMfjfI9dKox994dxuVJ4ng1XQnmeD64qBTZRndxkCPVcz5zId6aelXQFEBDSfyf/FUYi0TMS5Tt93wS8+2IwOuX62L9Hl8vY45erYzgbzyuhA0jLfpwyPBv/FPcr7RvOdvxyyf5sZTq27K6UBCBj5T3eudTvV4Lcx8NVocBSlSuVmiEVjftiK5Lf7ycWi2E0Gie0Mo6u7Bdz/nzfAdDrHOBAyxEAls1YRIbOwuG24/Q6B5hTXE11wXQkSAhEQpzoqidTb6EitxTES6PERFHE5/OxY8cOPB43giBQXFxCWVkZu3fvJhQKsnLlKqZPn44gCAwPD7Njxw58Ph+rV6+mtLQUmHyuqZTyhw4dorW1FUEQ0Ov1LF26lBMnTtDV1UlVVTXLli07LbK6y+WipqaGefPmUVBQcMnKFGBgYIAdO3YQDodZtWoV06ZNo66unkOHDlJWVsbKlStRqVQkEgmamprYv38/mZmZrFu3Dr1eD1y6gUyKs8/n48CBA8ycOZO8vDxEUaS3t5e6ujpWrFiRbot9fX3U19exbNlyjEbjJZE/gNvtZseOHQwNDbFgwQLmzZtHY2MDhw8fZtq0clasWHGa/N1uNzU1NcyaNYvi4mLg0rWrYDDI7t27GRoaQhAE8vJyWbFiJaFQiH379jNzZjUlJSVpnocPH6KiYnr62JWkzK4KBQYwPDzMU089hcmUzHx77733pvNEqdVqXC5XOkmjx+PBZDIRiUQIBoOYTCbC4TCRSDLXUFNTE06nk2XLlqVzZ+l0OtxudzoRnEKhwOVyIQhCOqeWRCJBIpEQDodRKBTIZLJ0ziKPx0N7eztz585NZssdlSwukUjQ09ODRCKhqqrqI6feCEbDPPPen5BIpQiCQF1PI8tmLGH7yd3kmbPZ01DD9+78FhadmWd2vsC24zu5a8VtlGUXI5NMftqPFGKxGFarlaEhG++//z7z5y8AwOfzYrFY2Lu3hp/+9Cfo9QZ++9vf0tjYSEZGBrt27eLHP/4xWVlZl2T0KIoiLtcwfX29tLS0YrfbGRoa4p133mHVqlU89thjI0kPl4zw9/HrXz/F//zPyzz66L+Sn59/yTqFaDTKk08+yfCwE4slg/379/GlL/0tP/vZzygvL+ftt98iEAiwadMmurq6+Ld/+wGlpaVs3bqV/v4+Hnroi8jl8nO/aII5/+Uvf+HkyZPMnTsXURRxOBz87Gc/4/jxYzz11K8xGAw0NDTwwx/+EKvVypNPPonRaLwk/ERR5LXXXuXtt99h0aJF/OQnP+FLX/oSzz77DCUlJbz55mZkMimrVq0GIBgM8t///d+8+OILfO97/0xRUdElVQqJRIKhoSF6e3s4dOgwBQUFzJ+/gD/+8Xn+8Ifn+cd//AeKiopwu9389Kc/ZefOHXzve/+cVmBXEq4aBaZUKkkkEmRmZnD8+HGOHDlCR0cHXq+XsrIy2tvb0Wg06HQ6otEIJSUltLa2EQwGyM/P5+TJWsxmM6IoUlpait/v54knnkAQBHw+LytWrKCpqQmbbYgHHniASCTC7t27CYdDKBQK/H4/VVXVDA8PEwqFCIfDaLUampqaue+++3C5XGzevBmv1zvCy4NOp2NoaIj8/AI6OjrIysqipKQEnU73kcpCAqiUyRE2gFypYUHpHExaIyc66lDIFUglMuRSOdfNWYvL7znNlHgpIAgCJpOJhx56iPr6empra7njjtvZuXMnPp8PQZCgUimRSKSEQiHa29tZv/5aqqqq+dGPfoTX6yErK+uS8JTJZGzYcCPLli3n0Uf/lWXLllFSUoJUKkEqlaJQKFCpVOnZ2htvvIHf72fWrJnE44m0heBSdGKCIKDVavB6vQiCgEqlpq+vD61WyyOPPMIzzzxDY2MDGzduZOj/Z+/N4+Qqq8T9p/a9a+mq7up9787WnZ0kZIEQIEBAlE0QAQVHBcQZHR11vs4wOjMygzqO+hsFFUFUEBAEBQKEQEjIQhaS7nR3et/3rn1f7/39UV1FEhJIh+4ESD2fTwipunXvue957z3ve97znjM5STKZ5NZbb+Pll1+mq6ubRCJxxgxYul3279/Pb3/7EHl5eTz//PN84hOf4NFHH8VsNlFQUDhVcBHy8vL40pe+xAMP/PKMu+nUag1KZaqQqkqlRKfTcfXVn2TlypV8//vfx+/3Z/T/wgsvMDExzsKFixAE4Yy6aFP613HTTTfR399PS0srV199NTt27KC/f4CFCxsyfVKlUnHzzZ8hEPB/aNyex3NORSEmkwleffVVCguLcDgcOBwOysrKUCgUKBRyurq6EASBlpZWGhubCAaD1Nc30NzcglQqoaGhgXA4jFwuIxQKkUjEqa2tAcDt9hCLxSkrKyM/P5+DBw9itVqx2WzIZDLcbg8ulwuPx0M8HqeiogKtVkdDQwMNDQ3k5uZSWlpCf38/breL6upqLrjgQgKBIEajkdLSUqqqqtBqtR+4HQRRRBBFfGE/gUgQURRRK5RIJVJCsRDReIxAJIhUKqUstwiVXMXZ6r7xeJznnnuORYsWUVc3h2RSIBgM4PG4EcVUdVm1Ws3GjRt58cXNfPvb36KwsACbLQ84c+4OiUTC7t27cLvdXH755YiiQDweZ2Jigng8TiKRyLjkfvvb39LR0UFr6xEef/wxmpubz4iMkBp9J5MCXq8Xj8eNICQRBCFTZVkmkyIIKcNRXV1NXV0d9957L0888SfOO++8M1bQMk0sFuPll1/iwgsv5O677+bFF1/k0Ucf5ZlnnqGtrY329jZ+85vfMDo6isVimZrNnNnXmiiKCIJAOBzB4XAgilBYWMgll1zCH//4R2w2K+eddx6CINDb28tvfvMburq6aG4+zJ/+9CcOHjx4RmVN/71584sUFRVitVp58MEH6O3tobm5haeeeorGxkMolUoqKirPuM6nwzljwOLxOIIgsGbNWpxOB8lkAq/Xi9frZWCgH6/Xh1KZGlkuWDAfm82KICTp7e1l0aJFiKJIMBgkGo3idDqJRMKIIkQiUZLJ1JS8v7+Pvr5eBgcHmDt3DkNDg4RCYdavv4iVK1dQUFDAsmVLGRkZIRQKZWZiAAaDAZfLjdFoxO124/F4ePPNHRQU2Dl48CBSqZSurk58Pt8HbotQPMKoe4x5xXUsKJnDiGuUlxpf542WncwprCGRTNAz0cdvX3+MMe/EVLDHmTVh6dH3oUOHaGlp4ROfuBqpVEp/fx/z5s1n+fLzGB0dZd++fTzwwAOsXbuWG264AavVxpe/fOcHnqVOR04Ah8PBM8/8hcsvv4L8/HwGB4cwmUysW7cOqVRCW1sbP/3pT/H5fPzwhz/k7ru/QmVlBStXrqKkpOSMyAoQjUbp6+tl3rx5nHfeCsbHx9HptLhcLZMVhQAAgLFJREFULrZs2UJLSyu5ubk8/fTT7N69i7vuupvy8nIuumgDGzduRDbldj5TSCQStFodPp+f8fEJEokEixcv4qc//Sm33nobxcXFrF+/nqamJv7whz9k1rnPJMlkkr6+PgoLC1i7di3hcJiuri7++7//mx07drB48WL6+/t54IEHGBkZ4b/+67/4+7//B2pqalm1amVmvfZMIYoinZ2d7Ny5i2uuuY7Kykruu++/+Id/+BrV1dWsXLmCWCzO//f//ZzJyQkE4cw//6fKOeNC1Ol0fPnLd6JSqZBKpYiiyCWXXJoppR0MBpFIJJk1qtzcXEKhELFYjJycHM4//3wUCgUrVqxAJpNlHhK5XM66det48803sdvtRCIREokkS5Yspby8HKlUjsVipqqqCkiV7q6ursm4NJPJJHK5nIqKCu6++250Oh3r1q3LXEMmkxGNRtFqtYRCIXQ63QduC7M2h8+suZbNB7ciiiI3rr6GirxS/uJ/kW0tu7h00XrmFNYw5BxFJpNTXzaXHI0B6RlevBUEAZfLxQ033EBNTQ1SqZQvfvGLPPbY4/T29nDHHXdgt9vp6uoikUjg8/n48pe/zJw5c5BIzmx6JafTyeLFi7j44ouRyWRcdtlljI+P8ec/P8W6detYuXIlzz33HDk5OcybN49oNMqVV15FfX09JpPpjMmq1+v5whf+jieeeIL+/j7uuOMLrF27Fp/PzyuvvEJ9/QKuvPJKdu7ciUQiJRgMUlxczDXXXJMJ4DiTKJVKPvOZz/C73z3Cjh3buf32z3P++atRKBS43W6Gh4dZtmwZPT09SKVSDAYD69dfeMbWvwAUCgU33HADjzzyCM899yyf+tQnKS4uRqVSUldXx549b5GYqophMBhoaGggHo8zMjJCdXU1FosFOLPeAofDwZVXXsnChQ2oVCoaGhqIRqMMDw9TXV09FQAjRS5XsHLlCsrKys+IbNNFIn5YnZvvgc/nw2g0MjgxTE5ODgAqmRKlTHHSTjDbtxkOhxkfH0ev15ObmzutzjjdyEKJRIKvt53hrc9SdcMXURpM08qF+EHa4sO2v+pkfFTkTDMteadmp77uVoZf/yvVN96FMsd0ij/9YLJ+GPcsvRdnMrrzg/BR6K8zKaMoisSSMaLJ+DGfq+UqIsEwRqMRr9ebeb+fjHNiBpZ2R01MTOBwONBoNNjtdhwOB36/H6PRSF5eHmNjY/h8PoqKigiHQ7jdqcjEoqIi5HI5TqeTsbExZDIZOTk5yOVyzGYzCoUCtVp9TJjp8fs+jv/s+M5wtkJTP0whse/Hh1XW99oI/GHjVDfYfhjkP9tterIX/fs9u9Ntw5kI4jlVWacj10eBc8KApWlpaaGjo4OCggIOHDhAOBxGKpUyOTlBbW0dfr+f3Nxc9u7di16vw+8PEIvFWL9+PXV1dXR1dbFnzx6WLVtGW1sbPT3d3HHHFwiHw4yNjVJcXMLg4AAWSy4+nw+NRpOJUlSpVESjMUwm0xnd93E8qUKfCcLhEFqtLrOmkX4AYrEYgUAAk8l0TCj/8Zyp/TWRcJhEMnlC12kwGEAQBAyG9x6lnQlZ02ukCoUClUqV+Tz9t9frRavVoFSqzpqcaVmSySShUBCtRotMLieVY+Ud/QeDAYzGD4/+k0ISrVb3LuMbDAYRhOR76v+DJCtI75mSSiXIZHLi8TgazbvLMwUCfqRSWSbAKuXO9pKTY0Qul+P3+5FKpSd1/59qm5/svtLPdDriOZkUkEhApVIf016CIODzeTEYDMhkqVd/OBwmFotiNJoQBAGv14NebzhppGnqmRNP2Z082/3knDFgqRdMgAULFmCxWNi8+UUSiQRyuYK8vDza2tq46qqrUKvVHD58mHA4hNFozMy2ILWpTxAEIpEI5eXlOBwOZDIZ27ZtQ61W097eQXFxMQcPbkWn0+FwOKY2TKfWunQ6PXl5eZkZ3dloA0EQ6OrsYO/ePcybv4AlS5YhlUqJRCJEIhECAT+vbd3CRRsuJjfXhkKhIBgMoFAo8ft9uF0uKquqUSqVs945w+Ewb721m57uLq7YdBV2e0FGj4Ig0NragmNyklXnr8ZoNJFIJIhEImjUavoH+tHpdBQUFGb2+80G6RfE5OQEu3a+CRK4/PJNqFRqEokEwWAAkPDqlpepra2jorISnU5PKBRCEJLIZDK6u7soL6/M9LPZbFdRFOnq6mDP7l3MX1DP0qXLkUiYCiiK4Pf7ef31V1m//mJyc61H6V9BIBDA5XJRWVl1hvQfYu9be+jp6eayyzdRUFB4rP5bmnG5nKw6fw05OTlTA7MwGo2G/v4+9HoDBQWFp70emkgk2Lr1FUwmExUVVbS1tbJk8TLkCjlarY5wOIwoCnR0tKPX6SkpLSMxFSz2/PPPcf6qNeTb7bz99n4UCiVLly4nEomg1WpRKBREIxEiU23+xrbXWL9+A5bcXORyBcFAAIVSid/nxe12U15RObV/VIFGo33XjM3tdvHiC3/jwvUbcDhSG5QrK6pQqpTIZHKi0QixWIzmw00sXLQYmSxVHbm/v4/m5iYuvngjcrmclza/wJq1F2Cx5CIIqXeWRCIhGAwgiiKtrc24XC5WrFhFTo4x88ypNWqGBgbQG/SYTGYikSh6vQ65/OTLOjPBOWPAwuEwg4ODFBYW0dXVRVlZOd3d3QhCksLCQrxeD7FYjOHhYSwWMw6Hg6VLl1FaWopWqyUajTI0NMSSJUuora2lqakJm81KMpnEZDLR2tqCXK5gxYrzOHKklfz8fBwOB8FggPXrL2L79jcoLy+noaH+A29E/iBIpVIKi4qwWm1IJVKCwQDJpMC+vXsQESksKCIai7J79y70eh25Fit+vx+vz4tOp8PpdFBUXHJGQmuVSiWVlVWMj40iTIV++/0+9u7dgzXXilqjweGY5Pm/Pcf8BfU4nU4EIYlcJmdkdJjqqhry8vJPeVT7QTAaTZSWlTE6Mkw0GiMSidDX18vQ0GDGiA4M9NPccphly5bT19tDMBTEYsml+fBhjEYTBoPhjOwDKigoIi8vb8o7ECCRiLNv31uIIpSVlRGNRtm9eydarZbc3FwCgQA+nw+D3sCkY4KioqIzon+FQkllVTVjY6MIySRer4dgMMiB/XsxmS3IZTIcDgcvPP9X5i+oZ3JigmQyiUwuY3x8jIqKKmy2vNPetyaXy6msrKKl5TC5Fismo5m9+/bgcbupr29g5843ybfbkSDBarMxMjKMy+2isqKKZDJJ0+FGYgf2UVRYRCKRYO/ePfi8XkxmM0uWLOPNHW8giiIlpWXEYjH27NmFRqsl15KLz+/H5/Wg0+mZdEwSDAUZGhykrLycBQsa3jUANpnMWG15DA0OoFAqUcgVvLlzO/FYnIrKKnbu3M6cOfOYmBincKKI7p4u/H4fVZXV+H0+Xtu6BbPFgkwuZ3JyksNNjUSjEZYsXY5Go2H3rjex2vJQKpU4Jif529+eY2HDQsbGxxAFAZlMztjYKBWVlUQiEYRkkvkLGiguLpnV/W3TerL/7d/+LTOaSf+ZM2dO5vtIJMLdd99Nbm4uer2ea6+9lvHx8WPOMTAwwKZNm9BqteTl5fHNb36TRCIxM3fzHvj9flQqNRMTExQWFlJbW4vdns9ll11OLBZj9eo1dHd3IYoiS5cuw2y2YLfb6e3t5a233iIQCCCTyTCZTGi1WrxeLzqdnkgkNbJZu3YdlZUVDA+PsHjxEiKRCBdccAErVqyktraWJUuWEolEkEhmbzZwKiQSqe0DSpWSru5OOjraGR4eRKFQ0NfbSyAYQKPWUFpahs/np6e3m+KSUuKxGPFYHGuu7YRulNkg5d6IIQLdPV0cOdJCJBIhmUwyPDJMIpEgLy+fXKuV0ZERXC4H1VU1TE5OoNXqyLfbz9hMN53uanxinL7ebtrb25BIpIwMD+GYnEztqSsvR6FQ0NPTjVKpwpprw+FwYDKZMpGIsz37Sru3NBotvb09dHZ2MDQ0hFKpor+/l0AggEaT1r+Prq4uiotLSSQSJIUkNlseGs0H34t4KkSjkSkXcoLu7i6OHGmd2n4SZXBggGQyid1ux5aXz/jYGE6ng8qqaiYmxtHr9NjtdhSK05sBpHVRUVFJLBqjs6uDgsJCdDo9wVCQQDCISq1i1arV5BiNJBJxVGo1sWgUt8eNUqGkfkEDgpAkFosRjUbo7enGXmAnLy8vte9OIae3r4fgVJuXTLV5Z1cnJSUlxGJxkoKAzWrDZDbjcEwyNjqaSUBwNAqFgvnz53P4cBOiCCaTCaVSicvtRCIBa66VhQsXISHlSlSr1fh9fmLxOGazhTlz5+F0OkAUmRgfw+P1UFFZhVarSy1/xGIMDvaTSCSwFxSQn5/P+Pg4bpeL6po6nE4Her0eu70AvU7PwMAAfr9v1oPnpv10z58/n1dfffWdExz1gvja177GCy+8wFNPPYXRaOQrX/kK11xzDTt37gRS+yU2bdqE3W5n165djI6Ocuutt6JQKPjBD34wA7dzYiQSCfn5+dx6660AmRD1uXPnIpPJmDt3LlKplAULFmRG63PmzEEqlaLX69FqtWg0Gj7zmc9k0kF96lOfQiKRIJPJuOKKK455SCQSCYIgZGZaEomE1atXIwhCpr3OlhFLuREaUas1lJWV01C/kKSQxOv1UlKaMlSiKBCLRpFKJBQUltDT04XVlofNaqOtrRWfz4vFkjvrsjqdk7S1tZJryaWkpJSSklJcLid5tjwiR73cgJSrQyqlu6eL2to5eH1ehoaGKC0tn/XZgiiKdHd34vf7KSkppaKyGq1WQ2dnB5VV1SBCIhEnFosSj8WwWfMYGh5CoZBTVzeH5ubDjI6OYDDkzKq7E1L7wJqbmzJGqr6+gUQigd/vo6SklGg0iiCIxGJRJBIoLS2jp6cbiyWXgoICWluaz6D+nRxpa8VqtVFaVkZpaTlutwt7QWq7SjweJxZPRbHpdDpMJjO9Pd3U1NQRCPgZHBigpLgUlVp92m2q0WhZuGgxgUAqc8nAQD8ajWYqO0VqHS4WiyIIAm63C6VKSSIeQ6fX09vXQ1lZObFoDCEhYLXZ6O7qYtHipVNGRENpSRnBUDDjipNIJJSVltHb24PVZsNut9Pa0ozb5aKsvHxqC9CxBiy9NlhQUERVVTX2/HzGJ8Zxu92oVGp8vlSKu/Re2PGJcUZHRlCpVVMGRszEAbS3HcFsseB0ORkbG6W0tJxEIkF+fj7RaJTo1N7V1NqzAUNODl1dHVRV1+D1ehgYGECtVlNRUXlG9uNNK4z+3/7t33j22Wc5dOjQu77zer3YbDYee+wxrrvuOgDa2tqYO3cuu3fvZuXKlWzevJkrr7ySkZER8vPzAXjggQf41re+xeTk5Cm/aD6MYfTTYSZeUKcbRp9KahwlkUiimvKPA8RiqQ3ZcrmcZDKBRJJ6UORyBfF4DJkslQA5Go1m9tLN9mxBFAUikSgSiQSlUpnZvxeNRJBIpUgkTG2yJDPwSCYTKJXv7LFTqVRnbGYTj8eQy+UoFMqMLNFoDIVCTjIpIJNJSSSSKBTyzL4gpVJJPB5HKpVmXF3vK+tphtEfHaiTTCQy6yOpz97RfyKRyGTkSOfrTCXAlhGNxlCpVLPuBk/rP7XRX5LRoyiKRCKRTP9Lz0akUikSIJFMolQqZ0z/6XVjURSn9ohGAJBIpAiCgEIuJzklQ7p9pRIJSCQkkwkUCmUm1ZVUKiUej2eeu1SbJ5HLFSQSiUz/lstlxONxZDI5MpmMWCyGTCabSuP1Tv86PpADUn1OKpUhCAKxWCxz74IgoFAoiMfjyGUyEslkZgCe9oCl+6JMJkNIJhERUanUACdt89Q1j23z1DFJVCr1Sd8TZy2MvrOzk8LCQtRqNatWreK+++6jtLSUAwcOEI/HufjiizPHzpkzh9LS0owB2717N/X19RnjBbBx40buvPNOWlpaWLx48QmvGY1GMxkrgNPKRvFxCh39oCiVKo4fKxwdHXf8msHRs+y0+/BMrNWA9Bh3Zfqa6vd0YaZuTCaTnbpBmAHSFQ7SpF4OcrTa1GfpJk3/LZe/08bpqMXZljVtAJRKJekO8M5n09P/bJPWv1r9bv2/lwxpyWdK/+nkBmmOlifNyV+iqTY+uv2Obtv3bvN3/p2+36MH+CcL5U8PSGQy2QnbKS3L0Vc7+tqZgclx8pyK3o9u85PJOdNMy4CtWLGCRx55hLq6OkZHR/ne977H2rVraW5uZmxsDKVSiclkOuY3+fn5jI2NATA2NnaM8Up/n/7uZNx3331873vfm46oWU7CR8mQf1T2sJxMpo+KrB9GOeHDI+uHtX1OxEdJ1plgWgbs8ssvz/x/Q0MDK1asoKysjCeffHJWR2bf+c53+PrXv575t8/nO6P547JkyZIly4ePDxRfbDKZqK2tpaurC7vdTiwWw+PxHHPM+Pg4drsdALvd/q6oxPS/08ecCJVKRU5OzjF/smTJkiXLuc0HMmCBQIDu7m4KCgpYunQpCoWCrVu3Zr5vb29nYGCAVatWAbBq1SoOHz7MxMRE5pgtW7ZkEpxmyZIlS5Ysp8q0XIjf+MY3uOqqqygrK2NkZIR7770XmUzGTTfdhNFo5I477uDrX/86FouFnJwc7rnnHlatWsXKlSsBuPTSS5k3bx633HIL999/P2NjY3z3u9/l7rvvPmYheyZIRzGlo9Q+bgiigCiRkEwmp0LJzy3f97mOhFRNN1EiISkkz8heyixZTpd0NOJMr9FNy4ANDQ1x00034XQ6sdlsrFmzhj179mQq3/7kJz9BKpVy7bXXEo1G2bhxI7/4xS8yv5fJZDz//PPceeedrFq1Cp1Ox2233cb3v//9Gb2pNMFgkHAoOCvnPtuEg0Eiah1urwd5LP7+P8jysSMUDhPR6HF73MijsbMtTpYsJ0Wr06HTzXw5no91OZWP4K2dMv6+Doa3/oWqG76IwmAiOwM7x0jvA3vtr1TfdOdUH8iS5cPL8e/mbDmV9yHVYB9fI5YyWhIkSLL261xCPKpXS1L/Oceip7N85JidDvqxNmApzpUn+1y5zywnHpRl9Z/l3GP203RnyZIlS5Yss0DWgGXJkiVLlo8kWQOWJUuWLFk+kpwDa2Ap3i8i8VSy2J/KHobpHJ8+9kyVkgcQEVNBH6kLwwnkPeGxJ2E2ZE+Xcz/+/Ed/dnR5+eML5p2sgN5My3qyPvV+ek3Ld7LjZqtNj7/+8d8df933K0Q42xn+j77O8W19fF89/rdnUv/Ht9/x/56urLPdT0+lwOSJ7unDkpvyaM4ZAwZkSgVIpVISiQQSiYRYLIZCoXjPwofRaBRRFFGr1ad0nXA4jEwme9/yMMlkkmg0ikajOaWO8EFKQsSTCbrGe5n0OiizlVKcW4AMKY6AG3fQQ6WtFJk0lYk6Eo/SNtJFIByguqASi95E91gfCSFBXUE1oigw5B6jyFKAWj6zpeVT5V5iHDlyhNHRUWpraykrK8PhcNDS0oLdbqemphaFQo4gCAwNDdHR0U5RUTHV1dWMjY3R29tLfX09OTk59Pf3k5OTg9VqndHKsOn6Su3t7Xg8bkQRjEYj5eXldHZ24PcHqK+vJzc3F0EQGBgYYHh4CFEUU5WGKyvp7+9HoZAzb958otEoIyMjVFRUnHYRxvcjHo/T1tbG8PAwNTU1lJWV4XK5aG5uxmw2M2/ePFSqVI2o8fFxHA4HlZWVtLe3IwgC9fX1JJNJBgYGKC8vz5QqmWlEUSQcDk9VTi8jJycHQRDo7e1FLpdTWlp6zPEOh4OxsVFqa+vweDwcOXKE2tpa8vPzGRkZQSKRUFhYCMz8C3diYoKmpiZ0Oh0NDQ3IZDJaW1vxej0sWFCf2SObLssyMNCPKEJpaSm9vb2Mj4/R0LAQtVpNX18f+fn5mdDxmZA1PRgcGhpiYGAAURSRyWSUlpYyPDxMNBpFLpczZ84czGYzkCr+e/jwYURRpL6+HpVKxeHDh1EoFMybN2+qcv0QFRWVs9ZXT5VzwoClX4rPPPMMeXl5LFu2jD/96U+cd955tLe3sWBBPZWVlccUoEwkEqjVauLxOFu2bMHlcnHzzTdPGb1opuZOuoZP2ijKZDL+/Oc/k5dn44ILLpyqr5XM1FWSyWQEg0E8Hg+iKLJr1y4+9alPZQplpusxCYKAUqnMvChPtwZXugPv7tjPH7c/RXleKU/sepavXXknuXoTD7zyCDUFVZRbS5CRyvDx/NtbeP3wDvKMVp7bt5lLFq3ntcPbkcvkXNJwIYFIkGH3GLesuW4WtAWvvfYav/rVr6ipqeHhh3/LN77xTX77298SDodwudx885vfZM2aNXR1dfEv//Iv5OZaGBkZ5eabb2bPnj3E43HeeustLr74Yh5//DG+8pWvANYZlzMajbJjxw7a2tro7u7GZrOxatUqXn31VXQ6LWazmX/7t++h0WhobGxk27ZtOBwOhoeHufbaa2lrO0IikeQzn/kMra2tAJSXl8+4nOk+8Oabb/Lzn/88065f+co9/PnPfyYSCTMxMcGtt97GJz/5Sd5++21+8IMfoFQqufXWW/nLX/6CTCbjmmuuweVyMTw8zF133TnjcqZljcViPPnkkzQ1NfGd73wHURRpbW3lxz/+EV/60pePMWA+n4+f/eynWK02CguL+PWvf4XL5UKlUnHLLbfyu9/9juuuuy5jwGYSj8fD9773PSQSCR6Ph5UrV1BYWMSf/vQ4BoMBjUbLv//7v2M2mxFFka6uLv77v/+bW2+9FZlMyv/8z48BCW1t7dTV1fHiiy/yjW98Y8blFASB1tYWXn75FVwuFz09PXzxi3/HY489xpw5czAYcrBYLJhMJmKxGA888Ev27duPVCqhvr6BlStX8OyzzxKNxvjsZz9LR0cHyWSS8vKKGZd1upwTBiyN1+vlrbfeQqPRsH37dubPn4/TmXogn3vuOYqLi8nLyyMajTI+Ps6iRYvo6+ujr68XlSpVJLGzs5N9+/YhkYDb7aG8vJxwOIxEIsHhmKS6uhpBEBgbG+dXv3qQRYsW09jYyNKlS2lsbESr1aJUKmlqauSyyy7H7/fT3t5OU1MT0WiUBQsWsH37dqRSCQsWpEY/Q0NDrF27lqqqqtOeRQSjQXJ0BhZWLGDYPUYwGuKNlp009hzGlpPLhM9BkdmOCPjDfuzmPOaV1LHjyB6i8SgSJEglUtpHuxj3TPKZ1deglCve97qnQyDgJzfXwooVK+jr68XlclFXV8emTZv4yU9+Qn9/P6tWraK9vR2DwcD/+3/f5cEHH6ClpTkz8HC5XDzxxBOsW7cuU7JnpkeKer2eO+64A6fTyb333suGDRs477zzqKuro7GxkZaWlqkihAo2bdrEhg0b+MUvfkFZWRn5+Xl0dnaQSCTZvXs3breLu+/+SmYQNZOzxfT5/H4/RqOR885bzsDAAACf+9zncDgcPP3005k6ezqdjiuuuJwdO97MFLeUSiU0NjYyOjrKl770pUxRxZkkbWgPHDjAb37zGxYtWkRbWxs1NTX87//+BIfDSXd3N/Pnz8dgMJBIJHjqqad49dVX2bTpSgYGBojFYqhUavx+P0899SS1tTXMmTNnxmUFSCQShMNh1qxZQ09PN16vl6uu+gRWq5WWlhb27dtLPB5HFEW8Xi8//elPGRkZoa+vF6PRiCiCSqVkYGCAI0eOcM0112AwpLJVzKTuZTIZGzZczJo1a3nkkYex2WyUlZUhk8kwGHKorq6moKAAiURCKBSipaWFO++8E5lMyoMPPsicOXVIpTJkMil79uzB6XRyzz33zFpfnQ7nTBBHPB4nNzcXjUbDW2+9RXl5OaFQiEAggEqlwm63c+jQQdRqNfv27UOr1XLgwAHGxsZYsKB+qoJqjK1btxIOh5BKU9PwxsZG9Ho9VquVZFLg0KFG4vFUJVS324MgCExOTiKKIlqtlkOHDqFSqSgvr8BisTAxMc5LL20mNzeXcDjM4cOHiUQilJdXMDg4wOjoKB0dHcRisQ+UWcSkM+LwuXijeVeqmm/Yz96ut7lu9dU4/W6e3PUskXiqaKhFb6Z3fICdR/aiVWo5r3oJn1qxifOqlzDmniDXYOEPO/7McwdeJpaIz1jGk/R5cnOtjI6O8vLLLwFQXV3NHXfcwf79+4lEwqxcuRKpVMrcuXMJh8P827/9G9u37+C881Zw9913s2LFCgwGA3K5jN27d/M///MTJibGj1lb+6CkH1iFQsGbb+5AIpGwYcMG8vLy6OvrY9++fcjl7zzg6Zl3U1Mj1157LZdffgVXXfUJLrzwQgYHBzGbzfzoRz9i8+bNmQq+M4lEIsFiseBwTPLKK1sQBAGbzYbZbOaNN95gYCDlzhRFkdraWqqra5BIYOnSpdx0002sW3cBQ0ND2Gw2/u///o8nn3wi41qfScLhMM899xwrV66koaGen//85/z1r3/F6XRx6623snnzi2zZ8krmudqyZQuf/exniUYjPProo9x002dYtmwZdXV1eL0+hoaG+MEP/pOuri5gZrPzpKtkvP7667S0tGKz5ZGbm8vw8BB79uxGIpFmrtnY2MjQ0BCf//zn2bbtDRobG/niF7/ImjVrEAQBg8HAM888zS9+8Qt8Pt+M9dV3Cl3KGBkZYdeuXVxzzTWUlpaxdu06amtref7553nttdcylZZFMVXAUqVSIwgCS5cu4+qrr2b9+vUMDw9htVr58Y9/zIsvvnjWc3CeMwYsGAzidrtZtGgRAAaDnp6ebvx+f2Y0qlSq6OhoJx6Pk0gkKC8vp7+/j9bW1kynstvtTE5OZtxFkCrKeeDAAbxeD4GAH4/Hkykd3th4CLfbzaFDh+jp6SGZTBKPxxkfH2N8fJxgMITdXkBfXx8AeXl5RCIR4vEYPp+fvLzUQzE4OHjaHTohJGkZbKcyv4zPXXQTEgn0TQwgAUpyC7HmWIjEo7zd28Te7oMcHjzCebVL+ewF1+P0u/CGfDSUzsMVcFORV4o35KOmoJKmvmYcftcHVc0xCIJAU1MjtbV13H333YCE9vZ2/vznp/j973/PVVddhUKh4OWXXyYej3PfffehUqlYu3Yt69ato7q6mtzcXHw+H0VFxUilKcNx6FDjjL680ucaGxvjxRdf5OqrP4Fer+evf/0rGo2Gz372swwPj/D222/zt7/9DYfDwYMPPsjChYuYO3cuOp2O885bTldXF/X19YyMjHDeeeexbdvruN3uGZMzTTKZpLGxkaqqKu6++27kcjk7d+7kmWee4corN3HeeefR1NTE9u3bOXDgQCazh0qlYtmy5YyOjlJXV4vH42bevHns3r0bp9M543IKgkA0GqWwsJDy8gqSySTBYBCDQU91dTUajRaHw8HmzZvp7OxEFEXKysooLCwiHA5TWFhIbW0tPT3dNDTU43a7sdlsvPHGGzP+snW73QwM9HP77bdzySWXcPDg2zz//N8ACbfeeitOp5ODBw/y3HPP4XQ60Wq1VFdXYTDoiUajzJ8/H6lUgkKhQK/XYbfb6evro6enZ0blBIjFYjz99NPU1dWxcOFCnE4nMpmMefPmYTDocblcPP/88wwMDCCXy2lvb6ezsxOlUoXJZGLZsmV0dHRSX9/AyMgwy5Yt47XXXpuVvjodzhkXol6vZ8OGDVgsFiQSCS5X6sWbXp+SSqVcfPHFSKWp9EyhUJji4iLmzZtHPB5Hq9Wi1WrZtGkTCxYswGw2k5+fz9///d+j1WopLS0lFAplrqdSqZDJZPj9PhQKJVqtFp/Ph1QqISfHyNKlS9Dp9JSXl5OXl8fExARqtRqtVktDQwNqtYp4PIFKpaKyspLi4uJjSptPB4VMzpq5K/jjG3/mV6/8joq8Mi5asBa1Qs0TO59Fp9Jw45praB/uRAQumr+GZ/dupmO4i5W1Syky2xn1jCOIIlctuYRDfYd5tWkHtYVVmPXGGdDOO0ilUi66aAMPPPAAP/rRj6YW4/PYvHkzBoOB559/gZGRURwOBwqFArPZTF5eHnfccQd6vR6/309rawuf+cxnUCgUPPjgg6hUKurq6mYluquzs5N58+azevUalEolFRUVPPTQbwiFQlx++eWo1Wq2b9+O3W7H7XZz0003oVarp37bhc1m47rrruOll3S88cYbrFmzBqNxZtsUUiPwSy65mPb2dn70ox9SVlbGBRdcwJYtW/jZz36OVqvllls+S1tbO0ZjDnPnzmPevPkoFAoGBwdRKORcf/0NvPnmm7z44ossXrzoXdXXZwKdTseNN97Ib37zGw4cOMBVV13Fhg0b8Hg8/PCH91NeXs7q1Wv4y1/+wpo1q7n22mt4/PE/oVaruPXW21AqlRw6dIirrrqK2to6Ojs76evr4/bb78i4vGYKm83GJZdcwsMP/xa5XMENN9yA1ZrLr3/9G/x+Hxs2bMjMcG+++Wbq6+v50Y9+TElJCZdffjk+n4+hoWE++9nPMjk5yaOP/g6bLY/i4mJgZt2ITqeTSCTCDTfcgEajobS0lGAwyP33309VVRVr167lD3/4PRaLhU9/+tP88Y9/RBQFbrrpJvR6PU1NTdhsNq655hq0Wg1vvPEGa9eunZW+Oh0+1sl8j+ZUp+QnC3k9lTDtE13znd/D6ab7Ofp66b99ve0Mb32Wqhu+iNJggveRRxAFwrEI0XgMrUqDSq4kKSQJREPIZXK0SjXxqRGqXCYjFAuTSCbRqbQoZHISQjIVWCJXkBSTBMJBNEo1ylmIQhRFkVAoRCQSQafToVAoCIfDCIKARJIasYqiiFyecnklk0nUanUm3DoSiWQi5Px+PxKJBIPBMONyQmpkKwhCJkJVEASCwSCJRCKznpGOdI1Go+h0uowcsVgqg7xSqSQej+P3+9Hr9Zno1ZPKO9VGvu5Whl//K9U33oUyx/S+8h7friqVimQyid/vR6FQoNVqicViSKVSZDIZsVgMtVpNMpkkmUyiUqlIJBL4fL7M72djUCCKYqYN9Xo9crmcaDRKMBhEq9WiUqmIRCKZCLhAwI9UKsu0bSQSQalUZty2iUScnBzjjJfzSAdY+f3+qfWkVB8LBoPE43H0ej1SqZRoNJoJCAsGg2g0GjQaDclkMtPGoiji8/lQKlODXZg5A5Z+Ro6PeD6+TcPhMEqlEqlUSiAQQBRFDAYDUqk0s4ShUqmm11ffQ6aZSOZ7Thiw1C2mFlJ9Pj9qtRqDwYDL5UKr1aJQKHA4HBiNORiNpqmHIoDb7UapVJCba33PMPtQKMjw8Ajl5eUoFKnAhnA4jMvlwmg0ZmZmVqv1hLOoQCDA2NgY5eXlyOXyU+oMp2LAzoRqT7b3Zbq8n6wzsVA8U2HJH5QT7Ws60THvIcQpGbDZ1v9MvmDPBB8leWezr55K/ztVPsjzns1GPw1EEZqammhqSu1nWLBgATt37mT58uU4HA5sNisul4uNGy9Dq9XS39/PK6+8gsViobi4mHnz5pFMJjPh7qIoYjKZGB8fw+Vys2fPbi66aAPl5eWo1WqcTiePP/4YN930GV57bSs2m40lS5YSCoXIyckhHA4jigJarY6WlmY6Ojq4+ebPvqehnN79vnuT7/GbfyORMCqV+pRck+kF3uOPTc2KUu2bPv/phvqnZTxe1kQiTjyeQKPRnNK50tsbjpcjfQ/p2fDpyHn03yf6fSQSQSaTZQYy73Wek7Vp+pgTzbxPR9YT618gHI6gVp++/o+W8XRlPZU2Ta9Jz5z+3/n8g8p6vDE4Ff0fLU86wOdE3x0t3wcxFCfaoD7d5z+ZTCKRSN51rCgKpG//g8p6OpxDBiw1AxNFMbOJWaVS0dbWxoIFC6itrcXlcuF0OpmcnMThcJBMJgmHw3g8HrZu3UokEsHv96PT6YjFYpkZV/rY559/nvXr12M2mwkEAiSTAoODg0xMTGK3F7Br1y4GBgaorq7m0KFDWCwWtFotEokEu73glDdKn8q9iqLI4OAA+/fvpby8gsWLlwIgCClXQiQS4dVXX2HdugvJzbUik8mIRqMZ15HL5aSgoBCpVEpnZwcTE+NEo1GWLl2GTqdHFAUmJsZpbW0hMNUmCxYspLik5LSM2OHDjbQdaWXN2gsoKEjt2YnH4ySTSXp7u+nu6uSiiy5BPfUSi0ajqJRKXG4Xopia3UajEZqaGomEw6jUapYsWUYikUAqldDUdIjx8QmSyQQ5hhxWnb8m43acDn6/j7f27CYai3LRRZeg0WgQRZFoNAJI2LFjG3l5dubOnZdxDabXWXt7uyksLEar1eJ0OGhra031o4oKysoqiMViJJNJ3nprN36/D5VKjd1uZ+HCxdPaA/jO5tVB9u3bQ1lpBYuXLEUqlU4FSURS+t/yMhdceBEWS+4x+o/HY7hcLuz2AqRSKV2dHYxPjBOLRlm6bDlarQ5BEBgfH6O1tZlQMIhOr6e+fiFFRcXT1n8ikeDw4UY6O9tZu/ZC7PZUSHc8nmqPnu5uunu6uOiii9FqtYhiSv9KpRL3UfqPRCI0NzcRDofRaDQsXrx0Sv9SmpoOMTkxTiwWx2g0smLl+adkEI9u05T+/ex9azeRaIQNGy5BrU7pPxKNIAHeeON18vPtzJ07P+NuE5JJZHI5o6MjWCwWdDo9k5MTHDnSSjwWo6y8nIqKKmKxGIlEgn179+DxelCp1BQWFNCwcDEymWza+h8eHmLf3j2UlZWzcNESZDLZMfp/besW1l2wHrPZglwuzxjfeDyG05l6/mUyGT093YyMDJNIxFmyZBkajRZRFBkbHaH1SAuhYBBDTg719QspKCg8o2H154wBi8VijI2NU1tbQ0tLCyMjI6jVanw+HwaDPrMZdfHixcTj8alsBdWMjY1z5Egrc+fOZeHChbz22lZyc3Pxer2MjIzwqU99ipdeegmz2YLX653aAzbGxMQEJSXFGAwGRFFkdHSE/Hw7kUh4ymDlU1FRidfrZXBwkNra2hm/55wcA2azBY/HQyQSSW1obGnG6/NQWlpOMBhgx443MBgMlJdX0N/XhyAK6PUGWlub+dQnrwMJ7N27hyuuuJJwOIzb7ebAgX2EQ2FKS8uorKxi9+5dLFq8BI321F8Ix5Ofb6e7uytVRTscJh6PceDAfhQKOWq1hpGREf72/HPMn19PIODH7Xah1xsYGRlGo9Fw6aWX09rSwsTEOBs2XML42Bhtba2MjowgiiIVlVUIgsj42BiVVdWn/YApFEqKiotpbj6cCSOfGB+jo6OdvHw7AC3NTXS0t7H8vBV0d3cRCYfJt9t5+eXNXHXV1cydO583d26noqKSsrJyAoEAe/bswuv1kJNjpKKikoMHD1BYWITNajttWfV6A7m5Vnx+L5FIBIAjrc24PR4KCgoJhkLs2PEGer2BivIKevt6MuseLc2H+dQ11wNT+t90FeFQCJfLxf79ewmFQpSUlFJVVc3u3Tupb1iEWn16+pdIJOTn2+nr6yEUekf/+/ftzawJjYwM8/zfnmNBfQOhUAiXy4lGo2F8fAyVSs2ll17OkSOtjI6OcPHFG5mYGKejvY2hqewnFRWpTbfDw0OUV1SeVlIASG2bKCwqpqXlMNFoLNWnxsfoaG8j356PVCqlpeUwbW1HWLFiFT09XYTDYez5Bezdu4e1a9dRN2ceu3btoLy8irLSMjxeD3v27MLn9WLIyaG8opJ9+96irLSMvLz8D6B/PTZbPl6fj2g0gkQi5ciRVtxuJ/n59oz+DXoDFZWVdHd3kUwmM/q/9rpPI0HCrp072HTl1UQiqed//769hCNhSkpKqaubw5s7ttOwcDEqleq05PwgnDNh9E6nE5/PSzAYoqqqing8jtls5sILL+DIkTYmJydZvXo1CxcupKamBq/XSzQaIz8/nxUrVhIORwiHw9TW1mGxWDCbzSiVSg4fPozRaGTu3LkYDAZqampYtGgRHo+HoqIiYrEYGo0ao9GE2+0mNzcXqVTKsmXLsdvz8ft9aLXaaY0GT4XUwq1AMpFgYnycpsaDNDUeJBD009/fTySSWrCtqqzG6XTSeOgg+Xb71MJugMKCQswWS+plEouhVKqm9mcNIwEsFgsulxOz2YJUKsFmyyc3N5XtYrpuGVEUScTjhMMhhocHeeut3fT29jAxMc742FjGXVtYWMRAfx893V3U1s6ht7cHmUxGYWERKpUKn887lcJLhSHHyJEjrZSVV+DxpNYy1Wr1McefjvsoHdDg83np6upg9643cXvcDA4O4PN6AAklJaVIJNDc3EQwGKCgsJD+/j60Wm1mhuL3pWZYOp2eZDLJ4EA/tbV1DAz0Z/qIWq2moLBw2sEHqWNFkokE8Vgcx+Qkzc1NHDr0Nj6/j8GBPmKx1Axmzpy5eDxuDjc3kZ+f0r/f7yffXoDFkks4HErNdlUqLLlWRkaGkUgkmM0W3G431lwbUokUm81Gbm7uKct4dJsKgjDl7Ygw0N/PW2/tpr+vl4nJcUbHRkkKAiajiby8fPr6euns7KC2to6+vl5kUhlFRcWoVCqCAT9SqRSVSoXRaORwcxOlpaV4vR5kMvlUNpt3jj8dUvpP4PN5GRjoY9fOHXi9HgaHBvB4vIgilJSUIooizc1NhEIhCguL6B/oxWQyUVBYhCAIeL0+VCoV+qlAn8GBfqprahno78uslWt1OoqKS6YdfZzuK4lEgmgswuTEBM3Nh3n77f143C76+/qITXkwamrq8HjdNDcfxj6lf4/Hg9Wah9FoJBwJZ4JizGYzgwP9SCQSjEYTjslJLOZcJBIJVqsVs9lyWm36QTgnZmASiYSCggLuueerGZ9z+uGRyWQ0NCwEyEQ15eQY+cIXvpA5Np1a6ugAi7Sv/Wi/8KJFizLnTqedkkgk1NfXZ9bN0p+l/11bW5f590xOu2PRKHvf2oMhx0BOTk7GhdA89VA7HJPEYvEpN02CPFseQ0ODSKUy8mz5HDnSgsvlSm3OtFppbDxINBJBpVbh8XrRxuJUVlal3JHhMJFwmJycnNO6h2g0SkdHO/b8Agx6A4uXLMPn8xEOh5mcnMDv95Gccn2AiEajoa+3h/y8fJDAxMR4anBRN4c333yDxkNv43K7MJvN9PX2oNXpMBpNjIyMEA6HMusL00UQBNrbjxAI+LHZbBQUFLJkyTL6+3upq5uDx+slEY8jlUqIJ+LYTQWMjozgcjmpqKjE6XQwMjLMvHkm5sydR0dHG263k2QylTZsYKCfosIiZDLZ1CZ7/2nJmXJpxti3bw9Gk4lYPEZDwyLkcjnNzYdJJpO43S7i8RihUIh4PIbNZmNkeAiZTIbNlkdrawtOpxOrNQ+rzcahQweJRiOolCo8Hg86nY6KikrCkTDhSDgTUQnTXwNxOh0cbmokPz8fgyGHxUuW4vf7CIXDOCYn8PlSno1YPIYECVqNhr7eXuz2AgDGx8eIRiNUVlWn9N94EKfTSW5uLgP9/Wi12lTSgPFxwqHgaetfFEXa2lqn9J+H1Wpj/vx6Bgb6mVM3D5fblXIXyqQkk0lycoxMTIzjdDooKSmjp7uL0dER5s6dz5w58+hoP4LX607lI1QoGB4apKioCKlUlokQPPqdMR054/EY+/fvxWDIQW8w0FC/ELlCTmtLC4Io4nK7iCfiRKYGKDZrHsPDQ8hlMvJsebS1t00ZMhvFxSU0Nh4kEg6j0+lwOCbRaLRUVFQSCocIh1P6PxtrYOdEFCKceuTQTEboTJfpKP69ohDT8gcCfnw+L2ZzbmaG5/f78Pv9GAw5U+ssKmKxGCajCY/Xg1arRafT43BMkpNjRKvVEo1GmJycRKlUYrHkTiWvFcnNtRIKpfI6ptbzdNPuvGlZnc7UOmJ6PU4QBJwOB0hSYebhcBipVIZUKkGj0eLxuLFYcgERj8eDzZaHXC7H5/Pi9Xgxmkyo1WocjsnUQ6zX4XK6iMVTD6t8mklIjw6bdzodaDRajMZUaHb6s1RbxZBKUgZMp9OTSKQCEHJzrfh8qTVYs9mCKIpTg4gYNlsesVgMn8+HzZZyGU5OpvYFWiy575bzfaIQj9W/D7PZ8i795+QY8fm87+jfZMbjcaPRaNEb9Dgmj9Z/lMnJiWP0D2Cx5BIKhfB43JjNFnQ6HXDq/fjogBKXyzWl/1xkstT2CKfTAaKIYkr/6cGkWq3B5/NiseQiJJN4fV7y8vKQSlP7Lj0eDyaTCbVaM6V/A3q9HqfTSTwez/SV09F/an3IgVqtyUQsv6N/3ZSrTpLZAhCPx4nH41ittswavMViSfVvpyPzXTpxgc1qhSn9q1RqcnNPoP9TlDUQCEw9/yfWv9+fCtuPRmOYzSn9p55/HZOTkxiNRrTa1Hr/xMT4CZ//cDiE2+3GbDaj0+mBU6/akQ2jn4YB+7jxfmH0J4pCmz1ETieqL/PrGe6Cp3LPpyPre7dpqg1O55xHz+pPJNd0Ddj7y/r+spwuH6RdZ4ozqf8POuA9Lf1PQ9b3u+apfD6d76bT17Jh9FlOypmdzp/9/Vmzeb7jz3vi85/eNU83pPtUzzv9tbMzz0dd/x/kerOt/+l8dzq/eb/vZptzJogjS5YsWbJ8vMgasCxZsmTJ8pHkY+1C/Agu750WIsA5cq9ZTsTMlYnJkmW2mA1X48fWgL2Tnujj+WCnUrikQvlFQfigy1BZPmKI4tQATRQRRBFRPL3Q8CxZzgyzsyb/sTVgAMGgn1AoeLbFmBXCoRARXQ4ujxtZNHq2xclyFghFwoR1RpxuF/KpTBtZsnwY0Wr16PWGGT/vx9qA6XR6NBrt2RZjVggEPIRCfsxGEwrD2a3Jk+XsoPA6CIV8WEwmFDNcly1LlplEKp3ZWmxpPrYGLDVVlSKTfTzjVKRSGRJAJpUhk31s1ZjlZIgiMok02weyfGTIroFNk3NiU7Nkyrt8LtxrlhSieNzK7swWasyS5aPCx3N6kiVLlixZPvZkDViWLFmyZPlIkjVgWbJkyZLlI8nHeg1spjh6k+iJynOf7LsPE++30VWSWUybOj71o/c972zc86lsyp1uEtWzJWf62h92WY++5odd1vR1z7acWc4+54wBS29sTnf86XboRCKBTPbuUFBBEBAEAblc/r4ZoOGdB+m9ZJitl4Iz4CYQCWLRm8nRpEof+MJ+kkISk86IRJRk2kcQBZx+NxqlGq1Kw6TPiSiK2HKsCKKAL+zDpDUik556qfNTJZlMMjExQSgUIj8/H51Oh8vlwuVK1fg6unBiMBhkbGwMrVZLfn4+wWCQyclJ8vLy0Ol0eDwe5HI5hqnCgTMla1qfkUiE0dFRBCFJQUEharUap9OJ1+vBYsnFbDZnjnc4HPh8XvLy8tFqtYyPjyMIAgUFBYiiiMvlwmKxTLvUx6lydBtaLBYkEgl+f2qvpM2Wl6lrF4vFpu5JwG63o1AoGBkZQalUkp+fTzwex+v1ZgpvzqSsqVpWcUZHR4lGo+Tn56NWq5mYGCcajSKRSMjLy0evT/XfcDjM6OgoKpUKu91OJBJhfHwMq9WGwWDA5/MBYDSmthlkDdnHi3PCgImiiM/n4+WXX6a4uBiv10thYSHz58/PFIuTSCQkk8kTjkSDwSBPPfUkF198cabaqkwmI5lMsnXrVpLJJJdccglApjhlMpnM1C/yeDzs27cPv9/PkiVLsFgs7Nu3D5lMxsqVK1GpVOzevRu9Xk9DQ8OMlkBJ38PhwSM8+MrvSAhJrAYLX7n8CyjlCh545REWVzawseFCZFLZlPESOdh3mJ8+/yCfWHEFcwtreHT7k0iAG1dfgzfk5chIJ7esuR6daub22aUHGdu2vc7//d8vSCaT1NTU8OlPf5pf/vKXeL1e9Hod//Iv/0p1dTWhUIj//d//Zc+e3SiVSu666y727HmLjo52Fi5cxDXXXMPDD/+Wa665loaGhlkxtL///e/ZvPlFpFIpS5cu5ZJLLuUnP/kfwuEIJpOJ7373u1RUVNDa2sr3vvc9wuEw8+fP45Of/CQPP/ww8XicL3zh7wgGg7z99tvcfffdmZfzTJDWf2dnJ/fddx9utxudTsc///M/U1JSwk9+8hPy8mzcfvsdKJVKEokEzz77LI899kdAwgUXXEB9fT2PP/44CoWce+75Kk1NTXi9Xj7/+c/PaBn5VBXxJM8//zyPPvooSqWC4uJibrzxJu677wdIpTJ0Oh1f+tIXOf/81USjUX7961/xyiuvIJcr+MIX7qC7u4cDBw5QW1vL5z73OX73u0e48ML1nH/++Vnj9THknDBgkKq83NLSQigUQqNRU1dXx+OPP04ikcBgMLBkyRIOHDiAVCpBp9PjdDpIJJIUFNgJhyOMjo4xMTFJd3cPk5OTLF26lM7OTvbv309eXh47d+5kYGAApVJJVVU1PT1dlJdXsHDhQv785z9jteaSl2fjoYce4qKLLmL37t3k5BioqKjAYjGzZ89ufD4flZWV6PX6GTVi8WSc3R37qS2q5oZVV/PD5/4/jgx30DnaTedoD+fVLCUSj6FTpYreDTqHef7AFox6I9F4FGfARY5Gj0Km4FBfMwOOQT6z+lq0U8fPJNFolK1bX2P9+vVcfvnl/PM/f4eJiXFuvvlmDAYDP/vZzxgbG6WqqgqPx4Pf7+c///MHPPbYY+zatRun08miRYsZGBjgscceo6ysjHnz5s3Kyysej9PWdoQNGy7GarXyyiuvIJO9Rn6+nW9+85vce++97Nu3j5KSEnp6ulm2bBkXXnghP/zhD2lpackUxNyzZw9DQ0PccccdmaKQM0kymWTv3r1otRr+4z/+g/vv/2/27NnDW2+9xRtvvMEdd9xBMBhEqVQCUF1dzbe+9W06OzvYuXMXOTkGSktL8Hg8vP766/T393HPPV/NHD+TJBIJ2traWLp0KcuWLeMPf/g9TqcTqVTKunVrqa6uyVRQDwaDTExM8t3v/guvv/46O3fuRBRh8eJF9Pb28sQTT6DV6liyZEnWeH1MOaeCOCQSCa+++ipGowmLxcKRI62YzWYOHTrEn//8FAqFgv7+Abq7u2hqOkxz82EOHjyERqNBIoGWlmZeffVVxsbG2Lx5M11dXcyZUwfAnj17iEQiuFwuNm9+AafTyZEjrQSDQXp7eykpKaW6uoZAIMDQ0CADA/0Eg0H2799He3sHZrMFp9NFZ2fnjCdmlUnllNmKaR/q5PE3n8Hld+EOetjTvp+Vtct4du8LbDm8jaQg4I8EefSNJ5FIJCjlSrrH+sg15NJQOp/i3EJG3GPkm/LZ2ryDPZ0HSArJGZVXoVBQVVXFnj27eeihh4hEosyZMxepVMIPf3g/DocDk8kEQEFBAffeey9+v5/+/n4uuugibr75ZuRyOYWFhTgcDoLBEI8++ihOpxOY2QTPCoWC2to6nn76aX7zm99QVlbG/Pnz6evr5cEHH2RgYICiokJkMhmXX34FX/7yl9m2bRulpaVcdtnlLF++nIqKSkZHRykoKOCllzazbds2ksmZbVOpVEp5eTljY2P8+te/5siRNpLJJH/963OsX38hW7e+ykMPPUQ8Hkcmk1FXV8cbb2zjd7/7HTabjY0bL6O8vIK6ujm0t7dTVFTEk08+yYEDB1K5OGe4Tevq6tiyZQs//elPMZvNFBcXU1JSSiQS4Te/+TUvv/wSgiBgNpv57ne/i1KppKWlmYsuuohbbvksSqWK6upq+vp6USoV/O53v2N0dBQ4dxJ8nyucMwZMEAQSiThr1qzhlVdepq2tjWQylQA1Ho+jUqlwOBxIpVLq6xswGnMoLS1jcnKS0tJSYrE4UqmUeDxOVVUVJSUlDA8PMz4+TiAQQCaT0t/fj91uRxBE1GoNCxcuRKfTMWdOHQcPHqS5uZmioiL6+wdYsWIFPT09iKJIe3s7NTU1Uy+ON0gkEjN23ymXpoQL56/hpnXX0jrUzuq5K8jLsWHSm7hs0UXUFlYx7BqleaiNPscgxbmF5GgMOH0uJjwOorEoFzesQyFTYNGbcfpSxuClg1txBb0zKqtMJuPGG2/k+utv4NChg3zqU58kFosSDAa5777/oqSkhH379rN//z5GR0fZs2cP//M/P+YTn/gE8+fPZ8mSJVx00UUMDw9TWVlJV1cXXV1dbN++HUFIzpiskCrPvm/fPr7whS/wrW/9E62tLcyfP59/+Id/oKWlhfPOW05BQQFvv/02Ho+Hhx56iLa2Nm699VasVitXX/1JtFotJpOJiYkJkskkTz31JGNjYzMmY9o9ft555/GNb3yTzs5OGhrqWbhwIXK5giuvvIply5YzNDRES0sLLS3N7Nixg4su2sA3v/lPdHV1kUwmue666wiFQlRWVtLZ2YlEIuHxxx/LrDHNFOFwmF27dnHttddy7733Mjo6Rm9vL0uWLOHTn76Ruro6BgeHOHToIIODgxw+fJj777+f9evXs2TJUubMmcsVV1xBX18/8+bNp7m5mbGxMV56afOMPldZPhycMy5EiUTChg0bsNnyWLNmDaFQiI0bN5Kbm4vRmENVVTX9/f3k5OQwZ84campqkMlkhEIhzGYzF198MYWFBdTVzSEUCtHQ0IDFYiEWiwGwf/8+iouLaWw8xCc+cTWjoyNYrTaUSiXXXHMtu3bt5IUXXuC2227DarXS3t7O/PkLEIQkgiBSU1OD2WzG5XIhCDOcWVwEhVROOBqmtrCKa1ZcCcD2Vgv/+fRPUCmUfPaC63nh7S0sLJvLLeuuJ5FM8PiuZzBqjTSUzaPfMcSwe4wbVn6C7W172Nf1NmW2EjRK9czKCiiVSrxeD2vWrOXaa69jcnKS5577Kw899Ft0Oi3V1dX84Q9/ZNWqlWze/BKTkw6efvrpjBvutddeY+PGjRQXF3P4cBPhcJjS0lIkkpkdr+l0elasWMEzzzyNVCplyZKlWK1Wuru7KCsr484776K5+TAvvPACq1ev5i9/eQaDwcAPf/hD7rrrLqxWK729vdx2261s3bqV7dt3UF1dlQk4mUnkcjmJRAKbzcpdd92N1Wpl0aKF3Hvvv6JQKLjjjjvYtu11pFIZeXl5PPDAAwhCklWrzic3N5empiZisRi33HILf/jDH2hqamTp0mWo1TOrf5VKxYoVK/jTnx5n166dVFRUUFlZya9//Wuefvpp9Ho9V131CZ566s/U1dXy1lt7GR4eYvPmzfT3D3DPPffw+uuvsXLlSpYsWUJrawsDAwOsW7cuE6RyJphO1OfZ5FQjPj+sSMRpzqmHh4f51re+xebNmwmFQlRXV/Pwww+zbNkyINUg9957L7/+9a/xeDysXr2aX/7yl9TU1GTO4XK5uOeee/jb3/6GVCrl2muv5ac//ekpL177fD6MRiODE8Pk5OQAoJIpUcoUJ23sd8qrHPtZOupOKpW+K1Lw+GOP5ugw3lgsRlNTI6FQmOLiIsrLK44JDoGUb7+vry/jzjk+GjF9vrQs79dpfL3tDG99lqobvojSYHrPVFJpOcOxCBIJqBWpl04kHiUQCaKSK9GqtEQTUeRSOUq5IvO9RCJBJVcSTyZIJBOolWriyTjekB+9SpsxYDMZdCKKIqFQCJlMhlqtRhRFAoEgoVAQjUaDTqcjHA4jl8uJRqMIQhJRTBk+jUZDOBxGrVYjlUrxeDyIoojJZMoE1cyUnJDSvdfrRRRFcnJyUKvVhMNhBEFAq9WSSCSIRqPI5XLC4XDmd3q9HolEQjweR6PREIvF8Hg86PX6zDrYSWWdaiNfdyvDr/+V6hvvQpljel9ZI5EIyWQSrVaLRCIhEong8/lQKBTk5ORkBmMKhQKv10symcRoNKJSqabaWcjcn9frxWQyodFo3lvWaSKKIolEAq/Xm1mf1mg0hEIhAoFARv+RSASZTEYsFsu4XOVyeeY7pVKJXC7H6/USj8cxm82zFt15vPwAXq+X3t6ejJcnjclkory8fEb74umSLsc0NDTE5OTkMd9JJBJKSoqxWm3HvMdm8tqxZIxoMn7M52q5ikgwjNFoxOv1Zt7vJ2NaMzC3283q1atZv349mzdvxmaz0dnZmQkVBrj//vv52c9+xu9+9zsqKir4l3/5FzZu3Ehra2tmtHbzzTczOjrKli1biMfjfP7zn+eLX/wijz322HTEmRZpBSSTKTeSTCYjkUhkQuAhZWTS7sR0yHwymcy8gLxeD3q94V0PbSAQoKKikkQijtVqO2G4vVwup6qq6pjfHd8pZqtDp897/GxJrVChVrwTRaZVat71fRqFTI5iKmGsUqbAZrC86/wzJatEIjkmmEEikWAw6DEY3hngpAc7J5oBHD0QslhmT05RFFEqldhstmO+S/cPSBkDhUJxUlnT36lUKvLz82dN1hNdX61WH/PZ0XIfvVUhLV8arVaLVvtO9OlMyyqXy991fZ1Od0yfSP//idr06OPS66UzLef7MTk5yeuvbyMejx2znbKqqoqioqITviPOBslkkiNHWmlsbMzIKZGkkoVfeOGF5OZaz7qhfS+mNQP79re/zc6dO9mxY8cJvxdFkcLCQv7xH/+Rb3zjG0BqJJKfn88jjzzCjTfeyJEjR5g3bx779u3LzNpeeuklrrjiCoaGhigsLHxfOT7IDOy1114jGAyyYcMGHn/8cURRxGAw0NDQQGdnB6II+fn5rFy5Ep/Px44d25HJZPj9ARyOSdasWUNtbR0ymYxgMIhcLufpp/+MWq3B7Xbx6U/fiFQqRaVSTS1wC6hUaoLBVF0ypVJJNBolJyfnA+2hmc4M7Pg2gGNnkEfPANMIgnBKI6/Z6tzp0WHa7fNe8hx93NmQ891tKp7QXfleM/yjeV9ZpzkDe29ZP3z6T8txtAypoq0fPv1nOT3Oygzsr3/9Kxs3buT666/njTfeoKioiLvuuou/+7u/A6C3t5exsTEuvvjizG+MRiMrVqxg9+7d3HjjjezevRuTyZQxXgAXX3wxUqmUt956i0996lPvum40GiV6VNHG6S4cpx8Ih8PB5OQkTqeDeDxOMBikuLiY4eFhtmzZwqZNV1BUVIzb7cbtdvPmmztQKBRs2HAx4+PjPPXUk+zZs4eent6pdRovGo2aWCyG1WolkYhnQqJVKiWxWJxIJEJFRQUej4eJiQnKyspwOp2sW7eOurq6ad3HB0EURYLBIAff3o8oiiyob6Cx8RCIsGLlqszoOxXskmDnzh0UF5dQVVWdca+m29Hn86LV6lAqlTMa7p+WE2BkZJjGQweprKrGaDRy4MB+SktLmT+//hh5fD4vO3fuYN26C9HrDUgkksyLL5FIEA6Hj1lTmmlZk8kk7e1H6O/vY8niZUxOTtA/0MeSJcsoKCjMHCcIAqOjw3R3d7Fq1WoUCmXmO4lEMlV4NTXznK02DYWCHNi/DyRQv2AhhxoPIooiq1adj1qtmTpWIB5P8Oab2ykuLqG6uuaY9k5tfvah1WqRyxWzIqsoioyNjtLYeJDSsjJyc60c2L+PktJS5s1bMDV7SR3n9XrZtetNVq9eR05OzjH6j8fjhEJBjEYTkDViH0emZcB6enr45S9/yde//nX++Z//mX379vHVr6b2g9x2222Z6KmjXSHpf6e/GxsbIy8v71gh5HIsFstJo6/uu+8+vve9701H1HcRi8XYt28fgpDE4XAwNDSE1+vF7XazceNGdu58E4vFQiQSpq+vD7/fz/DwMKtXryGRSOB0OjPrB+nsDpFIah1GEERcLjfxeJz+/n6WLVtKc3MLOTk5iKIwtbYgotVqGRoaYuXKlRQXF3+g+zkdBEGgpKSULa++jFKlwu/3UVtbN3UPApOTE3R0tGM2W4hFoxw8eICurg6WLl1OT083oVCIgoIC3tj2GmvXXcjcufNnZWFcEARkMhlWm419+95i7py5KBUKbLZUv0kmk3R0tONwTGC12hgfG+O1117Fnl9AUXExXV2dKBVKZHIZR1pbuPKqq7Fabe9z1dOX1WQy09HRzsFDB5BKpFitVvQGA4IgEIlEOHy4EVEUqayopLenB4/bzcJFS4iEw4yMDpNrsdLV3YlWq2XDhktQKmduc/DRJBIJiotL2bZtKzqdnkDAT01NLTJZSv8TE+N0dXVgNBqJRiMcfHs/3d2dLF26nN7eHgJ+P0VFxWzfsY3Vq9fOmv5FUUQml5FrtbJ/317mzV+ATC7DassDSUr/XZ0dTE5OkGu1MTkxwbbXX6WgoJCS0jI6OtqQyeQo5HKamw9z9SevxWq1zricWc4+0zJggiCwbNkyfvCDHwCwePFimpubeeCBB7jttttmRUCA73znO3z961/P/Nvn81FSUnJKv02P6Do7O4hGo1x44XoGB4c4cOAAer2OsrJyxsfHKSoqYufOXUSjES644AJyc63s3q3j8OHD9PT0EA6HqK2tY2JiAo1Gg8/nIxqNYDDkkEgkCAQC5OQYiMdjxOOJqbQ7eYyPjxMMBhkaGqK+vp7+/n6As+ID1+v1eL0eKiuqKC0pxeVysvPNHUQiESYnJtAbcmhvP8KSJcuQSKVYrTZGR4d5+8B+EskEublWOjra0ep05OfbZzyNELyTycRqtTE8NERdbR1Gk5n29jbe2rMLu70QQUjidDnxeDyYzRaUSiWFhUV0d3UyOjpCaVkZ3d1dGAw5mC0WTCZz5twzjUKhwGg0YtAbqKysYnJygsamQyTiCSLRCLkWK0eOtKTaq6oGnU6PTC6npeUwfr+fhQsXs3//XnRaLfn59szMbCZJuwcNhhx8Ph9FxSUUFBQyNjbGzjd3EA6FcTgn0esNtB05wqLFi5FKpeTl5zM8NMTBtw+QTCYxmUwcaWtFq9FSUFA4q/rPzbUyMjLM3LnzUwOE9jb27N5JYUEhSUHA43Zn9K9QKMi3F9DX18vExARFxSV0dbWjVmswGk3k5Mx8VGeWDwfTMmAFBQXMmzfvmM/mzp3L008/DYDdbgdgfHycgoKCzDHj4+MsWrQoc8zExMQx50gkErhcrszvj0elUn2glDUSiYTy8gqKiorRarV8/vOfB8hk3YjFoqhU6imXoAaDIeWKWrXqfOrq5gCphWupVEosFkMmkxGNRpFKpZnw5PTiczoCrqGhAZlMhk6np6OjncVTL4XrrrsOjUYz4+HH70XahTQ+PsaO7dswGo20tjZjMpqIx+MYDDmUl1cyOTlOSXEqa4RUKsVkMpNMJpHLZXh9HnQ6HbkWKx6PG5/POysLvGl3W0vzYdrbj2C12ph0TFJVXYPT6aRuzhykUindXZ0kEglGRkaIT7V/IplAqVTgmJxELpej1WpxuZyEQqGp2fDMursg5d7etu01wuEwvX09JBIJampqkUqlLFt2HpFIBK/Pw9DQIMPDQwiikElbJooibrcLk8mMBPB5vSQS8Rk3Ymn9T0xMsHPnDnQ6Hc3NTan1BVHEZDZTWVWNwzFBWXkFvT09yORylEolSUFAJpfh9XpRqlSYzRYCfj8ejweTyTwruRAFQaD5cBOtrS3YbHk4XY4p+SapqZ2DTCalu7uLaDTCwGA/8XgciURCLB7DIMvBMTmBXCZHr9fjcbsJhUIolapZ0X+Ws8u0DNjq1atpb28/5rOOjg7KysoAqKiowG63s3Xr1ozB8vl8vPXWW9x5550ArFq1Co/Hw4EDB1i6dCkAr732GoIgsGLFig96P+8i3WGPjkw63oWZXv852qi8MxM41vWQPuZkKX+O/3zJkiWUl5dnQrnTUWdHy3amUKlUNCxchEwqw5aXh9fjoby8krz8fGQyWeZlZDAYCIcjyGRSSkvLsFhyCQb8JJKphLWlZWWzkkboaMwWC/UNC9Hr9Gi0Wvx+P3V1c6fSL0FRcQlGkxmTyURlZRVarRaLxYLFYsXpmESnN5CTY6BwaqYwW0gkEmpq6ojFolitNuKxGMmpBL0qlRqtNk55eSWVldWoVCpyjEZkMhkymRSlUoXH42HBggYSiTgej2dW+4RSqWTBgnqkUhk2mw2v10tVVTV5eWn9p2Zqev3CTJh6aWk5FouFQCBAIpHAbi+gqqoaheLkAVMzgSXXQn39QvR6HTqdHr/fz5w5czPrWcXFJRiNJoxGEx6PG61Wi9VqIzc3F4djEoPegCHHSEnJcCbKOMvHj2lFIe7bt4/zzz+f733ve9xwww3s3buXv/u7v+NXv/oVN998MwD//d//zX/9138dE0bf1NR0TBj95Zdfzvj4OA888EAmjH7ZsmWnHEZ/OlGIZ4sTNe9MyHi6UYgn4/gotFNhNiP7ZpqzJeustOtpRCGeFTlPk4+SrFlOj7MShbh8+XL+8pe/8J3vfIfvf//7VFRU8L//+78Z4wXwT//0TwSDQb74xS/i8XhYs2YNL7300jGzmz/+8Y985StfYcOGDZmNzD/72c+mI8pHho/Cg/NRkDHN0RvAP8ycbpueDV18FPWfJQucRiaODwMfpRnYbHE6M7AsHxNOcx9YliwfFmZqBnbOJPPNkiVLliwfL7IGLEuWLFmyfCT52IbnfAQ9o6eBeMIkxVnONbJ9IMtHg5le4vnYGjCAYMBPKBw822LMCpFggLDWgMvrRh6Nkn19nVtIJBJCkTARbQ5OjwtZJHK2RcqS5aRotXp0ulOrNjIdPtYGTDu1h+jjiD/gIRAOYsoxotSbIBvDcW4hgtw9SSDsx5xjQmEwnm2JsmQ5KTNdiy/Nx9aAvZNJ++O5zCeVSJGIIjKpLJWWKhuFeO6QrhsnkSARRaRSGTLZx/ZRzpLlpHw83+5ZsmTJkuVjzzk1bDtRVeUsWbJkyfLR5JwxYOlovXStoHSNo5kkaxCzZMmS5cxxThiwtOFqamqaSkYssmHDxZjNqTIbRyd7PVHV3OM/SyaT78rCnc7PljViWbJkyXJmOCcMGKRKujz99NPccsstBAIBRkZGeOmlzQQCAZYsWcqOHTuQyWRIJBCPJ1i6dCnd3V1EIhFCoRA6nZ6CggJKSoppbW1FJpNTWlrKgQMHiMfjbNiwgaVLl35ojdipzDZPZLSn85uZ4lRlPdsJXafTRtOR9Wy1afraZ1PW2ZIz/ZuZ5qOSePpkScU/qPxn+113TgRxiKKIy+UkHA5hs9mYO3cuXV1dRKMxTCYzBw7sx+VyUVFRgcvlIifHQH9/H4FAkMLCQjweD7m5uRw6dIjnn3+B2to6Ojra6ejoIBgMkptrob+//0O7mTQtVzQRwxcJkBSSCKJIOBYhGA0TjIZJCMnMcaIoIgKRRIx4MoEIhGMRQrFw6ntEIvEogijM+D2nXb2BQAC3200ymQRSNeM8Hg/RaPSYe0omk7jdbiKRSGam7fP5iMdTOdbi8fi7fjOTCIKA1+vF5/MhCEKmuGkgECAYDCII77RRLBbD4/GQSCQAiEQi+P3+zD1HIpFMnbCZJH2+eDxVsiUWiyGKIuFwGL/fTyAQmKoa/k77B4PBzD0BBAIBwuFw5p7D4fCMb6I/uv/5p2qOJZNJBEEgGAxm2vXoNjr6HqLRaEYfaf0nEgkiU3vkzkRfjcfjGTn9fn9G16Io4vP5MveU/t1st2n62pCqXedyuY55HkRRJBQKZeSEVD9N3YM/U0YnGAwSDAYzz9hsyTpdzokZmEQiobi4BKvVxptvvpl5aUxOThKPx7Hb7fT29pFIxInHE0QiUeLxOOFwmGg0RiwWIxqNkkgkyMvLo6urC5PJhF6vn3pAogQCAQRBOCuVlt+LdAfrHu/j4dcfwxPwUldUw8bFF/HI648TioRQypRcu+pKVtQszWyI7psc4JcvP8zaeauoL53LH7b/maSQ5MbVnyKWiLOv+yDXr/wEBvWJ66KdrqyiKLJ161YeffRRRFHkvPPO4+qrr+bXv/417e3t2O35fO1rX6e8vJxgMMgDDzzAnj27sVptfPWrX6W1tZVXX91CfX0DN910E08//TTV1VVccMGFM1oXTBRFEokEjz/+OC+//BIAn/rUNZjNZn75y19O1aey8g//8A8UFxfT0dHBT37yP7hcLhoaFnLDDTfw+9//HrfbxS233IrRaOT555/n9ttvx2KxzNhMPq3/kZERfvrT/6W3t5eysnJuv/12Hn30Ufr7+1AqVWzatIlrr70WgO3bt/PQQw8Rj8e56qormTNnLr/73SNoNFruuusuenp66Orq5JZbbp3xwqzJZJItW17hscceQxRTNQjPP/98fvSjHwGg1+v40pe+TENDA4FAgPvvv5/Ozk7UajWXXHIJBoOBF154gTlz5nDbbbfxt7/9FavVxmWXXTbjz6Yoimzbto1HHnkEQRBYvnw5VVVVPP7445mB0z/+4z9y/vnns3nzizzxxBMIgsDatWtZunQZv//979FoNNx55510d3fT2dnJ5z73uRlt07T++/r6+J//+R88Hg82m42vf/3rFBQUcODAfn76059x++23c+GFF5JMJnnmmWf461//SiKRKhT66U/fyI4dO5BKJXz5y3cyOTnJoUOH+NznPof2LO+zPWcMmNFo5M4772R4eHiqBH2qpLooiuTl5TF37jw0Gg0LFtQjl8uRSCCRSKJUKlm8eDEajYZVq1ZhtVoZHx/HbDYjkUhYunQpMpkMuVx21qfTJyOejLO1eQc52hw+s/Y6fvnywxRY7PjCftbMWUmJtYg5RTWpg0URh9/FE7uew+l34Q646ZsYRCaVoFcb2Nd9kIHJIS5ZeCE6lWbGZY3FYrzxxjbmzZtLQ8NCHnnkERQKBYODg/zLv/wLDzzwAFu2bOH222+ns7OTffv28q1vfYs///nPPPvss/h8PhYvXkJTUyNPP/00w8PD3HDDDbNS1NLn8/Haa69x2WWXEw6Hee2111i1aiVKpZKrr76akpIS8vPzicfjvPDCCxiNJr74xS9x//33o9Vq8Pt92O12du7cidvt5vzzV2EymYCZdc0kk0m2bduG2+3hX//1Xn7605+yY8cOHA4Hq1atora2jkWLFiGVSonH4wSDQa6//jomJx1s3fralJchVYn7lVde4ciRI3zhC3d8oCrpJyMSibBly6ssW7ac4uJinnzyCUpKSojFolxzzbUUFRVSXV2dqWo9MTHO8uXLmT9/PtXV1fzhD79n0aJFNDU18Ze//IWenm42bbpyVgaWiUSCbdu2UV1dzfLly3nood+wYcNF3HvvvRw4sJ+nn36G4uJi4vE4r766lWXLllNeXsaf/vQnwuEIFouFaDTKli1bOHLkCLfffvustKkgCDQ2NuLxeLj77rv4+c9/TmNjI7FYjN///g+43S48Hg8AcrmcSy+9lKVLl/Laa1vZtWs3kUgEg0FPJJKStbu7m89+9rOZQsBnk3PChZgmJyeHuXPnUlWVquBbWVlJVVUVBoOBOXPmUFZWRl1dHVVVVVRWVlFbW0t5eXnmu9raWiwWC3PnzsVut5Ofn09NTQ2VlZWUlpbNeHn1mUIqkWLS5jDhmeTIUAfBSAitSoNcKudQXzO/2fJ79nYfTLmx4lGe2v0cSSFBmbUEp99NcW4BFr2FhJBk2DWGxWBh3DNJ11gfwgy7ERQKBXPnzuPVV1/lwQcfpLi4iNLSUgKBAI2NhxgfH8dgSKWkCYfDaDRaKiurKCoqRhCSbNy4kY6OdsrKymhsbKSmpobt27dnHtCZlFWv11NTU8Mf//gHnnzySebMmUNurpVQKMSLL77Ij3/8IwYHB5FIJJjNJoaHhzl8uIlgMMCCBfXU1NQQDAbxeNwoFHL8/gCNjY0z7kZMXd+Mx+OmsfEQk5OTaLVadDodjY2N/OIXv+D5558nmUwil8tZv349PT29PPnkExQVFXLhhesRRRGz2UJbWxvl5WUcPtxMb2/vjLuRVCoV8+fP529/+ysPP/xbyssryM/PQxRh69at/PCHP6KpqSkzQ9XrDTQ3N/Pggw/w8ssvc+mlG+np6aaysoKDB9+mrm4OO3bswOl0AjOrf7lczvz583njjW088MAvKS4upqysnLw8G3v2vMWll15KSUkJCoWC+voFvPDC8/zqV7+iurqGTZs2IZVK0Gg0dHZ2Ul5expEjrfT29s6YfGlSVcOrcTqd/OhHP8bn85OXl8cvfvELzGYTeXn5jI2N4vP5Mn1Fr9ezd+8+rrvuOi688EJUKjUajYbe3l4KCwvp7Oygq6vrrLsRzwkDlnb3TExM4PV6cblc+P3+U/59IpGgq6uTQCDwrvOOjo4yPDx87PrRcX/ONjKpjMsWXcTquSt5du+LzCuuZX5JHVcsvZR/+sRXOK92CW0jXYz7HIx5JvAEfXgCXpoHjnCgu5ER1zifX38TSyoakEokeIIeOkd7eHznM7gC7hmVNRwOc/Dg23ziE1fzta99jYGBQUpKSrj99tt54YUXUKmUrFy5komJCURRJB6PEQ6HCYdDKBRK1qxZw7e//R38/gBVVVXs2rWTt97aw7PPPnuMn38mcLvdHDlyhC9+8UvceuutNDU1odNp+epXv8r3v/99FAoFXV1dTExMsGnTlWzatIk//vEx6usbOP/88/nyl+/ksssuJxqNkUymRskPP/xbRkZGgJl72UqlUtatW8dnPnMzTz/9NFarlfPPP5+LL76Yf//3f+fqq6+msbGRkZERJiYm6OzsZP369XzlK/fQ3t6BQqHg29/+NmVlZRiNRrq6umhvb+dXv3oQn883IzKmCYfDvP32Aa6//nruueerDA4OEolE+dznPscPfvCDzIt+dHSUcDjM+vUX8R//8R9cd931tLQ0U19fzz//8z8TDkeorKxi586dHDx4kKeeejKzzjdTRKNR3n77AFdccQVf+9rXGB4eZmxsjD179uB0OrniiitwuVyMj4+zf/8Brr/+Bu6++yt0dnag0+n49re/Q2VlJWq1iu7uHo4cacu06Uy+NwRB4NChRkpKivl//+//kZdnY/v2NwiFQvT19dPc3MyWLa9y+PBhRkZGiEajvPrqq6jVatauXUtBQQHf/OY3mTt3LhqNhpGRETo6OvjVrx7E5XLNmJynwznhQoTUIvTPf/5zFi1ahMMxyfLly7HbC0gkEqjVasxmM2NjY8jlcpRKZWahW6FQEAwG+P3v/8B1112H3W4nHA6Tl5eH1+vlueeeQ6GQc/XVn8Tr9aJSqcjJycHlcmGxWMjJyTmrkYnpSCOdSodBo6civ4zPXnA9EiS82riNl95+lUAkyHXnf4Lfvv4484pq+NYn7yGeTPDoG0+gVWk5v3Y5w+5R9na9zbUrruLN9rfoHu1Fp9Yin+EURgqFgvLycnbu3MWRI0ewWCzYbDZEUUSvN/DlL3+ZiYkJfvObh7j77rvRanV8+9vfJhgM8NWv/j0SiYRXXnmF/Px81q1bR0dHO263m5ycnBnXgUajobCwkOeff554PE51dTXBYIhf/epXGI05yGRylEoF//qv/8rf//3fU1hYyNy5c7nrrrvQ6/VMTEzwyiuvcMMNN3D4cBO7d+9Bo1Ejl89cm6b1r9Vqsdvt5OZa+frXv47ZbOb111/niSeeIBDwc/XVV/PEE39CFFOeivQLrKSkBJPJRHt7O+3t7Xz+85/nscceY2xsjPz8/Bl3zaX0X8Ebb2znwIG3sVqtyGQyHn30dzz11JP4fH6uuGIT//mf/8lFF13Evn37ePzxxwiHw3ziE1ehVCp55ZWX0et1XHbZZfT09OB0Oqmrq5txN7JCoaCiopJt216no6MTs9lMMpnkxRc3c9VVV2E2m/nBD35AVVUVJSUlbN/+Bmq1BrvdjlarpbW1ldbWVm677TYef/xxxsZGsdvtM6p/SA1gSkpKcLlcPPLII3g8HlauXMVXv/r3BINB7r//flauXIFMJuN73/set99+O9u3b+czn7kJo9GIRCKhq6uLgwcP8vnPf57nnnuWgYEBzGbLjMs6Xc6JiszpqKof//jHhMNhNm3aRF1dHf/93/9FcXFKsYsWLcThcOBwODAajYyNjZFIJKmsrESj0dDX18fixYvp7u4mHA5TUVHO2NgYsVgcnU5HPB5HFEWi0Sg2m41QKIRGo+GWW25BoZj5StHTqcgsiiKCKDLiHkOlUGI1WADwBH0MOAYx6UwUmu2MeSbQqjTk6s2IiEz4nMikUqx6C96wn1AsTH6OjVAsTPdYLwXmfGw51lTeyRm4p6Mjy9rb24lEItTU1GCxWJicnCAUClNaWkowGGRsbIyysjJ8Ph+dnZ3YbDYqKioAGBoaIjc3F61WS29vLz6fj7lz52YWx2cyOMLn89Le3oFUKqGubg4ajYaBgQEmJiaorKxEp9MxODhIUVERTqcTtVpFXl4+EomEQCCA0+mkqKiIWCxGa2sr+fn5lJSUvLec06zInPYEjI6OkkgkKC4uRiqV4vV6M7OBysoqJicnkUgkWK1WOjs7iESi1NbWYjAYmJycRBAE8vLycLvddHV1UV1dTW5u7oy3aSgUor29jVgsTm1tLUajkdHRUQYHBygqKiYvL4+BgYGMcevo6ECj0VBTU4NMJmN4eDgTZDUwMIDT6WTevHmZNZuZehbTkaMdHe0EgyFqa2vRarUMDqa8BiqVioGBgYy7tqOjg1gsRl1dHSaTicnJSZLJBPn5dlwu16y2aTKZpL+/n5GREYqKUm55mUyGIAgMDQ1iMOQgl8sZHR0hN9eK0+mkrKwssybncDiIxWLY7Xa8Xi8dHR1UVFSQl5d3WrLOVEXmc86Aeb1eCgsL+exnP8svf/lLVq1axa5du1AoFCxcuJCBgQFycnI4dOgQSqWSYDDIrbfeyksvvYTVamVwcID16y9idHQUh8NBSUkJTqeTRCJBNBqloqKCAwf2s3TpMsrKymhoaJgKCjl7BizdBif8HJHTMT9H/24m7+1oV+zx5z56Jnv8/7+XHEcfeyZkPdG/j9/ofqI9OKcs5zQN2NEynezz4+X7sLXp8df8MMv6YZczff60zk8kb/rvk+0V+6CyzpQBO2dciPF4HJVKyQUXXMDw8DC7du1EqVQQjUbRaNQsXbqUjo5OtFotl1xyCVarFZ1Ox+hoalqv0WiorKzE4/HQ2trKihUr6O3tpbOzE51Ox8TEOG63G7/fT011DY2Nh8jPz5+V6LfT4WQd8Xgj9F4vkPf63UzLeaLzvtcD816G4Z3KBDPLyWQ9PkPLiY4/oS5m4eV1/LXf65rT2dg6W7J+UP2f7JxnQ//Ht+n7te9stumJOFquE93L+8n6YQhYO2dmYGn3Xtpnn95gKpPJMtFXiUQCqVSKQqHI5EwURRGpVEo0GkUul2c2VqpUqsw6mdfr5YUXXsBiSbnmLrroIhQKBSqVKnO9D8MMLJlM4nI50Wp1RKMRhoeHsNnyyM+3H2PABEGgubkJrVZLVVXNu/JGxmIx5HL5VOaS2cnEEI1GcbtdmM0WxsZGCQYDlJSUYTAYjjk2Eolw8OB+5s+vJyfHeMxLQhRFYrEYKpVqVh64o92IyUQSjVZLX18PcrmcsrIKFApF5rhUP/HQ3d3FwoWLj/lOIpGQSMQRBBGlUgnM7AwsLacgCDidjoz+R4aHsdpsJ9R/S8thdDodFRVVZ1X/FksuY6Oj+PxeSkvLjxmRp/X/9oH91Nc3YJha5zyT+hdFEb8/tXFep9PR19eLXJ5ax5XLj9X/+PgYQ4MDLFy0JKPntP7TSxCnpP/TkBOm9O+YRKfTE4lEGBoewmazYbcXnFD/er2B8vKKd/QviiCRzIj+szOwaZBu5JNtEFQoFCSTyWM6z/GL0+nfHr1omf5Mo9Fw8803IwhJFApl5uV0/PXPNj6fjxde+BvLl59Hc/NhamrqeP31rVx66eWYzWZCoRBdXR2o1RrGx8fwuD1MTk6yYEE9IyMj+HxeCgoKefPN7cybN5/6+oWzsr9GEES6u7vYv/8tli09j+bmJkrLynnjjdfYuPEKZDIZY2OjjIwMYbPl09/XRzgUJj/fTklJKd09XchkMpRKJfv37WXjZVeQl5c/K8E08XicN3e8gUajRS6XE4vHCYWCBIMh6usbEASBnu4ugqEgxcUltLY0EwwEqKmpRSKRMDg0iNVqo7+/l0gkwvr1F8/45uA0Pp+PF1/4G0uXLefIkVZqamp57bVX2bjxciwWC4FAgK7ODjRaLU6Hg87ODibGx5k3v56xsVG8Hg92ewG7dr3J3HnzqK9vQCqdeSOWbrP9B/Zx3nkraDx0kNKycrZvf51LL70chULO+Pg4w8NDmExmunu6CIdD2AsKKCkpo7u7C7lchlKpYt/ePVxy6WXk59uBmX8Wk8kEO9/cgVQmRavVEovFiIQjRMJh5s1fkLmXQDCIVqOh9UgLwVCQmppUUMng4AC5llwGBvsJhyNs2HAxKtVs6d/Liy8+z/LlK2hpOUxVVQ3btqWeKbPZTCAQoLurE7VGjWNyku7uLsbHx5g/fwFjY6N4PB4KCgrZuXMH8+bNZ/6CemSzoP/pcE4YsPQIqKenh9HREWpr62hrO0JpaSklJaWEw2Fef/119Ho9Wq2W5cuXA6mFz66uLjweD3V1dZnNyydyAymVymNGeR8Wo3U0Op0OnVaL3+cjEPBTVFRE8+FGenu76eiIo1AoONzUSH3DQgA0Wg29vT1Eo1F8Pg8F9kL2799LPB7HZDLPmltGKgWTyYRUKmPSMYlcriAvL58jR1ppb28jGg0zMT6O1+vFaDSnXh46HYcPNzIw0I/BYMDhdKDT6lAqlRiNphmXM41cLifHaCIUCjLpmGRO3VzcHhmDA33EYhG0Wh1vHzhAYWEhRUXFKJRKYvE4TYcbCQQC1NbWsXPnDnRaLVarDaVS8f4XnSbpvmowGNDpdPj9fvw+HwUFhTQ1HqKvt5fOznZkMjmHDr3NokVLkEilqNVquru7CAaD+AM+CguKePvtfYiigNlsYTaq7Kb0L8VkNqOQy/F4PMgVCmy2PFpaDtPW1koiEWdifAKfz0d9fQNymQy9wcChQwcZHR1Bo9EyPj6GVqtFrlBgNJpm7XmUyxXk5OTg9rgZHR1lzpy5uF0u+gf6CEfCaDQaDh18m/x8O6qiIuRyBbFYnMbGg4RDIermzGXPW7vRaDRYLLmZWdtMkta/Xq9Hp9MRDAXxeL3YCwpoaW2mv7+XtrZWlEolTU2HqK9fiEQiQafV0dvbQyQSxu12U2Av4O0D+0AUyc21Ip2lKsvT4ZwwYGkGBgYYHBygrm4O+/btx2y28Oabb6JUKhkcHECv15OXl09rawsTExOYTCaamg5TW1vLzp07qa6uJhwOE4/Hyc3Nxel0EI3GKCoqZGhoCIlEyvLly9FoNGc1dP5kJJNJYrEYSpUKjUbD5OQEUpmMwsIi1Go1wWAQb5mHtrYj6HQ6NGoNUol0aq9VBJF3DHXafTrTpM+ZSCSIRaMYc4yMj43idrkwmUwUFxcjiqDV6mhvb6OrqwMhKaDRaBBEgXgiTiKZQKlUIZ+aWafdSLOBKIrE43ESiSQWSy5ut4tAIIC9oJCqqmoEQaBuTh3t7e2Z6EOlUonXGyIajSIKIiqlCplMTiye2g8mlcpmtP8c3abRaBSlUpkaZTsmkcllFBQWolZrCIVS+Q/b29swm80olcpMdo5IJOV+V6pUhMPhqT1VIsxI/OnJZdVoNMRi77iTi4tLkEik6HUG2tqO0NPTTVIQ0Gl1CMnklKs/5b6XyxUkkwlisdisZY0QRZFYPIYgCFjMFlxOJ8FQELu9kMrKSgRBIBQM0tZ+BCQglUpQqVSEwyn9JxNJlAoFMpmM+NR5ZiPlFUAyKRCZWkbR63Q4nQ4kEsjPt6NWq1N5JX0+2tuPYDZb0Gp1yKTSTCo9mVyOQqkkHA5nco+e7XfcOWPAUv7/VLTgrl07SSTiHDz49pQCUjv6Y7FUTsPu7h5GRoZZtep8JBIJbW1tWK1Wmpubcblc2Gw29uzZTXV1Nd3dPbS3t2M0GpHL5Rn34dlW7ImIRCKUlJSi1+u54IL1DA8Ps3r1Wuz2gkwWEZPJTHFxKdFoqoOaLRby8vIJBYNEY1HOP38Nw8PDxGfJgAGZpLiVlVXk2+1otBoCgQAXXLAek8mcWXcoLS3DarUyMjyMWq2msrKasrJyRoaHKSgopKCgkNbWFgIBP3q9flZ0kkwkyMnJQafVUVlVzcBAP5bcXGpr61Cp1MRiMbRaPQ0NCzGZzVRUVKDV6jEajZm9h+efvxqpTMbAQD+xWOxdLuiZIhQKHqX/ixgZGWb16nUZ/UulqSwMJSWlRKdeUDk5Rmy2PILBIPF4LKX/oSFisSiCIDIbqT8FQSAej1NWXoHVamPRoqUEgwEuvOCiqTyR4Pf7KC4uwWJJrZEqVUpqamupKK9kaHgIu91OYWERR460EgoFZ2UfIEAiHicnx4hOq6eqOqV/q81GTU0dKpWKeCyGTq9n0aIl6PUGDIYc9LqU/k0mE5MTk6w6fw1SqYTBgQES8fis6r+0pBSdTse6C9YzOjrCmjXv6N/jdmM0GikqLiEaCSOS8tpYLLkUF0eJRMKsWrWakZGRjAE725wzQRyBQICHHnqIDRsuYmJigq6ubkKhECtXrmR8yh3ldrtIJgUKCgro6Oigvr6eiooKdu58E6vVxtDQIKFQiGXLltPR0YHVmks4HJ5aO5Nw2WWXkZ+ff0aM13SDONLt8H7RhUcfe6LQ6veKuJspTiznyUf7x4cHH33sbIUmv7es7zrqGHmOlvVE7fu+cp5GGP37yXqiCLlT6Suz1abpv0/l/Cfqq+k2P5v6P1mbSiTpeIjT1P9pynn03+8n63s9/zPRptkgjmkSiUSorq7GZEotVi5YsACFQkE0GqGyspJoNEo4HEalUhGNRrDb7VitViYmJli37gJyc3MzpTBKS0sxGAzIZFJ6enpRKBQ4HA46OjrIz88/27d6UqbzcJyog56pWeWJ5ZzOS/TMyXxqbXpyec5k+76frGdicHIqTPfleOLjz8xa9Om2afrjM6X/U2nTE8nyfp+dbc4JAyaRpLILXH755UilUoqKik75d0ePWI7+3Zo1awCoqqqio6OTiooKysvLZ1z2LFmyZMlyYs4JAwbHhsZPZwRxsmPTn+fn2zMhutM99wcmvTcjyznMh2c0nCXLmebsx0F+xEm7EGbLd30iRFFEplAiJOIkwkGyJuzcIq3veNCPVKFAIpO+a60lS5ZzgXNmBvZxQ2m2ItfqcR85iMqSh1ShPNsiZTmDxIN+XM370RVXIpuFwqJZsnwUyBqwjyhyrZ6CNRvpe+FPCLEouQtXZl9k5wCiKBL3uxnf9SrJWATb0rVIZPKsIzHLOUnWgH0EkUzF4Roq5lBx9S2M7thMxx//b+rLsytblllGBIlUgrFqHiUbr0NptqZU/iGKDMuS5UyRNWAfVSSpfPD60mqqb7yTRCiAEJ/ZirNZPoRIJMhUGmRqLRKpNGu8spzTZA3YR5mpmZhEJkdhMJ1tabKcDbLGK8s5zMfGgInnaCzeh2lTYZYsWbKcKjMROfvxCaMXBThHjViWLFmyfFRIGS4xa8CORhCFTJHKLFmyZMny4UUQBUSED3yej40BE0UQhMQxyTWzZMmSJcuHh/T7WUgmZsRh9rFZA4OUVScZRyZ7pxxBdo0oS5YsWc4uR2fDTybjMzbJ+FgZMFEEgSRiQkAilaXKnWc3RmXJkiXL2UNMBdmJQhJBSKYC7sSZiViYlgErLy+nv7//XZ/fdddd/N///R+RSIR//Md/5E9/+hPRaJSNGzfyi1/84pgSIwMDA9x55528/vrr6PV6brvtNu677z7k8g9mS5OIJI9ulWQSSTL9bdaIZcmSJcvZQTzqvzPLtKzGvn37SCYzVoHm5mYuueQSrr/+egC+9rWv8cILL/DUU09hNBr5yle+wjXXXMPOnTuBVEn7TZs2Ybfb2bVrF6Ojo9x6660oFAp+8IMfzOBtpRBP8H9ZsmTJkuXjwQeqyPwP//APPP/883R2duLz+bDZbDz22GNcd911ALS1tTF37lx2797NypUr2bx5M1deeSUjIyOZWdkDDzzAt771LSYnJ6cqG78/J6rInCVLlixZPtpMtyLzaUchxmIx/vCHP3D77bcjkUg4cOAA8Xiciy++OHPMnDlzKC0tZffu3QDs3r2b+vr6Y1yKGzduxOfz0dLSctJrRaNRfD7fMX+yZMmSJcu5zWkbsGeffRaPx8PnPvc5AMbGxlAqlZhMpmOOy8/PZ2xsLHPM0cYr/X36u5Nx3333YTQaM39KSkpOV+wsWbJkyfIx4bQN2EMPPcTll19OYWHhTMpzQr7zne/g9XozfwYHB2f9mlmyZMmS5cPNaYX+9ff38+qrr/LMM89kPrPb7cRiMTwezzGzsPHxcex2e+aYvXv3HnOu8fHxzHcnQ6VSoVKpTkfULFmyZMnyMeW0DNjDDz9MXl4emzZtyny2dOlSFAoFW7du5dprrwWgvb2dgYEBVq1aBcCqVav4z//8TyYmJsjLywNgy5Yt5OTkMG/evGnLoZGr0Mizhi1LlixZPg7IpLJpHT9tAyYIAg8//DC33XbbMXu3jEYjd9xxB1//+texWCzk5ORwzz33sGrVKlauXAnApZdeyrx587jlllu4//77GRsb47vf/S533333ac2wFDIFiqOybmTJkiVLlnOHaRuwV1/9/9u7/5io6z8O4M/D406I7k7F46A8oKTIEEaQ12WtP7hlxPo115yjDaMfg86Fm2tarWhthVtbrVqj9QvbbLFqQWYqISBlQwyCBG2ISeKceCVDjjIR7vn9g/H5+gG+fuXLvvfxfb0e2234eb/HXk/f8nnt8P2+zx709/ejuLh42tgbb7yBqKgorF69WneQedK8efOwY8cOlJaWwuv14qqrrkJRURFefvnluaUQQgjxjzOnc2BGmTwHdjnnBIQQQqhjNvd3JT8LcbLnynkwIYSILJP39ct5b6VkAztz5gwAyHkwIYSIUMFgEHa7/ZJzlGxgCxcuBDDxwcD/LaAKhoeHsWTJEpw4cUL5X4lGUhZA8lzJIikLEFl55pKFJILB4GWdMVaygUVFTZy/ttvtyi/0xWw2W8TkiaQsgOS5kkVSFiCy8vyvWS73jUnEPJFZCCHEP4s0MCGEEEpSsoFZrVaUl5dHzMdLRVKeSMoCSJ4rWSRlASIrT7iyKHkOTAghhFDyHZgQQgghDUwIIYSSpIEJIYRQkjQwIYQQSpIGJoQQQklKNrB33nkHKSkpmD9/Pjwez7SnPF8JvvvuO9x3331ISkqCyWRCbW2tbpwkXnzxRSQmJiImJgY+nw+9vb26OYODgygsLITNZoPD4cBjjz2GkZGRMKaYUFFRgVtvvRVXX301nE4nHnzwQfT09Ojm/P333/D7/Vi0aBHi4uKwevVq7Wnbk/r7+1FQUIDY2Fg4nU4888wzGBsbC2cUAEBlZSUyMzO1Twnwer3YtWuXNq5Slqm2bNkCk8mEDRs2aNdUyvPSSy/BZDLpXunp6dq4SlkA4OTJk3jkkUewaNEixMTEYPny5Whra9PGVboPpKSkTFsbk8kEv98PwKC1oWKqq6tpsVj40Ucf8dChQ3ziiSfocDh4+vRpo0vT2blzJ59//nl++eWXBMCamhrd+JYtW2i321lbW8uff/6Z999/P1NTU3nu3Dltzj333MOsrCzu37+f33//PZcuXcq1a9eGOQm5atUqVlVVsbu7m52dnbz33nvpdrs5MjKizSkpKeGSJUvY0NDAtrY23nbbbbz99tu18bGxMWZkZNDn87Gjo4M7d+5kfHw8n3322bDn2b59O7/55hseOXKEPT09fO655xgdHc3u7m7lslzswIEDTElJYWZmJsvKyrTrKuUpLy/nzTffzFOnTmmv33//Xcksg4ODTE5O5rp169ja2spjx46xrq6OR48e1eaodB8IBAK6damvrycANjU1kTRmbZRrYCtWrKDf79f+PD4+zqSkJFZUVBhY1aVNbWChUIgul4uvvfaadm1oaIhWq5WffvopSfLw4cMEwB9//FGbs2vXLppMJp48eTJstc8kEAgQAJubm0lO1B4dHc3PP/9cm/PLL78QAFtaWkhONPSoqCgODAxocyorK2mz2Xj+/PnwBpjBggUL+MEHHyibJRgMMi0tjfX19bzrrru0BqZanvLycmZlZc04plqWTZs28Y477viP46rfB8rKynj99dczFAoZtjZK/QpxdHQU7e3t8Pl82rWoqCj4fD60tLQYWNns9PX1YWBgQJfDbrfD4/FoOVpaWuBwOJCbm6vN8fl8iIqKQmtra9hrvtjZs2cB/PupAO3t7bhw4YIuT3p6Otxuty7P8uXLkZCQoM1ZtWoVhoeHcejQoTBWrzc+Po7q6mr8+eef8Hq9ymbx+/0oKCjQ1Q2ouTa9vb1ISkrCddddh8LCQvT39wNQL8v27duRm5uLhx9+GE6nE9nZ2Xj//fe1cZXvA6Ojo9i2bRuKi4thMpkMWxulGtgff/yB8fFx3V8AACQkJGBgYMCgqmZvstZL5RgYGIDT6dSNm81mLFy40NCsoVAIGzZswMqVK5GRkQFgolaLxQKHw6GbOzXPTHknx8Ktq6sLcXFxsFqtKCkpQU1NDZYtW6Zklurqavz000+oqKiYNqZaHo/Hg61bt2L37t2orKxEX18f7rzzTgSDQeWyHDt2DJWVlUhLS0NdXR1KS0vx9NNP4+OPP9bVo+J9oLa2FkNDQ1i3bh0A4/6dKfk4FWEcv9+P7u5u7Nu3z+hS5uTGG29EZ2cnzp49iy+++AJFRUVobm42uqxZO3HiBMrKylBfX4/58+cbXc6c5efna19nZmbC4/EgOTkZn332GWJiYgysbPZCoRByc3Px6quvAgCys7PR3d2Nd999F0VFRQZXNzcffvgh8vPzL+uZXf9PSr0Di4+Px7x586btbDl9+jRcLpdBVc3eZK2XyuFyuRAIBHTjY2NjGBwcNCzr+vXrsWPHDjQ1NeHaa6/VrrtcLoyOjmJoaEg3f2qemfJOjoWbxWLB0qVLkZOTg4qKCmRlZeHNN99ULkt7ezsCgQBuueUWmM1mmM1mNDc346233oLZbEZCQoJSeaZyOBy44YYbcPToUeXWJjExEcuWLdNdu+mmm7Rfiap6Hzh+/Dj27NmDxx9/XLtm1Noo1cAsFgtycnLQ0NCgXQuFQmhoaIDX6zWwstlJTU2Fy+XS5RgeHkZra6uWw+v1YmhoCO3t7dqcxsZGhEIheDyesNZLEuvXr0dNTQ0aGxuRmpqqG8/JyUF0dLQuT09PD/r7+3V5urq6dD+M9fX1sNls037IjRAKhXD+/HnlsuTl5aGrqwudnZ3aKzc3F4WFhdrXKuWZamRkBL/++isSExOVW5uVK1dOO25y5MgRJCcnA1DvPjCpqqoKTqcTBQUF2jXD1mZO21AMUF1dTavVyq1bt/Lw4cN88skn6XA4dDtbrgTBYJAdHR3s6OggAL7++uvs6Ojg8ePHSU5sn3U4HPzqq6948OBBPvDAAzNun83OzmZrayv37dvHtLQ0Q7bPlpaW0m63c+/evbpttH/99Zc2p6SkhG63m42NjWxra6PX66XX69XGJ7fQ3n333ezs7OTu3bu5ePFiQ7Y3b968mc3Nzezr6+PBgwe5efNmmkwmfvvtt8plmcnFuxBJtfJs3LiRe/fuZV9fH3/44Qf6fD7Gx8czEAgol+XAgQM0m8185ZVX2Nvby08++YSxsbHctm2bNkel+wA5sevb7XZz06ZN08aMWBvlGhhJvv3223S73bRYLFyxYgX3799vdEnTNDU1EcC0V1FREcmJLbQvvPACExISaLVamZeXx56eHt33OHPmDNeuXcu4uDjabDY++uijDAaDYc8yUw4ArKqq0uacO3eOTz31FBcsWMDY2Fg+9NBDPHXqlO77/Pbbb8zPz2dMTAzj4+O5ceNGXrhwIcxpyOLiYiYnJ9NisXDx4sXMy8vTmhepVpaZTG1gKuVZs2YNExMTabFYeM0113DNmjW6c1MqZSHJr7/+mhkZGbRarUxPT+d7772nG1fpPkCSdXV1BDCtRtKYtZHngQkhhFCSUv8HJoQQQkySBiaEEEJJ0sCEEEIoSRqYEEIIJUkDE0IIoSRpYEIIIZQkDUwIIYSSpIEJIYRQkjQwIYQQSpIGJoQQQknSwIQQQijpX39kgBcNylTiAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from PIL import Image\n", + "import requests\n", + "from io import BytesIO\n", + "import matplotlib.pyplot as plt\n", + "from llama_index.core.multi_modal_llms.generic_utils import load_image_urls\n", + "\n", + "image_urls = [\n", + " \"https://venturebeat.com/wp-content/uploads/2024/03/Screenshot-2024-03-04-at-12.49.41%E2%80%AFAM.png\",\n", + " # Add yours here!\n", + "]\n", + "\n", + "img_response = requests.get(image_urls[0])\n", + "img = Image.open(BytesIO(img_response.content))\n", + "plt.imshow(img)\n", + "\n", + "image_url_documents = load_image_urls(image_urls)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22fcd5fe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The image shows a table comparing the benchmark scores of various Claude 3 AI models (Opus, Sonnet, Haiku) against GPT-4, GPT-3.5, and two versions of Gemini (1.0 Ultra and 1.0 Pro) across different academic subjects and tests.\n", + "\n", + "The subjects covered include undergraduate and graduate level knowledge, grade school math, math problem-solving, multilingual math, code, reasoning over text, mixed evaluations, knowledge Q&A, and common knowledge.\n", + "\n", + "The scores are presented as percentages, except for the \"Reasoning over text\" row which shows raw scores out of a certain number of shots. The Claude 3 models generally perform comparably to GPT-3.5 and GPT-4 on most benchmarks, and outperform the Gemini models on the tasks where scores are available for comparison.\n" + ] + } + ], + "source": [ + "response = anthropic_mm_llm.complete(\n", + " prompt=\"Describe the images as an alternative text\",\n", + " image_documents=image_url_documents,\n", + ")\n", + "\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "id": "0ff6a440-ed96-4ae7-88e4-26929822874c", + "metadata": {}, + "source": [ + "## Structured Output Parsing from an Image\n", + "\n", + "In this section, we use our multi-modal Pydantic program to generate structured output from an image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84c712df-9457-4d25-8b6c-525aaf00f45a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/jerryliu/Programming/gpt_index/.venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "source": [ + "from llama_index.core import SimpleDirectoryReader\n", + "\n", + "# put your local directore here\n", + "image_documents = SimpleDirectoryReader(\n", + " input_files=[\"../data/images/ark_email_sample.PNG\"]\n", + ").load_data()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ddba6422-4fb0-4a0a-b89e-03eeb0ab840a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from PIL import Image\n", + "import matplotlib.pyplot as plt\n", + "\n", + "img = Image.open(\"../data/images/ark_email_sample.PNG\")\n", + "plt.imshow(img)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4604a04-10b8-4284-be99-fd219fb43492", + "metadata": {}, + "outputs": [], + "source": [ + "from pydantic import BaseModel\n", + "from typing import List\n", + "\n", + "\n", + "class TickerInfo(BaseModel):\n", + " \"\"\"List of ticker info.\"\"\"\n", + "\n", + " direction: str\n", + " ticker: str\n", + " company: str\n", + " shares_traded: int\n", + " percent_of_total_etf: float\n", + "\n", + "\n", + "class TickerList(BaseModel):\n", + " \"\"\"List of stock tickers.\"\"\"\n", + "\n", + " fund: str\n", + " tickers: List[TickerInfo]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6f87b00-b8f1-4455-97b3-6384429364e2", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.multi_modal_llms.anthropic import AnthropicMultiModal\n", + "from llama_index.core.program import MultiModalLLMCompletionProgram\n", + "from llama_index.core.output_parsers import PydanticOutputParser\n", + "\n", + "prompt_template_str = \"\"\"\\\n", + "Can you get the stock information in the image \\\n", + "and return the answer? Pick just one fund. \n", + "\n", + "Make sure the answer is a JSON format corresponding to a Pydantic schema. The Pydantic schema is given below.\n", + "\n", + "\"\"\"\n", + "\n", + "# Initiated Anthropic MultiModal class\n", + "anthropic_mm_llm = AnthropicMultiModal(max_tokens=300)\n", + "\n", + "\n", + "llm_program = MultiModalLLMCompletionProgram.from_defaults(\n", + " output_cls=TickerList,\n", + " image_documents=image_documents,\n", + " prompt_template_str=prompt_template_str,\n", + " multi_modal_llm=anthropic_mm_llm,\n", + " verbose=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b2985e5-c4b8-4860-9140-bba9813e345b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;3;38;2;90;149;237m> Raw output: {\n", + " \"fund\": \"ARKK\",\n", + " \"tickers\": [\n", + " {\n", + " \"direction\": \"Buy\",\n", + " \"ticker\": \"TSLA\",\n", + " \"company\": \"TESLA INC\",\n", + " \"shares_traded\": 93664,\n", + " \"percent_of_total_etf\": 0.2453\n", + " },\n", + " {\n", + " \"direction\": \"Buy\", \n", + " \"ticker\": \"TXG\",\n", + " \"company\": \"10X GENOMICS INC\",\n", + " \"shares_traded\": 159506,\n", + " \"percent_of_total_etf\": 0.0907\n", + " },\n", + " {\n", + " \"direction\": \"Buy\",\n", + " \"ticker\": \"CRSP\",\n", + " \"company\": \"CRISPR THERAPEUTICS AG\",\n", + " \"shares_traded\": 86268,\n", + " \"percent_of_total_etf\": 0.0669\n", + " },\n", + " {\n", + " \"direction\": \"Buy\",\n", + " \"ticker\": \"RXRX\",\n", + " \"company\": \"RECURSION PHARMACEUTICALS\",\n", + " \"shares_traded\": 289619,\n", + " \"percent_of_total_etf\": 0.0391\n", + " }\n", + " ]\n", + "}\n", + "\u001b[0m" + ] + } + ], + "source": [ + "response = llm_program()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0304aeac-89b4-4aea-8209-7c7fbf3af5d7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fund='ARKK' tickers=[TickerInfo(direction='Buy', ticker='TSLA', company='TESLA INC', shares_traded=93664, percent_of_total_etf=0.2453), TickerInfo(direction='Buy', ticker='TXG', company='10X GENOMICS INC', shares_traded=159506, percent_of_total_etf=0.0907), TickerInfo(direction='Buy', ticker='CRSP', company='CRISPR THERAPEUTICS AG', shares_traded=86268, percent_of_total_etf=0.0669), TickerInfo(direction='Buy', ticker='RXRX', company='RECURSION PHARMACEUTICALS', shares_traded=289619, percent_of_total_etf=0.0391)]\n" + ] + } + ], + "source": [ + "print(str(response))" + ] + }, + { + "cell_type": "markdown", + "id": "19d296f0-81f1-4f9a-80b0-05ccb7482569", + "metadata": {}, + "source": [ + "## Index into a Vector Store\n", + "\n", + "In this section we show you how to use Claude 3 to build a RAG pipeline over image data. We first use Claude to extract text from a set of images. We then index the text with an embedding model. Finally, we build a query pipeline over the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "150d3627-43ec-40cc-b901-09d559840a25", + "metadata": {}, + "outputs": [], + "source": [ + "# !wget \"https://www.dropbox.com/scl/fi/pvxgohp5ts5mcj2js8drk/mixed_wiki_images_small.zip?rlkey=3zf0z0n2etsjp19tofasaf4vy&dl=1\" -O mixed_wiki_images_small.zip\n", + "# !wget \"https://www.dropbox.com/scl/fi/vg2h92owduqmarwj7fxnc/mixed_wiki_images_small.zip?rlkey=fejq570ehhil3qgv3gibaliqu&dl=1\" -O mixed_wiki_images_small.zip\n", + "!wget \"https://www.dropbox.com/scl/fi/c1ec6osn0r2ggnitijqhl/mixed_wiki_images_small.zip?rlkey=swwxc7h4qtwlnhmby5fsnderd&dl=1\" -O mixed_wiki_images_small.zip\n", + "!unzip mixed_wiki_images_small.zip" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acb73dab-ef81-4e09-8b74-dc2dd22526ee", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.multi_modal_llms.anthropic import AnthropicMultiModal\n", + "\n", + "anthropic_mm_llm = AnthropicMultiModal(max_tokens=300)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b84bdfe2-0907-4c61-bad3-19d924787e19", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mixed_wiki_images_small/8.png\n", + "mixed_wiki_images_small/14.png\n", + "mixed_wiki_images_small/28.png\n", + "mixed_wiki_images_small/15.png\n", + "mixed_wiki_images_small/11.png\n", + "mixed_wiki_images_small/10.png\n", + "mixed_wiki_images_small/20.png\n", + "mixed_wiki_images_small/23.png\n", + "mixed_wiki_images_small/26.png\n", + "mixed_wiki_images_small/19.png\n", + "mixed_wiki_images_small/4.png\n", + "mixed_wiki_images_small/5.png\n", + "mixed_wiki_images_small/7.png\n", + "mixed_wiki_images_small/6.png\n", + "mixed_wiki_images_small/2.png\n" + ] + } + ], + "source": [ + "from llama_index.core.schema import TextNode\n", + "from pathlib import Path\n", + "from llama_index.core import SimpleDirectoryReader\n", + "\n", + "nodes = []\n", + "for img_file in Path(\"mixed_wiki_images_small\").glob(\"*.png\"):\n", + " print(img_file)\n", + " # put your local directore here\n", + " image_documents = SimpleDirectoryReader(input_files=[img_file]).load_data()\n", + " response = anthropic_mm_llm.complete(\n", + " prompt=\"Describe the images as an alternative text\",\n", + " image_documents=image_documents,\n", + " )\n", + " metadata = {\"img_file\": img_file}\n", + " nodes.append(TextNode(text=str(response), metadata=metadata))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "818b7a22-62e2-4e17-94fb-091229493374", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import VectorStoreIndex, StorageContext\n", + "from llama_index.embeddings.openai import OpenAIEmbedding\n", + "from llama_index.llms.anthropic import Anthropic\n", + "from llama_index.vector_stores.qdrant import QdrantVectorStore\n", + "from llama_index.core import Settings\n", + "from llama_index.core import StorageContext\n", + "import qdrant_client\n", + "\n", + "\n", + "# Create a local Qdrant vector store\n", + "client = qdrant_client.QdrantClient(path=\"qdrant_mixed_img\")\n", + "\n", + "vector_store = QdrantVectorStore(client=client, collection_name=\"collection\")\n", + "\n", + "# Using the embedding model to Gemini\n", + "embed_model = OpenAIEmbedding()\n", + "anthropic_mm_llm = AnthropicMultiModal(max_tokens=300)\n", + "\n", + "storage_context = StorageContext.from_defaults(vector_store=vector_store)\n", + "\n", + "index = VectorStoreIndex(\n", + " nodes=nodes,\n", + " storage_context=storage_context,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a89b3256-f361-4ef9-8ed1-55a26b9430ce", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.llms.anthropic import Anthropic\n", + "\n", + "query_engine = index.as_query_engine(llm=Anthropic())\n", + "response = query_engine.query(\"Tell me more about the porsche\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f182c1e6-f66b-4e6b-9b8f-98925acea4ee", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Unfortunately I cannot directly reference the provided context in my answer. However, from the details given, it appears there are images showing a white Porsche Taycan electric sports car. The Taycan seems to have a sleek, aerodynamic design with features like LED headlights, alloy wheels, and a full-width rear light bar. The photos show the Taycan parked indoors, likely a garage or showroom, as well as outdoors on a street in what looks like a residential area. Additional relevant details about the Porsche are not provided in the context, so I cannot elaborate further on the specific vehicle model or its characteristics. Please let me know if you have any other questions!\n" + ] + } + ], + "source": [ + "print(str(response))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3764d8d-62ae-4195-af74-4103af3972f5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "**Node ID:** e04f2364-8fa2-413c-8d76-4981990e49b9
**Similarity:** 0.83693930783145
**Text:** img_file: mixed_wiki_images_small/11.png\n", + "\n", + "The image shows a white Porsche Taycan Turbo electric s...
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "**Node ID:** e2de0d05-2e97-43bb-80dd-f28c4e9bcb28
**Similarity:** 0.8357091967156951
**Text:** img_file: mixed_wiki_images_small/2.png\n", + "\n", + "The image shows a white Porsche Taycan electric sports c...
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from llama_index.core.response.notebook_utils import display_source_node\n", + "\n", + "for n in response.source_nodes:\n", + " display_source_node(n, metadata_mode=\"all\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/node_postprocessor/JinaRerank.ipynb b/docs/examples/node_postprocessor/JinaRerank.ipynb index 5925382fc8e08..ce07b72652609 100644 --- a/docs/examples/node_postprocessor/JinaRerank.ipynb +++ b/docs/examples/node_postprocessor/JinaRerank.ipynb @@ -5,7 +5,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\"Open" + "\"Open" ] }, { diff --git a/docs/examples/output_parsing/BUILD b/docs/examples/output_parsing/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/docs/examples/output_parsing/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/docs/examples/query_engine/recursive_retriever_agents.ipynb b/docs/examples/query_engine/recursive_retriever_agents.ipynb index 68a6c9092f9ba..fe450a923a3b7 100644 --- a/docs/examples/query_engine/recursive_retriever_agents.ipynb +++ b/docs/examples/query_engine/recursive_retriever_agents.ipynb @@ -329,7 +329,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Boston is home to several professional sports teams across different leagues. These teams include the Boston Red Sox in Major League Baseball, the New England Patriots in the National Football League, the Boston Celtics in the NBA, the Boston Bruins in the NHL, and the New England Revolution in Major League Soccer. These teams have a rich history and are widely supported by fans in Boston and across the country.\n" + "Boston is home to several professional sports teams across different leagues, including a successful baseball team in Major League Baseball, a highly successful American football team in the National Football League, one of the most successful basketball teams in the NBA, a professional ice hockey team in the National Hockey League, and a professional soccer team in Major League Soccer. These teams have a rich history, passionate fan bases, and have achieved great success both locally and nationally.\n" ] } ], @@ -368,7 +368,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Houston is home to several professional sports teams across different leagues. The city has a professional football team called the Houston Texans, a professional basketball team called the Houston Rockets, a professional baseball team called the Houston Astros, a professional soccer team called the Houston Dynamo, and a professional women's soccer team called the Houston Dash. These teams compete in the National Football League (NFL), National Basketball Association (NBA), Major League Baseball (MLB), Major League Soccer (MLS), and National Women's Soccer League (NWSL) respectively. Houston also has minor league baseball, hockey, and other sports teams, making it a city with a rich sports culture.\n" + "Houston is home to several professional sports teams across different leagues, including the Houston Texans in the NFL, the Houston Rockets in the NBA, the Houston Astros in MLB, the Houston Dynamo in MLS, and the Houston Dash in NWSL. These teams compete in football, basketball, baseball, soccer, and women's soccer respectively, and have achieved various levels of success in their respective leagues. Additionally, the city also has minor league baseball, hockey, and other sports teams that cater to sports enthusiasts.\n" ] } ], @@ -393,7 +393,7 @@ "Calling function: summary_tool with args: {\n", " \"input\": \"positive aspects of Chicago\"\n", "}\n", - "Got output: Chicago is a vibrant city with a diverse economy and a wide range of industries. It serves as a major hub for finance, culture, commerce, industry, education, technology, telecommunications, and transportation. The city has a thriving arts and music scene, making significant contributions to visual arts, literature, film, theater, comedy, food, dance, and various music genres. Chicago is also known for its prestigious universities, including the University of Chicago, Northwestern University, and the University of Illinois Chicago. Furthermore, it is home to professional sports teams in all major leagues.\n", + "Got output: Chicago is recognized for its robust economy, acting as a key hub for finance, culture, commerce, industry, education, technology, telecommunications, and transportation. It stands out in the derivatives market and is a top-ranking city in terms of gross domestic product. Chicago is a favored destination for tourists, known for its rich art scene covering visual arts, literature, film, theater, comedy, food, dance, and music. The city hosts prestigious educational institutions and professional sports teams across different leagues.\n", "========================\n", "\n" ] @@ -416,7 +416,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Chicago is a vibrant city with a diverse economy and a wide range of industries. It serves as a major hub for finance, culture, commerce, industry, education, technology, telecommunications, and transportation. The city has a thriving arts and music scene, making significant contributions to visual arts, literature, film, theater, comedy, food, dance, and various music genres. Chicago is also known for its prestigious universities, including the University of Chicago, Northwestern University, and the University of Illinois Chicago. Furthermore, it is home to professional sports teams in all major leagues.\n" + "Chicago is known for its strong economy with a focus on finance, culture, commerce, industry, education, technology, telecommunications, and transportation. It is a major player in the derivatives market and boasts a high gross domestic product. The city is a popular tourist destination with a vibrant art scene that includes visual arts, literature, film, theater, comedy, food, dance, and music. Additionally, Chicago is home to prestigious educational institutions and professional sports teams across various leagues.\n" ] } ], diff --git a/docs/examples/retrievers/auto_vs_recursive_retriever.ipynb b/docs/examples/retrievers/auto_vs_recursive_retriever.ipynb index ed15a55ced23f..a1ce27db76c2e 100644 --- a/docs/examples/retrievers/auto_vs_recursive_retriever.ipynb +++ b/docs/examples/retrievers/auto_vs_recursive_retriever.ipynb @@ -406,7 +406,7 @@ "outputs": [], "source": [ "from llama_index.core.retrievers import VectorIndexAutoRetriever\n", - "from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo\n", + "from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo\n", "\n", "\n", "vector_store_info = VectorStoreInfo(\n", diff --git a/docs/examples/retrievers/videodb_retriever.ipynb b/docs/examples/retrievers/videodb_retriever.ipynb new file mode 100644 index 0000000000000..5431393f1bc5e --- /dev/null +++ b/docs/examples/retrievers/videodb_retriever.ipynb @@ -0,0 +1,458 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open\n", + "# VideoDB Retriever" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### RAG: Instantly Search and Stream Video Results 📺\n", + "\n", + "\n", + "> [VideoDB](https://videodb.io) is a serverless database designed to streamline the storage, search, editing, and streaming of video content. VideoDB offers random access to sequential video data by building indexes and developing interfaces for querying and browsing video content. Learn more at [docs.videodb.io](https://docs.videodb.io).\n", + "\n", + "Constructing a RAG pipeline for text is relatively straightforward, thanks to the tools developed for parsing, indexing, and retrieving text data. However, adapting RAG models for video content presents a greater challenge. Videos combine visual, auditory, and textual elements, requiring more processing power and sophisticated video pipelines.\n", + "\n", + "While Large Language Models (LLMs) excel with text, they fall short in helping you consume or create video clips. VideoDB provides a sophisticated database abstraction for your MP4 files, enabling the use of LLMs on your video data. With VideoDB, you can not only analyze but also `instantly watch video streams` of your search results.\n", + "\n", + "In this notebook, we introduce `VideoDBRetriever`, a tool specifically designed to simplify the creation of RAG pipelines for video content, without any hassle of dealing with complex video infrastructure." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "## 🛠️️ Setup connection" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Requirements\n", + "\n", + "To connect to VideoDB, simply get the API key and create a connection. This can be done by setting the `VIDEO_DB_API_KEY` environment variable. You can get it from 👉🏼 [VideoDB Console](https://console.videodb.io). ( Free for first 50 uploads, **No credit card required!** )\n", + "\n", + "Get your `OPENAI_API_KEY` from OpenAI platform for `llama_index` response synthesizer.\n", + "\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = \"\"\n", + "os.environ[\"VIDEO_DB_API_KEY\"] = \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Installing Dependencies\n", + "\n", + "To get started, we'll need to install the following packages:\n", + "\n", + "- `llama-index`\n", + "- `llama-index-retrievers-videodb`\n", + "- `videodb`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install llama-index\n", + "%pip install videodb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install llama-index-retrievers-videodb" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data Ingestion\n", + "\n", + "Let's upload a few video files first. You can use any `public url`, `Youtube link` or `local file` on your system. First 50 uploads are free!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from videodb import connect\n", + "\n", + "# connect to VideoDB\n", + "conn = connect()\n", + "\n", + "# upload videos to default collection in VideoDB\n", + "print(\"uploading first video\")\n", + "video1 = conn.upload(url=\"https://www.youtube.com/watch?v=lsODSDmY4CY\")\n", + "print(\"uploading second video\")\n", + "video2 = conn.upload(url=\"https://www.youtube.com/watch?v=vZ4kOr38JhY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> * `coll = conn.get_collection()` : Returns default collection object.\n", + "> * `coll.get_videos()` : Returns list of all the videos in a collections.\n", + "> * `coll.get_video(video_id)`: Returns Video object from given`video_id`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Indexing\n", + "\n", + "To search bits inside a video, you have to index the video first. We have two types of indexing possible for a video.\n", + "\n", + "\n", + "- `index_spoken_words`: Indexes spoken words in the video.\n", + "- `index_scenes`: Indexes visuals of the video. `(Note: This feature is currently available only for beta users, join our discord for early access)` https://discord.gg/py9P639jGz " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Indexing the videos...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████████████████████| 100/100 [00:39<00:00, 2.56it/s] \n", + "100%|████████████████████████████████████████████████████████████████████████████████████████████████████| 100/100 [00:39<00:00, 2.51it/s] \n" + ] + } + ], + "source": [ + "print(\"Indexing the videos...\")\n", + "video1.index_spoken_words()\n", + "video2.index_spoken_words()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Querying\n", + "\n", + "Now that the videos are indexed, we can use `VideoDBRetriever` to fetch relevant nodes from VideoDB." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.retrievers.videodb import VideoDBRetriever\n", + "from llama_index.core import get_response_synthesizer\n", + "from llama_index.core.query_engine import RetrieverQueryEngine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# VideoDBRetriever by default uses the default collection in the VideoDB\n", + "retriever = VideoDBRetriever()\n", + "\n", + "# use your llama_index response_synthesizer on search results.\n", + "response_synthesizer = get_response_synthesizer()\n", + "\n", + "query_engine = RetrieverQueryEngine(\n", + " retriever=retriever,\n", + " response_synthesizer=response_synthesizer,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dopamine is a neurotransmitter that plays a key role in various brain functions, including motivation, reward, and pleasure. It is involved in regulating mood, movement, and cognitive function.\n" + ] + } + ], + "source": [ + "# query across all uploaded videos to get the text answer.\n", + "response = query_engine.query(\"What is Dopamine?\")\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Morning sunlight can help trigger a cortisol pulse shift, allowing individuals to capture a morning work block by waking up early and exposing themselves to sunlight. This exposure to morning sunlight, along with brief high-intensity exercise, can assist in adjusting the cortisol levels and potentially enhancing productivity during the early hours of the day.\n" + ] + } + ], + "source": [ + "response = query_engine.query(\"What's the benefit of morning sunlight?\")\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "## Watch Video Stream of Search Result\n", + "\n", + "Although, The `Nodes` returned by Retriever are of type `TextNode`. They also have metadata that can help you `watch the video stream` of results. You can create a compilation of all Nodes using VideoDB's [Programmable video streams](https://docs.videodb.io/version-0-0-3-timeline-and-assets-44). You can even modify it with Audio and Image overlays easily. \n", + "\n", + "![Timeline](https://codaio.imgix.net/docs/_s5lUnUCIU/blobs/bl-n4vT_dFztl/e664f43dbd4da89c3a3bfc92e3224c8a188eb19d2d458bebe049e780f72506ca6b19421c7168205f7ad307187e73da60c73cdbb9a0ef3fec77cc711927ad26a29a92cd13691fa9375c231f1c006853bacf28e09b3bf0bbcb5f7b76462b354a180fb437ad?auto=format%2Ccompress&fit=max \"Programmable Video Streams\")\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from videodb import connect, play_stream\n", + "from videodb.timeline import Timeline\n", + "from videodb.asset import VideoAsset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'https://console.videodb.io/player?url=https://stream.videodb.io/v3/published/manifests/9c39c8a9-62a2-4b5e-b15d-8565cc58c8ae.m3u8'" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create video stream of search results\n", + "conn = connect()\n", + "timeline = Timeline(conn)\n", + "\n", + "relevant_nodes = retriever.retrieve(\"What's the benefit of morning sunlight?\")\n", + "\n", + "for node_obj in relevant_nodes:\n", + " node = node_obj.node\n", + " # create a video asset for each node\n", + " node_asset = VideoAsset(\n", + " asset_id=node.metadata[\"video_id\"],\n", + " start=node.metadata[\"start\"],\n", + " end=node.metadata[\"end\"],\n", + " )\n", + " # add the asset to timeline\n", + " timeline.add_inline(node_asset)\n", + "\n", + "# generate stream for the compiled timeline\n", + "stream_url = timeline.generate_stream()\n", + "play_stream(stream_url)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "### Configuring `VideoDBRetriever`\n", + "\n", + "**1. Retriever for only one Video**:\n", + "You can pass the `id` of the video object to search in only that video. \n", + "```python\n", + "VideoDBRetriever(video=\"my_video.id\")\n", + "```\n", + "\n", + "**2. Retriever for different type of Indexes**:\n", + "```python\n", + "# VideoDBRetriever that uses keyword search - Matches exact occurence of words and sentences. It only supports single video. \n", + "keyword_retriever = VideoDBRetriever(search_type=\"keyword\", video=\"my_video.id\")\n", + "\n", + "# VideoDBRetriever that uses semantic search - Perfect for question answers type of query.\n", + "semantic_retriever = VideoDBRetriever(search_type=\"semantic\")\n", + "\n", + "# [only for beta users of VideoDB] VideoDBRetriever that uses scene search - Search visual information in the videos.\n", + "visual_retriever = VideoDBRetriever(search_type=\"scene\")\n", + "```\n", + "\n", + "**3. Configure threshold parameters**: \n", + "- `result_threshold`: is the threshold for number of results returned by retriever; the default value is `5`\n", + "- `score_threshold`: only nodes with score higher than `score_threshold` will be returned by retriever; the default value is `0.2` \n", + "\n", + "```python\n", + "custom_retriever = VideoDBRetriever(result_threshold=2, score_threshold=0.5)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### View Specific Node\n", + "\n", + "To watch stream of each retrieved node, you can directly generate the stream of that part directly from `video` object of VideoDB. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[NodeWithScore(node=TextNode(id_='6ca84002-49df-4091-901d-48248dbe0977', embedding=None, metadata={'collection_id': 'c-33978c87-33e6-4259-9e27-a9edc79be9ad', 'video_id': 'm-f201ff7c-88ec-47ca-938b-a4e968676ba0', 'length': '1496.711837', 'title': 'AMA #1: Leveraging Ultradian Cycles, How to Protect Your Brain, Seed Oils Examined and More', 'start': 906.01, 'end': 974.59}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text=\" So for somebody that wants to learn an immense amount of material, or who has the opportunity to capture another Altradian cycle, the other time where that tends to occur is also early days. So some people, by waking up early and using stimulants like caffeine and hydration or some brief high intensity city exercise, can trigger that cortisol pulse to shift a little bit earlier so that they can capture a morning work block that occurs somewhere, let's say between six and 07:30 a.m. So let's think about our typical person, at least in my example, that's waking up around 07:00 a.m. And then I said, has their first Altradian work cycle really flip on? Because that bump in cortisol around 930 or 10:00 a.m. If that person were, say, to. Set their alarm clock for 05:30 a.m. Then get up, get some artificial light. If the sun isn't out, turn on bright artificial lights. Or if the sun happens to be up that time of year, get some sunlight in your eyes. But irrespective of sunlight, were to get a little bit of brief, high intensity exercise, maybe ten or 15 minutes of skipping rope or even just jumping jacks or go out for a brief jog.\", start_char_idx=None, end_char_idx=None, text_template='{metadata_str}\\n\\n{content}', metadata_template='{key}: {value}', metadata_seperator='\\n'), score=0.440981567),\n", + " NodeWithScore(node=TextNode(id_='2244fd64-121e-4699-ba36-f0f6a110750f', embedding=None, metadata={'collection_id': 'c-33978c87-33e6-4259-9e27-a9edc79be9ad', 'video_id': 'm-eae54005-b5ca-44f1-9c31-fcdb2f1db56a', 'length': '1830.498685', 'title': 'AMA #2: Improve Sleep, Reduce Sugar Cravings, Optimal Protein Intake, Stretching Frequency & More', 'start': 899.772, 'end': 977.986}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text=\" Because the study, as far as I know, has not been done. Whether or not doing resistance training or some other type of exercise would have led to the same effect. Although I have to imagine that if it's moderately intense to intense resistance training, provided it's done far enough away from going to sleep right prior to 6 hours before sleep, that one ought to see the same effects, although that was not a condition in this study. But it's a very nice study. They looked at everything from changes in core body temperature to caloric expenditure. They didn't see huge changes in core body temperature changes, so that couldn't explain the effect. It really appears that the major effect of improving slow wave sleep was due to something in changing the fine structure of the brainwaves that occur during slow wave sleep. In fact, and this is an important point. The subjects in this study did not report subjectively feeling that much better from their sleep. So you might say, well then, why would I even want to bother? However, it's well known that getting sufficient slow wave sleep is important not just for repair, excuse me, for repair of bodily tissues, but also for repair of brain tissues and repair and washout of debris in the brain. And that debris is known to lead to things like dementia.\", start_char_idx=None, end_char_idx=None, text_template='{metadata_str}\\n\\n{content}', metadata_template='{key}: {value}', metadata_seperator='\\n'), score=0.282342136)]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "relevant_nodes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'https://console.videodb.io/player?url=https://stream.videodb.io/v3/published/manifests/b7201145-7302-4ec5-b87c-d1a4c6592f69.m3u8'" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from videodb import connect\n", + "\n", + "# retriever = VideoDBRetriever()\n", + "# relevant_nodes = retriever.retrieve(\"What is Dopamine?\")\n", + "\n", + "video_node = relevant_nodes[0].node\n", + "conn = connect()\n", + "coll = conn.get_collection()\n", + "\n", + "video = coll.get_video(video_node.metadata[\"video_id\"])\n", + "start = video_node.metadata[\"start\"]\n", + "end = video_node.metadata[\"end\"]\n", + "\n", + "stream_url = video.generate_stream(timeline=[(start, end)])\n", + "play_stream(stream_url)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 🧹 Cleanup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "video1.delete()\n", + "video2.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 👨‍👩‍👧‍👦 Support & Community\n", + "\n", + "Leveraging the capabilities of automation and AI-driven content understanding, the possibilities for creation and repurposing of your content are boundless with VideoDB.\n", + "\n", + "If you have any questions or feedback. Feel free to reach out to us 🙌🏼\n", + "\n", + "- [Discord](https://discord.gg/py9P639jGz) \n", + "- [GitHub](https://github.com/video-db) \n", + "- [VideoDB](https://videodb.io) \n", + "- [Email](mailto:ashu@videodb.io) " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/vector_stores/DuckDBDemo.ipynb b/docs/examples/vector_stores/DuckDBDemo.ipynb new file mode 100644 index 0000000000000..4d0618c2f18c4 --- /dev/null +++ b/docs/examples/vector_stores/DuckDBDemo.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# DuckDB\n", + "\n", + ">[DuckDB](https://duckdb.org/docs/api/python/overview) is a fast in-process analytical database. DuckDB is under an MIT license.\n", + "\n", + "In this notebook we are going to show how to use DuckDB as a Vector store to be used in LlamaIndex.\n", + "\n", + "Install DuckDB with:\n", + "\n", + "```sh\n", + "pip install duckdb\n", + "```\n", + "\n", + "Make sure to use the latest DuckDB version (>= 0.10.0).\n", + "\n", + "You can run DuckDB in different modes depending on persistence:\n", + "- `in-memory` is the default mode, where the database is created in memory, you can force this to be use by setting `database_name = \":memory:\"` when initializing the vector store.\n", + "- `persistence` is set by using a name for a database and setting a persistence directory `database_name = \"my_vector_store.duckdb\"` where the database is persisted in the default `persist_dir` or to the one you set it to.\n", + "\n", + "With the vector store created, you can:\n", + "- `.add` \n", + "- `.get` \n", + "- `.update`\n", + "- `.upsert`\n", + "- `.delete`\n", + "- `.peek`\n", + "- `.query` to run a search. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic example\n", + "\n", + "In this basic example, we take the Paul Graham essay, split it into chunks, embed it using an open-source embedding model, load it into `DuckDBVectorStore`, and then query it.\n", + "\n", + "For the embedding model we will use OpenAI. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you're opening this Notebook on colab, you will probably need to install LlamaIndex 🦙." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install llama-index" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating a DuckDB Index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install duckdb\n", + "!pip install llama-index-vector-stores-duckdb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import VectorStoreIndex, SimpleDirectoryReader\n", + "from llama_index.vector_stores.duckdb import DuckDBVectorStore\n", + "from llama_index.core import StorageContext\n", + "\n", + "from IPython.display import Markdown, display" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup OpenAI API\n", + "import os\n", + "import openai\n", + "\n", + "openai.api_key = os.environ[\"OPENAI_API_KEY\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Download and prepare the sample dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2024-02-16 19:38:34-- https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 75042 (73K) [text/plain]\n", + "Saving to: ‘data/paul_graham/paul_graham_essay.txt’\n", + "\n", + "data/paul_graham/pa 100%[===================>] 73.28K --.-KB/s in 0.06s \n", + "\n", + "2024-02-16 19:38:34 (1.24 MB/s) - ‘data/paul_graham/paul_graham_essay.txt’ saved [75042/75042]\n", + "\n" + ] + } + ], + "source": [ + "!mkdir -p 'data/paul_graham/'\n", + "!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "documents = SimpleDirectoryReader(\"data/paul_graham/\").load_data()\n", + "\n", + "vector_store = DuckDBVectorStore()\n", + "storage_context = StorageContext.from_defaults(vector_store=vector_store)\n", + "\n", + "index = VectorStoreIndex.from_documents(\n", + " documents, storage_context=storage_context\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "The author mentions that before college, they worked on two main things outside of school: writing and programming. They wrote short stories and also tried writing programs on an IBM 1401 computer. They later got a microcomputer and started programming more extensively." + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "query_engine = index.as_query_engine()\n", + "response = query_engine.query(\"What did the author do growing up?\")\n", + "display(Markdown(f\"{response}\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Persisting to disk example\n", + "\n", + "Extending the previous example, if you want to save to disk, simply initialize the DuckDBVectorStore by specifying a database name and persist directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Save to disk\n", + "documents = SimpleDirectoryReader(\"data/paul_graham/\").load_data()\n", + "\n", + "vector_store = DuckDBVectorStore(\"pg.duckdb\", persist_dir=\"./persist/\")\n", + "storage_context = StorageContext.from_defaults(vector_store=vector_store)\n", + "\n", + "index = VectorStoreIndex.from_documents(\n", + " documents, storage_context=storage_context\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load from disk\n", + "vector_store = DuckDBVectorStore.from_local(\"./persist/pg.duckdb\")\n", + "index = VectorStoreIndex.from_vector_store(vector_store)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "The author mentions that before college, they worked on two main things outside of school: writing and programming. They wrote short stories and also tried writing programs on an IBM 1401 computer. They later got a microcomputer and started programming more extensively." + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Query Data\n", + "query_engine = index.as_query_engine()\n", + "response = query_engine.query(\"What did the author do growing up?\")\n", + "display(Markdown(f\"{response}\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Metadata filter example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is possible to narrow down the search space by filter with metadata. Below is an example to show that in practice. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core.schema import TextNode\n", + "\n", + "nodes = [\n", + " TextNode(\n", + " **{\n", + " \"text\": \"The Shawshank Redemption\",\n", + " \"metadata\": {\n", + " \"author\": \"Stephen King\",\n", + " \"theme\": \"Friendship\",\n", + " \"year\": 1994,\n", + " \"ref_doc_id\": \"doc_1\",\n", + " },\n", + " }\n", + " ),\n", + " TextNode(\n", + " **{\n", + " \"text\": \"The Godfather\",\n", + " \"metadata\": {\n", + " \"director\": \"Francis Ford Coppola\",\n", + " \"theme\": \"Mafia\",\n", + " \"year\": 1972,\n", + " \"ref_doc_id\": \"doc_1\",\n", + " },\n", + " }\n", + " ),\n", + " TextNode(\n", + " **{\n", + " \"text\": \"Inception\",\n", + " \"metadata\": {\n", + " \"director\": \"Christopher Nolan\",\n", + " \"theme\": \"Sci-fi\",\n", + " \"year\": 2010,\n", + " \"ref_doc_id\": \"doc_2\",\n", + " },\n", + " }\n", + " ),\n", + "]\n", + "\n", + "vector_store = DuckDBVectorStore()\n", + "storage_context = StorageContext.from_defaults(vector_store=vector_store)\n", + "index = VectorStoreIndex(nodes, storage_context=storage_context)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the metadata filters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters\n", + "\n", + "filters = MetadataFilters(\n", + " filters=[ExactMatchFilter(key=\"theme\", value=\"Mafia\")]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use the index as a retriever to use the metadatafilter option. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[NodeWithScore(node=TextNode(id_='736a1279-4ebd-496e-87b5-925197646477', embedding=[-0.006784645840525627, -0.021635770797729492, -0.015731574967503548, -0.03265434503555298, -0.005616107489913702, 0.025351788848638535, -0.0057811918668448925, 0.0027044713497161865, -0.01623653806746006, -0.023759208619594574, 0.027164479717612267, 0.017932699993252754, 0.029028963297605515, 0.003991158679127693, -0.0009047273779287934, 0.010973258875310421, 0.027164479717612267, -0.012844215147197247, 0.006972389295697212, -0.011148054152727127, 0.003528274828568101, 0.007736308965831995, -0.031022923067212105, -0.013996569439768791, 0.0012567456578835845, 0.004988139029592276, 0.010571876540780067, -0.024290068075060844, 0.019123896956443787, -0.02119554579257965, 0.014022464863955975, -0.023098871111869812, -0.009050510823726654, 0.001241370104253292, 0.006881754379719496, -0.007186027709394693, -0.0036577528808265924, -0.012734158895909786, 0.0034473512787371874, 0.003987921867519617, 0.01084378082305193, 0.003936130553483963, -0.01015754695981741, -0.011970238760113716, 0.004363407846540213, 0.0013425247743725777, 0.03288740664720535, -0.009186462499201298, -0.009549001231789589, 0.01988781802356243, 0.00900519359856844, 0.03363838046789169, -0.012539941817522049, -0.031955163925886154, 0.02144155278801918, 0.013096697628498077, -0.0035088532604277134, -0.009050510823726654, 0.002782158087939024, -0.014760489575564861, 0.0010722394799813628, 0.003816363401710987, -0.028821798041462898, 0.011102736927568913, -0.011335796676576138, -0.012798897922039032, -0.001216283766552806, 0.018787255510687828, 3.707318683154881e-05, 0.00591390673071146, 0.03358658775687218, 0.027371644973754883, -0.017414787784218788, 0.012973693199455738, 0.007419087924063206, -0.010791989043354988, -0.024303017184138298, 0.001213856041431427, 0.004201560281217098, -0.0054024686105549335, 0.023085923865437508, -0.02022445946931839, -0.0027643549256026745, 0.022334951907396317, 0.007198975421488285, 0.02203715220093727, -0.013841195963323116, 0.02256801165640354, 0.0038454958703368902, 0.0022626277059316635, -0.018424715846776962, 0.006308814510703087, 0.017220571637153625, 0.00503345625475049, -0.0069464934058487415, 0.029313813894987106, -0.007665096316486597, 0.004486411809921265, 0.029158441349864006, -0.013193805702030659, 0.0007109150174073875, 0.0006736901123076677, 0.00758093548938632, -0.011445852927863598, -0.021739352494478226, -0.008085899986326694, 0.028614632785320282, -0.009128197096288204, 0.008506703190505505, -0.006392975337803364, -0.020366886630654335, 0.021091962233185768, 0.030582698062062263, -0.046482592821121216, 0.016819190233945847, -0.016806241124868393, 0.014799333177506924, -0.011957291513681412, 0.01698751002550125, -0.026102760806679726, -0.010623668320477009, 0.04780326783657074, 0.019020315259695053, -0.0176090057939291, 0.02243853360414505, -0.0009945527417585254, 0.007542092353105545, 0.0009281952516175807, -0.011776021681725979, -0.008830398321151733, 0.05432895943522453, 0.01621064357459545, 0.00039571707020513713, 0.00791757833212614, -0.013044905848801136, 0.03190337494015694, -0.01125163584947586, 0.028847694396972656, -0.0282003041356802, -0.02044457197189331, 0.02770828641951084, 0.0271126888692379, -0.018994418904185295, 0.011983186937868595, -0.009613740257918835, 0.00953605305403471, 0.013491605408489704, -0.00014161653234623373, 0.026154551655054092, -0.021700508892536163, 0.022697489708662033, -0.027967242524027824, -0.001959972782060504, 0.02586969919502735, 0.03231770172715187, 0.019085053354501724, 0.001658936613239348, -0.006674589589238167, -0.014436794444918633, 0.005684083327651024, 0.023163611069321632, 0.0244583897292614, 0.0008909703465178609, -0.007250766735523939, 0.0011402154341340065, 0.022891705855727196, 0.029650457203388214, 0.006758750416338444, 0.00384873291477561, 0.004492885898798704, -0.0012939705047756433, 0.02680194191634655, -0.00532154506072402, 0.023396670818328857, -0.015653887763619423, 0.02957276999950409, 0.023293089121580124, 0.01736299693584442, -0.038196004927158356, -0.007444983813911676, -0.005366862285882235, 0.02031509391963482, 0.03356069326400757, 0.051221489906311035, -0.007716887630522251, -0.0014954706421121955, -0.006380027160048485, 0.005790902767330408, 0.01244930736720562, -0.0006445575854741037, 0.0018499166471883655, 0.021959464997053146, 0.01829523779451847, -0.013815300539135933, -0.6500830054283142, -0.008221851661801338, -0.01732415333390236, -0.012915428727865219, 0.0010447254171594977, 0.030997028574347496, 0.014216681942343712, 0.022697489708662033, -0.0171428844332695, -0.004389303270727396, -0.011387588456273079, 0.0074126143008470535, 0.0467415489256382, -0.003353479551151395, -0.05448433384299278, -0.03526980057358742, -0.013491605408489704, -0.021234387531876564, -0.023241296410560608, 0.0033761383965611458, -0.020392781123518944, 0.008267168886959553, -0.026465298607945442, -0.012022030539810658, 0.002188177779316902, -0.004007343202829361, 0.02667246386408806, -0.017311206087470055, 0.007192501798272133, 0.0038325481582432985, -0.005917143542319536, 0.013161436654627323, 0.013802352361381054, 0.006166388746351004, 0.04088914394378662, -0.007561514154076576, -0.021855883300304413, 0.028821798041462898, 0.0032385678496211767, 0.025170518085360527, -0.005162934307008982, -0.008636181242763996, 0.014915863052010536, -0.018994418904185295, -0.01266941986978054, -0.013400970958173275, 0.04000869393348694, -0.022270211949944496, 0.017816169187426567, 0.00038539929664693773, 0.00421450799331069, 0.016120009124279022, -0.0027659733314067125, 0.01747952774167061, 0.0074838269501924515, 0.004819817841053009, 0.032990988343954086, -0.003131748642772436, -0.0012308500008657575, 0.00835132971405983, 0.003641568124294281, -0.0026170737110078335, 0.0176090057939291, -0.0012494624825194478, 0.02072942443192005, -0.005936565343290567, -0.00503993034362793, 0.004994613118469715, 0.0225939080119133, -0.008435490541160107, -0.0035897770430892706, 0.016663815826177597, -0.019706549122929573, -0.02923612669110298, 0.025442423298954964, -0.0031560256611555815, 0.01698751002550125, 0.015822209417819977, 0.005907432641834021, 0.008655603043735027, -0.010565402917563915, 0.0022885233629494905, -0.029365604743361473, -0.01378940511494875, 0.009464840404689312, -0.00693354569375515, -0.05427716672420502, 0.016158850863575935, 0.00040603484376333654, 0.0036577528808265924, 0.03371606394648552, -0.009775587357580662, -0.004162717144936323, 0.026141604408621788, 0.010397081263363361, 0.010902046225965023, -0.007477353326976299, -0.007833417505025864, 0.017583109438419342, -0.023616783320903778, -0.011659491807222366, 0.0013117737835273147, -0.012041452340781689, 0.0014760489575564861, 0.02421238273382187, 0.002783776493743062, -0.0025571901351213455, 0.027319854125380516, 0.050030291080474854, -0.01894262805581093, 0.030453220009803772, 0.005295649170875549, -0.0030265478417277336, -0.013621083460748196, 0.00869444664567709, -0.02533883973956108, 0.02817440778017044, -0.004347223322838545, 0.0054024686105549335, -0.000619875849224627, -0.013116119429469109, -0.009322414174675941, -0.008759185671806335, -0.010306446813046932, 0.016430756077170372, 0.00438606645911932, -0.023474358022212982, -0.02312476746737957, -0.010332342237234116, 0.017893856391310692, 0.01829523779451847, -0.0025312944781035185, 0.01422963012009859, -0.0009710848098620772, 0.0136340307071805, -0.0002207194920629263, -0.002903543645516038, -0.0052438583225011826, 0.026348767802119255, -0.03016836941242218, 0.014074256643652916, -0.008778606541454792, 0.00034372357185930014, -0.0017592820804566145, 0.01346570998430252, -0.031307775527238846, -0.010125177912414074, -0.026063917204737663, -0.01676739752292633, -0.00585887860506773, -0.005726163741201162, 0.007762204855680466, -0.0018774307100102305, 0.013582239858806133, 0.011413483880460262, -0.02387573942542076, -0.01614590361714363, -0.005700267851352692, -0.02489861473441124, -0.017596056684851646, 0.016689712181687355, 0.0020263304468244314, -0.01804923079907894, -0.0006117834709584713, 0.006214942783117294, -0.0022011257242411375, 0.007710413541644812, 0.020548155531287193, 0.01118689775466919, -0.02682783640921116, 0.022088943049311638, 0.01149764470756054, 0.01259173359721899, 0.012429885566234589, -0.005528709851205349, 0.022231368348002434, 0.009432470425963402, -0.004965480417013168, -0.012132086791098118, -0.008286590687930584, 0.011737179011106491, -0.011653018184006214, 0.01716878078877926, -0.00195188052020967, 0.039413098245859146, -0.015213662758469582, 0.036978911608457565, 0.015071236528456211, -0.022075995802879333, 0.020638789981603622, -0.013070802204310894, 0.0008796410402283072, -0.005153223406523466, -0.019214531406760216, 0.0141001520678401, 0.027993138879537582, -0.00811826903373003, 0.01869661919772625, 0.0059883566573262215, 0.0386362299323082, 0.0336642749607563, -0.014656906947493553, 0.02662067301571369, -0.012235668487846851, -0.004415199160575867, -0.020496364682912827, 0.015874000266194344, -0.010973258875310421, 0.013659927062690258, 0.0005409751902334392, 0.004628837574273348, -0.02328014001250267, -0.008344856090843678, -0.007762204855680466, 0.02651708945631981, 0.02629697695374489, -0.020366886630654335, -0.0016095731407403946, -0.01922748051583767, -0.024290068075060844, 0.006758750416338444, 0.022956445813179016, -0.0028274753130972385, -0.006998284719884396, -0.0035703552421182394, -0.006745802704244852, 0.0014995168894529343, 0.020574050024151802, 0.010332342237234116, -0.027760079130530357, -0.013193805702030659, 0.03902466222643852, -0.0058685895055532455, 0.010779041796922684, -0.008849820122122765, -0.007166605908423662, -0.009380679577589035, -0.017816169187426567, 0.01794564723968506, -0.009348309598863125, 0.015563253313302994, 0.03205874562263489, 0.029831726104021072, -0.01820460334420204, 0.013180858455598354, -0.01966770552098751, 0.0123910428956151, -0.00822832528501749, -0.020340990275144577, 0.020431624725461006, -0.00789815653115511, 0.006218180060386658, -0.011426431126892567, -0.00622465368360281, 0.034389350563287735, -0.017181728035211563, 0.0029682826716452837, 0.007218397222459316, 0.013375075533986092, 0.03306867554783821, 0.011788969859480858, 0.006156677845865488, 0.0050561148673295975, 0.02449723333120346, 0.009031089022755623, 0.0038875762838870287, 4.352179530542344e-05, -0.0010155929485335946, -0.01439795084297657, -0.024600815027952194, -0.009853274561464787, -0.0021541898604482412, 0.014643959701061249, -0.015576200559735298, 0.015407879836857319, 0.009069932624697685, 0.004318090621381998, 0.007665096316486597, 0.010371185839176178, -0.0017317679012194276, -0.030997028574347496, -0.0030653912108391523, 0.03594308719038963, -0.009173514321446419, 0.00014424655819311738, -0.008603811264038086, 0.013970674015581608, -0.006804067641496658, 0.007438509725034237, -0.005014034919440746, -0.014825228601694107, 0.010455346666276455, 0.00681701535359025, 0.005476918537169695, -0.0021104910410940647, -0.012222721241414547, 0.01916274055838585, -0.021493343636393547, -0.002458463190123439, -0.027682391926646233, -0.0064447661861777306, -0.001683213748037815, -0.006836437154561281, -0.02053520642220974, 0.029987100511789322, 0.006606613751500845, 0.00537657318636775, -0.010164021514356136, 0.0072378190234303474, 0.01517481915652752, 0.01248167734593153, 0.009639635682106018, -0.020625842735171318, -0.022399690002202988, 0.0026850495487451553, -0.016845084726810455, -0.015757469460368156, -0.005415416322648525, 0.0188519936054945, -0.004806870128959417, 0.003722491906955838, -0.026374664157629013, -0.0345965139567852, -0.0015901514561846852, 0.0869574099779129, 0.010526559315621853, 0.030815759673714638, 0.026154551655054092, 0.01125163584947586, -0.010338816791772842, -0.03205874562263489, -0.022930549457669258, 0.003819600446149707, -0.024769136682152748, -0.016573181375861168, -0.03172210603952408, 0.011277532204985619, 0.01508418470621109, 0.03842906281352043, -0.012876585125923157, -0.010688407346606255, -0.00038357850280590355, -7.556253694929183e-05, -0.013892986811697483, -0.009322414174675941, 0.008085899986326694, 0.017116988077759743, 0.00822832528501749, -0.016430756077170372, -0.04959006607532501, 0.017065197229385376, -0.0019356957636773586, 0.003796941600739956, -0.02256801165640354, -0.0033372947946190834, -0.0015772036276757717, -0.008409595116972923, 0.005661424715071917, -0.0016476073069497943, -0.0026737202424556017, 0.03918003663420677, 0.013944778591394424, 0.017596056684851646, -0.006609850563108921, 0.009782060980796814, -0.022775176912546158, -0.015110080130398273, -0.014022464863955975, 0.028977170586586, -0.014190786518156528, -0.028718216344714165, 0.011050945147871971, 0.018877889961004257, -0.02022445946931839, 0.029650457203388214, 0.015187766402959824, -0.0006619561463594437, 0.0015861052088439465, 0.019486436620354652, 0.011232214979827404, 0.0028938327450305223, 0.015420827083289623, -0.0027934873942285776, 0.019395800307393074, -0.02028919942677021, -0.037626300007104874, 0.007509722840040922, -0.010170495137572289, 0.009128197096288204, -0.01586105301976204, -0.01935695856809616, -0.008603811264038086, -0.007406140211969614, -0.01595168747007847, 0.002808053744956851, -0.008105321787297726, -0.013362127356231213, 0.0021460975985974073, 0.018217552453279495, -0.0031819213181734085, 0.006745802704244852, 0.0015755851054564118, 0.030893445014953613, 0.009594318456947803, -0.02219252474606037, -0.030271951109170914, -0.002346788300201297, -0.0392577238380909, -0.0025976519100368023, 0.007988790981471539, -0.019085053354501724, -0.014359108172357082, -0.02000434696674347, -0.0018580090254545212, 0.006231127772480249, -0.007211923599243164, 0.022671593353152275, -0.015809260308742523, -0.00040987873217090964, -0.0020554629154503345, 0.005285938270390034, 0.0022561538498848677, -0.0026138366665691137, -0.00391023512929678, 0.02091069333255291, -0.02471734583377838, -0.017932699993252754, 0.008344856090843678, -0.004473464097827673, -0.0037645723205059767, -0.0007355967536568642, 0.00716013228520751, -0.0007975033950060606, -0.005629055202007294, 0.01747952774167061, -0.031307775527238846, 0.002071647671982646, -0.02359088696539402, 0.0002816146006807685, 0.01960296556353569, 0.005635528825223446, 0.0005057733505964279, 0.0063703167252242565, -0.022231368348002434, -0.0036253833677619696, -0.011814865283668041, 0.012235668487846851, 0.03938720002770424, -0.01235867291688919, -0.011542961932718754, 0.021493343636393547, -0.011860182508826256, 0.02175229974091053, -0.0019955793395638466, -0.039931006729602814, 0.009717321954667568, 0.011834287084639072, -0.008545546792447567, -0.004878082778304815, -0.019344009459018707, 0.007444983813911676, -0.000181370327482, -0.02299528941512108, -0.0012025267351418734, -0.025546004995703697, -0.008454912342131138, -0.0036448051687330008, -0.0171428844332695, 0.00028485155780799687, -0.02296939305961132, -0.004657970275729895, -0.009930960834026337, -0.012416938319802284, 0.015744522213935852, -0.021234387531876564, -0.021791143342852592, -0.0044799381867051125, 0.0029731381218880415, 0.003018455347046256, -0.03249897435307503, -0.038506750017404556, -0.013239122927188873, 0.004169190768152475, 0.01567978225648403, 0.03418218716979027, -0.0008974442607723176, 0.011012102477252483, 0.00018056108092423528, -0.005820035003125668, 0.026089811697602272, 0.000589934061281383, 0.01794564723968506, -0.0021428605541586876, 0.04360818490386009, 0.037445031106472015, 0.0029731381218880415, 0.018722515553236008, 0.0025005433708429337, 0.022166630253195763, 0.01645665057003498, 0.009458365850150585, 0.019408749416470528, 0.014967653900384903, -0.018101021647453308, -0.008940454572439194, 0.03154083713889122, -0.025066936388611794, -0.01645665057003498, -0.011737179011106491, -0.017842065542936325, 0.0005810324219055474, -0.029987100511789322, -0.02724216692149639, 0.012837741523981094, 0.02693141996860504, -0.01745363138616085, -0.00455762492492795, -0.014967653900384903, 0.007315505761653185, -0.03542517498135567, -0.001539978664368391, 0.0010107374982908368, 0.01835997775197029, 0.013148488476872444, 0.013569291681051254, 0.030556803569197655, -0.00402029138058424, -0.029495082795619965, 0.0038454958703368902, 0.0520501472055912, -0.008888662792742252, 0.009840326383709908, 0.01463101152330637, -0.013737613335251808, 0.00866207666695118, -0.02923612669110298, -0.012352199293673038, -0.04513602331280708, 0.014954706653952599, 0.003521800972521305, 0.0026219291612505913, 0.0035897770430892706, 0.004907215479761362, -0.023047080263495445, 0.03962026163935661, -0.012125612236559391, 0.03586539998650551, 0.006305577699095011, 0.0193181149661541, 0.015498514287173748, 0.00633470993489027, -0.009943909011781216, 0.030220160260796547, 0.005703505128622055, -0.0017689928645268083, 0.022542115300893784, 0.01257231179624796, 0.011847235262393951, -0.0072442926466465, -0.0020020531956106424, -0.01617179997265339, -0.022826967760920525, -0.01957707107067108, 0.019046209752559662, 0.033172257244586945, 0.016754450276494026, -0.012183877639472485, -0.0023435514885932207, 0.012643524445593357, 0.002867937320843339, -0.0037775200325995684, -0.004780974239110947, -0.003266081912443042, -0.0467415489256382, -0.012598207220435143, -0.019615912809967995, -0.01117394957691431, -0.01683213748037815, -0.006661641877144575, -0.03889518603682518, 0.012403990142047405, -0.011665965430438519, 0.006078991107642651, -0.01736299693584442, -0.026167498901486397, 0.04521371051669121, 0.011659491807222366, -0.009056984446942806, 0.026193395256996155, -0.0013781312154605985, -0.019486436620354652, -0.011471749283373356, -0.003118800697848201, 0.02786366082727909, 0.005379809997975826, -0.0032709373626857996, 0.003230475587770343, 0.009827378205955029, -0.008577915839850903, 0.0021153464913368225, -0.013621083460748196, -0.015420827083289623, -0.010306446813046932, -0.031178297474980354, -0.011957291513681412, 0.011523540131747723, -0.00889513734728098, 0.01355634443461895, -0.008435490541160107, -0.016741503030061722, 0.012242143042385578, -0.0033631904516369104, 0.019551174715161324, -0.026542985811829567, -0.029210232198238373, -0.023176558315753937, 0.011057419702410698, 0.0012502716854214668, -0.017557213082909584, -0.00044184361468069255, 0.0015027538174763322, 0.03754861280322075, -0.015886947512626648, 0.01801038719713688, -0.02168756164610386, 0.005826509092003107, -0.008862767368555069, 0.019085053354501724, -0.001272930414415896, -0.009529579430818558, 0.010558929294347763, -0.018282290548086166, 0.0035444595851004124, 0.013491605408489704, 0.010202865116298199, 0.024354808032512665, 0.013983621262013912, -0.017906803637742996, 0.002309563336893916, 0.02299528941512108, -0.008027634583413601, -0.005648477002978325, 0.0002723083598539233, 0.035917192697525024, -0.01621064357459545, 0.006425344850867987, 0.01779027469456196, -0.008927506394684315, 0.0011426431592553854, 0.004457279574126005, -0.0035120900720357895, 0.01126458402723074, -0.03703070059418678, -0.003347005695104599, -0.01916274055838585, 0.039931006729602814, -0.004376355558633804, 0.011640070006251335, -0.014074256643652916, -0.009652582928538322, -0.007198975421488285, 0.024393651634454727, -0.009743218310177326, -0.02290465496480465, 0.02318950556218624, 0.023383723571896553, -0.0031754474621266127, 0.010008648037910461, -0.0030653912108391523, -0.02496335469186306, 0.0024681738577783108, -0.038662124425172806, -0.035140324383974075, -0.03218822553753853, -0.026905523613095284, 0.04536908492445946, 0.007645674515515566, -0.0019486435921862721, -0.004836002364754677, 0.009665531106293201, -0.03125598281621933, -0.02877000719308853, -9.533827324048616e-06, 0.019279271364212036, 0.02549421414732933, 0.005114380270242691, -0.006399448961019516, 0.00869444664567709, 0.005457496736198664, 0.0132455974817276, -0.019654756411910057, 0.0216616652905941, -0.009031089022755623, -0.01157533098012209, 0.016845084726810455, 0.005237384233623743, -0.0005272181588225067, -0.004233929794281721, -0.007943473756313324, 0.01736299693584442, -0.011089788749814034, 0.02356499247252941, -0.02414764277637005, -0.011394062079489231, -0.027785973623394966, -0.016094112768769264, -0.014721645973622799, 0.002252916805446148, -0.0026219291612505913, -0.02069058082997799, 0.0057811918668448925, -0.008448437787592411, 0.0053992317989468575, -0.023137714713811874, -0.01007986068725586, 0.01876135915517807, -0.008921032771468163, -0.01007986068725586, -0.008921032771468163, -0.012365146540105343, 0.024536076933145523, -0.011743652634322643, 0.010112229734659195, 0.019214531406760216, -0.00967847928404808, 0.0019939609337598085, 0.014592167921364307, -0.0014622919261455536, -0.004460516385734081, 0.008027634583413601, -0.03293919935822487, -0.03604666888713837, -0.025817908346652985, -0.0032822666689753532, 0.012637050822377205, -0.003010363085195422, 0.03964615613222122, -0.015666835010051727, -0.007567987777292728, -0.005496340338140726, -0.0076197790913283825, -0.004959006793797016, -0.007024180144071579, 0.02449723333120346, -0.027164479717612267, -0.001715583261102438, -0.020276252180337906, 0.0036027247551828623, -0.02135091833770275, -0.0026154550723731518, -0.0107531463727355, -0.0038066525012254715, -0.017583109438419342, -0.00842901598662138, -0.012423411943018436, -0.013478657230734825, -0.017647847533226013, -0.03309457004070282, -0.011924921534955502, 0.03902466222643852, 0.20778626203536987, 0.006422107573598623, -0.012080295011401176, 0.016650868579745293, -0.017660796642303467, 0.018088074401021004, 0.022645698860287666, -0.0006623608060181141, -0.012863636948168278, 0.012009082362055779, -0.013193805702030659, 0.00944541860371828, 0.033301737159490585, 0.008396646939218044, 0.009438944980502129, -0.017997438088059425, -0.021700508892536163, -0.02113080583512783, -0.026284029707312584, -0.019188636913895607, -0.004114162642508745, 0.005713215563446283, -0.005680846516042948, -0.002369446912780404, 0.029779935255646706, 0.008545546792447567, -0.0165213905274868, 0.004288957919925451, 0.017751431092619896, 0.025002198293805122, -0.004230692982673645, -0.028070826083421707, 0.0031803029123693705, -0.005535183474421501, -0.031929269433021545, 0.016404859721660614, -0.0244583897292614, -0.00933536235243082, -0.010791989043354988, 0.006043384782969952, -0.004068845417350531, 0.014385003596544266, -0.005175882019102573, -0.00130287220235914, 0.008195956237614155, 0.014255525544285774, -0.021894726902246475, 0.011646544560790062, -0.014605116099119186, 0.010837307199835777, -0.04153653606772423, -0.013944778591394424, 0.029210232198238373, 0.02851105108857155, -0.015524409711360931, -0.021609874442219734, 0.01190549973398447, 0.02421238273382187, -0.004797159228473902, -0.027345748618245125, 0.022516220808029175, 0.02611570805311203, -0.020250355824828148, -0.017647847533226013, -0.003842259058728814, 0.0244583897292614, -0.026452351361513138, -0.02788955718278885, 0.04182138666510582, -0.035632338374853134, 0.021791143342852592, -0.003974974155426025, -0.00591390673071146, 0.013219701126217842, 0.02396637387573719, -0.02359088696539402, -0.02682783640921116, 0.01953822746872902, 0.0043116165325045586, 0.03534748777747154, -0.024937458336353302, 0.010902046225965023, -0.016404859721660614, -0.00794994831085205, -0.00455762492492795, -0.01785501278936863, 0.0032968330197036266, 0.011206318624317646, 0.0022027441300451756, -0.00800821278244257, -0.013905934989452362, -0.028744110837578773, -0.016754450276494026, 0.005917143542319536, 0.010545981116592884, 0.011076840572059155, 0.009141145274043083, 0.012831267900764942, -0.010053965263068676, -0.0020360411144793034, -0.03019426390528679, 0.028381573036313057, 0.028277991339564323, -0.019279271364212036, -0.03029784746468067, -0.01835997775197029, 0.011801918037235737, 0.044980648905038834, 0.002332222182303667, -0.029313813894987106, 0.003440877189859748, -0.012119138613343239, -0.013116119429469109, -0.012675894424319267, 0.021363865584135056, 0.006739328615367413, -0.013621083460748196, -0.037004806101322174, 0.002421238226816058, -0.004285721108317375, -0.008293064311146736, -0.00384873291477561, 0.0015067999484017491, 0.013362127356231213, -0.006483609788119793, 0.0032498971559107304, -0.007969369180500507, -0.0028663186822086573, 0.03262845054268837, -0.02739753946661949, 0.01547261793166399, -0.02480798028409481, 0.004334275145083666, -0.0052632796578109264, -0.0036027247551828623, 0.008480807766318321, 0.017958596348762512, 0.015278401784598827, -0.002523201983422041, -0.018748411908745766, 0.0011329322587698698, -0.01583515666425228, 0.010384134016931057, 0.007937000133097172, -0.009710848331451416, -0.008163586258888245, 0.010584824718534946, -0.005726163741201162, -0.020017296075820923, -0.018813150003552437, -0.013724666088819504, -0.02640056051313877, -0.0022836679127067327, -0.008966349996626377, 0.027268061414361, -0.022451480850577354, -0.010358238592743874, 0.010856728069484234, -0.012488150969147682, -0.012565838173031807, -0.03949078172445297, 0.012436360120773315, 0.013931830413639545, 0.00546073354780674, -0.015148923732340336, -0.010681932792067528, -0.1639709174633026, 0.023293089121580124, 0.015964634716510773, -0.006894702557474375, 0.026879629120230675, -0.02465260773897171, 0.03288740664720535, 0.002220547292381525, -0.022399690002202988, 0.0008723578648641706, -0.012598207220435143, -0.00705007603392005, -0.017414787784218788, -0.014902914874255657, -0.004399014171212912, 0.015019445680081844, -0.042805418372154236, 0.02006908692419529, 0.022399690002202988, -0.0007533999742008746, 0.006153441034257412, -0.016819190233945847, -0.022477377206087112, -0.019188636913895607, 0.002359736245125532, 0.02215368114411831, -0.0029456240590661764, 0.006862333044409752, -0.009561948478221893, -0.002031185897067189, -0.01191844791173935, 0.00922530610114336, -0.002044133609160781, 0.011996135115623474, 0.01623653806746006, 0.0047777374275028706, -0.009918013587594032, 0.0023273667320609093, -0.007283136248588562, -0.004780974239110947, 0.02662067301571369, 0.017777325585484505, 0.018994418904185295, 0.005470444448292255, -0.007147184573113918, 0.02372036501765251, 0.03278382495045662, -0.007406140211969614, 0.023539096117019653, -0.03063448891043663, -0.006409159861505032, -0.024639658629894257, -0.0026397323235869408, -0.006101649720221758, 0.02044457197189331, 0.018593037500977516, -0.001836968818679452, 0.011814865283668041, -0.004415199160575867, 0.0019227479351684451, -0.03659047558903694, -0.007477353326976299, 0.020871849730610847, 0.00444109458476305, -0.015537356957793236, -0.01791975274682045, -0.001009118976071477, 0.0006902794702909887, -0.023383723571896553, 0.006247312296181917, -0.015874000266194344, 0.009199410676956177, 0.0015237939078360796, -0.027811869978904724, 0.000155980495037511, 0.013724666088819504, -0.02306002750992775, -0.004350460134446621, 0.002518346766009927, -0.0019713023211807013, -0.021428605541586876, 0.025377683341503143, 0.014825228601694107, -0.02443249523639679, 0.03835137560963631, 0.027216270565986633, -0.024691451340913773, -0.02137681469321251, -0.010850254446268082, -0.03723786771297455, 0.0017835590988397598, -0.025079883635044098, -0.02028919942677021, -0.0032223830930888653, 0.02436775527894497, 0.0033033068757504225, 0.025753170251846313, -0.007749257143586874, 0.010209338739514351, -0.028407467529177666, -0.013880039565265179, -0.009820904582738876, -0.01264999806880951, 0.001183914253488183, 0.03288740664720535, 0.0349072627723217, 0.01061072014272213, 0.01701340638101101, 0.006331473123282194, 0.007315505761653185, -0.02015972137451172, 0.03599487617611885, 0.025610744953155518, 0.0059883566573262215, -0.005285938270390034, 0.02115670219063759, 0.01966770552098751, -0.04365997388958931, 0.009963330812752247, 0.014708698727190495, 0.0467415489256382, -0.0010212576016783714, 0.014035413041710854, 0.006862333044409752, -0.009037562645971775, 0.0030281662475317717, -0.08436784893274307, 0.001717201666906476, 0.0035088532604277134, 0.011769548058509827, 0.0007809140370227396, 0.027449332177639008, -0.020366886630654335, 0.03690122440457344, -0.01663791947066784, 0.006648694165050983, -0.003926419652998447, -0.04640490561723709, -0.032240018248558044, -0.020056139677762985, 0.02206304669380188, 0.005065825767815113, -0.008377225138247013, -0.009626687504351139, -0.03413039445877075, 0.005175882019102573, -0.0216616652905941, -0.008344856090843678, -0.0001375703577650711, -0.01191844791173935, 0.0022367320489138365, 0.003696596249938011, -0.02015972137451172, 0.006205231882631779, 0.0016508442349731922, 0.014126047492027283, -0.006486846599727869, -0.020677633583545685, 0.023098871111869812, -0.018618933856487274, -0.0019065631786361337, -0.00967847928404808, 0.006438292562961578, -0.005250331945717335, 0.024549024179577827, -0.03260255604982376, -0.003118800697848201, -0.0031527888495475054, 0.0032968330197036266, -0.04946058616042137, 0.0014040268724784255, -0.007011232431977987, -0.014436794444918633, 0.00016700636479072273, 0.03371606394648552, -0.01244930736720562, -0.014164891093969345, -0.008098847232758999, -0.009464840404689312, -0.009503684006631374, 0.013400970958173275, -0.015524409711360931, 0.025442423298954964, -0.030090682208538055, -0.022412637248635292, 0.024924511089920998, 0.021247336640954018, -0.015938738361001015, -0.012585259042680264, 0.021713456138968468, 0.0062699709087610245, -0.01299311500042677, 0.004165953956544399, -0.027268061414361, 0.019654756411910057, -0.0031592627055943012, -0.008901610970497131, 0.0072378190234303474, -0.03374196216464043, -0.005175882019102573, -0.03016836941242218, 0.0022399690933525562, -0.034233976155519485, -0.00769746582955122, 0.02502809278666973, -0.02303413301706314, -0.015692731365561485, -0.008933980949223042, 0.005201777908951044, -0.02788955718278885, 0.021635770797729492, 0.04254646226763725, 0.022024204954504967, 0.014022464863955975, -0.009205884300172329, -0.0282003041356802, -0.0005603968747891486, 0.012293933890759945, -0.0023856316693127155, -0.01149764470756054, 0.0048133437521755695, 0.00857144221663475, 0.0009629924898035824, 0.007341401185840368, 0.005124091170728207, 0.006952967494726181, -0.03001299500465393, 0.004399014171212912, -0.07949947565793991, 0.007334927562624216, -0.023862792178988457, -0.01041002944111824, 0.02119554579257965, 0.007341401185840368, 0.008383698761463165, -0.021648718044161797, 0.006072517018765211, 0.007509722840040922, -0.01148469652980566, 0.02131207473576069, 0.010274077765643597, -1.5641040590708144e-05, -0.011840760707855225, -0.0025895596481859684, 0.02059994637966156, -0.008487281389534473, 0.011950816959142685, 0.025571901351213455, -0.012086769565939903, 0.003906997852027416, -0.006137256044894457, -0.014372055418789387, -0.007982317358255386, -0.020988380536437035, 0.0025102542713284492, 0.018968524411320686, -0.011976713314652443, -0.0035023794043809175, 0.0033696643076837063, -0.02203715220093727, 0.02529999613761902, 0.017971543595194817, 0.004227456171065569, -0.025442423298954964, -0.008577915839850903, -0.01233925111591816, 0.03003889136016369, 0.010028069838881493, -0.02474324218928814, -0.01463101152330637, 0.01561504416167736, -0.022801071405410767, -0.022075995802879333, 0.009244727902114391, -0.021700508892536163, 0.004246877506375313, 0.01636601611971855, 0.008946928195655346, 0.021829986944794655, 0.014915863052010536, -0.021804092451930046, -0.016262434422969818, 0.003347005695104599, -0.02022445946931839, 0.01798449084162712, -0.0009095827699638903, -0.0011062275152653456, -0.006642220076173544, 0.0182304996997118, -0.0008051911718212068, -0.01683213748037815, -0.009134671650826931, -0.005752059165388346, 0.01061072014272213, -0.014566272497177124, 0.022114839404821396, -0.0032223830930888653, -0.01539493165910244, -0.021739352494478226, -0.005347440484911203, 0.0029342947527766228, 0.02084595523774624, 0.0006077372818253934, -0.00716013228520751, 0.022710436955094337, 0.013142014853656292, 0.00942599680274725, 0.005234147422015667, 0.033457107841968536, 0.004172428045421839, -0.02529999613761902, 0.026698358356952667, 0.03508853167295456, 0.03765219449996948, -0.014889967627823353, 0.025908542796969414, -2.9916493076598272e-05, -0.007723361253738403, -0.006590429227799177, 0.01960296556353569, 0.008629707619547844, 0.014877019450068474, -0.011860182508826256, -0.005651713814586401, -0.01621064357459545, -0.019188636913895607, 0.0077427830547094345, 0.02954687550663948, -0.010118704289197922, 0.006965915206819773, -0.002694760449230671, -0.01807512529194355, -0.02509283274412155, 0.028821798041462898, -0.024976301938295364, -0.022632749751210213, -0.015187766402959824, 0.008823923766613007, 0.02724216692149639, 0.010584824718534946, -0.022736333310604095, 0.006842911243438721, -0.03493315726518631, -0.013491605408489704, -0.013388022780418396, -0.03446703776717186, -0.0019486435921862721, -0.0015772036276757717, 0.008901610970497131, 0.023979321122169495, 0.038196004927158356, -0.01248167734593153, 0.007736308965831995, 0.00325313420034945, 0.023396670818328857, -0.018178708851337433, -0.0001203740612254478, -0.015511461533606052, 0.023111818358302116, -0.012222721241414547, -0.01115452777594328, -0.009167040698230267, -0.01385414320975542, -0.0031738290563225746, -0.0038357852026820183, 0.02159692719578743, -0.01966770552098751, 0.03905055671930313, -0.004787448327988386, -0.0009314321796409786, 0.0033599536400288343, -0.026776045560836792, -0.01017696876078844, -0.013841195963323116, 0.0006340374820865691, -0.030686281621456146, -0.021247336640954018, 0.02724216692149639, 0.015744522213935852, 0.027811869978904724, -0.012837741523981094, -0.021713456138968468, 0.0017398602794855833, 0.0021202019415795803, -0.0071536581963300705, 0.010902046225965023, -0.012099716812372208, 0.011219266802072525, 0.015187766402959824, 0.014605116099119186, -0.0069076502695679665, 0.014190786518156528, -0.0002251702971989289, 0.025261154398322105, -0.002346788300201297, -0.01991371251642704, -0.05026335269212723, 0.004470227286219597, -0.019680652767419815, -0.023539096117019653, -0.009807956404983997, 0.020483415573835373, -0.009069932624697685, 0.013737613335251808, 0.0006384882726706564, 0.011465274728834629, 0.0271126888692379, -0.03508853167295456, 0.02817440778017044, -0.028096720576286316, -0.009943909011781216, 0.03091934137046337, 0.005344203673303127, -0.005862115416675806, -0.013362127356231213, -0.02596033550798893], metadata={'director': 'Francis Ford Coppola', 'theme': 'Mafia', 'year': 1972, 'ref_doc_id': 'None', '_node_content': '{\"id_\": \"736a1279-4ebd-496e-87b5-925197646477\", \"embedding\": null, \"metadata\": {\"director\": \"Francis Ford Coppola\", \"theme\": \"Mafia\", \"year\": 1972, \"ref_doc_id\": \"doc_1\"}, \"excluded_embed_metadata_keys\": [], \"excluded_llm_metadata_keys\": [], \"relationships\": {}, \"text\": \"\", \"start_char_idx\": null, \"end_char_idx\": null, \"text_template\": \"{metadata_str}\\\\n\\\\n{content}\", \"metadata_template\": \"{key}: {value}\", \"metadata_seperator\": \"\\\\n\", \"class_name\": \"TextNode\"}', '_node_type': 'TextNode', 'document_id': 'None', 'doc_id': 'None'}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text='The Godfather', start_char_idx=None, end_char_idx=None, text_template='{metadata_str}\\n\\n{content}', metadata_template='{key}: {value}', metadata_seperator='\\n'), score=0.7543986421543848)]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "retriever = index.as_retriever(filters=filters)\n", + "retriever.retrieve(\"What is inception about?\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "llama-index-dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/examples/vector_stores/MongoDBAtlasVectorSearchRAGFireworks.ipynb b/docs/examples/vector_stores/MongoDBAtlasVectorSearchRAGFireworks.ipynb index f1c0db04e7472..50f96101eeba3 100644 --- a/docs/examples/vector_stores/MongoDBAtlasVectorSearchRAGFireworks.ipynb +++ b/docs/examples/vector_stores/MongoDBAtlasVectorSearchRAGFireworks.ipynb @@ -4,347 +4,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: llama-index in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (0.10.12)\n", - "Requirement already satisfied: llama-index-agent-openai<0.2.0,>=0.1.4 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.5)\n", - "Requirement already satisfied: llama-index-cli<0.2.0,>=0.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.5)\n", - "Requirement already satisfied: llama-index-core<0.11.0,>=0.10.12 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.10.12)\n", - "Requirement already satisfied: llama-index-embeddings-openai<0.2.0,>=0.1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.6)\n", - "Requirement already satisfied: llama-index-indices-managed-llama-cloud<0.2.0,>=0.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.3)\n", - "Requirement already satisfied: llama-index-legacy<0.10.0,>=0.9.48 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.9.48)\n", - "Requirement already satisfied: llama-index-llms-openai<0.2.0,>=0.1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.6)\n", - "Requirement already satisfied: llama-index-multi-modal-llms-openai<0.2.0,>=0.1.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.4)\n", - "Requirement already satisfied: llama-index-program-openai<0.2.0,>=0.1.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.4)\n", - "Requirement already satisfied: llama-index-question-gen-openai<0.2.0,>=0.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.3)\n", - "Requirement already satisfied: llama-index-readers-file<0.2.0,>=0.1.4 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.5)\n", - "Requirement already satisfied: llama-index-readers-llama-parse<0.2.0,>=0.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index) (0.1.3)\n", - "Requirement already satisfied: llama-index-vector-stores-chroma<0.2.0,>=0.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.1.3)\n", - "Requirement already satisfied: PyYAML>=6.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (6.0.1)\n", - "Requirement already satisfied: SQLAlchemy>=1.4.49 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.12->llama-index) (2.0.27)\n", - "Requirement already satisfied: aiohttp<4.0.0,>=3.8.6 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (3.9.3)\n", - "Requirement already satisfied: dataclasses-json in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (0.6.4)\n", - "Requirement already satisfied: deprecated>=1.2.9.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (1.2.14)\n", - "Requirement already satisfied: dirtyjson<2.0.0,>=1.0.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (1.0.8)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (2023.10.0)\n", - "Requirement already satisfied: httpx in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (0.27.0)\n", - "Requirement already satisfied: llamaindex-py-client<0.2.0,>=0.1.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (0.1.13)\n", - "Requirement already satisfied: nest-asyncio<2.0.0,>=1.5.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (1.6.0)\n", - "Requirement already satisfied: networkx>=3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (3.1)\n", - "Requirement already satisfied: nltk<4.0.0,>=3.8.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (3.8.1)\n", - "Requirement already satisfied: numpy in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (1.24.4)\n", - "Requirement already satisfied: openai>=1.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (1.12.0)\n", - "Requirement already satisfied: pandas in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (2.0.3)\n", - "Requirement already satisfied: pillow>=9.0.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (10.2.0)\n", - "Requirement already satisfied: requests>=2.31.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (2.31.0)\n", - "Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (8.2.3)\n", - "Requirement already satisfied: tiktoken>=0.3.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (0.6.0)\n", - "Requirement already satisfied: tqdm<5.0.0,>=4.66.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (4.66.2)\n", - "Requirement already satisfied: typing-extensions>=4.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (4.9.0)\n", - "Requirement already satisfied: typing-inspect>=0.8.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.12->llama-index) (0.9.0)\n", - "Requirement already satisfied: beautifulsoup4<5.0.0,>=4.12.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-readers-file<0.2.0,>=0.1.4->llama-index) (4.12.3)\n", - "Requirement already satisfied: bs4<0.0.3,>=0.0.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-readers-file<0.2.0,>=0.1.4->llama-index) (0.0.2)\n", - "Requirement already satisfied: pymupdf<2.0.0,>=1.23.21 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-readers-file<0.2.0,>=0.1.4->llama-index) (1.23.25)\n", - "Requirement already satisfied: pypdf<5.0.0,>=4.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-readers-file<0.2.0,>=0.1.4->llama-index) (4.0.2)\n", - "Requirement already satisfied: llama-parse<0.4.0,>=0.3.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-readers-llama-parse<0.2.0,>=0.1.2->llama-index) (0.3.4)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.12->llama-index) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.12->llama-index) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.12->llama-index) (4.0.3)\n", - "Requirement already satisfied: soupsieve>1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from beautifulsoup4<5.0.0,>=4.12.3->llama-index-readers-file<0.2.0,>=0.1.4->llama-index) (2.5)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from deprecated>=1.2.9.3->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.16.0)\n", - "Requirement already satisfied: chromadb<0.5.0,>=0.4.22 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.4.23)\n", - "Requirement already satisfied: onnxruntime<2.0.0,>=1.17.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.17.0)\n", - "Requirement already satisfied: tokenizers<0.16.0,>=0.15.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.15.2)\n", - "Requirement already satisfied: pydantic>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.10.11)\n", - "Requirement already satisfied: anyio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (4.3.0)\n", - "Requirement already satisfied: certifi in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (2024.2.2)\n", - "Requirement already satisfied: httpcore==1.* in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.0.4)\n", - "Requirement already satisfied: idna in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (3.6)\n", - "Requirement already satisfied: sniffio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.3.0)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpcore==1.*->httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (0.14.0)\n", - "Requirement already satisfied: click in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.12->llama-index) (8.1.7)\n", - "Requirement already satisfied: joblib in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.3.2)\n", - "Requirement already satisfied: regex>=2021.8.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.12->llama-index) (2023.12.25)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from openai>=1.1.0->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.9.0)\n", - "Requirement already satisfied: PyMuPDFb==1.23.22 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pymupdf<2.0.0,>=1.23.21->llama-index-readers-file<0.2.0,>=0.1.4->llama-index) (1.23.22)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.12->llama-index) (3.3.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.12->llama-index) (2.2.1)\n", - "Requirement already satisfied: greenlet!=0.4.17 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy>=1.4.49->SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.12->llama-index) (3.0.3)\n", - "Requirement already satisfied: mypy-extensions>=0.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from typing-inspect>=0.8.0->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.0.0)\n", - "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from dataclasses-json->llama-index-core<0.11.0,>=0.10.12->llama-index) (3.20.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.12->llama-index) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.12->llama-index) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.12->llama-index) (2024.1)\n", - "Requirement already satisfied: exceptiongroup>=1.0.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from anyio->httpx->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.2.0)\n", - "Requirement already satisfied: build>=1.0.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.0.3)\n", - "Requirement already satisfied: chroma-hnswlib==0.7.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.7.3)\n", - "Requirement already satisfied: fastapi>=0.95.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.109.2)\n", - "Requirement already satisfied: uvicorn>=0.18.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.27.1)\n", - "Requirement already satisfied: posthog>=2.4.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.4.2)\n", - "Requirement already satisfied: pulsar-client>=3.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.4.0)\n", - "Requirement already satisfied: opentelemetry-api>=1.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-exporter-otlp-proto-grpc>=1.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-instrumentation-fastapi>=0.41b0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-sdk>=1.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.22.0)\n", - "Requirement already satisfied: pypika>=0.48.9 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.48.9)\n", - "Requirement already satisfied: overrides>=7.3.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (7.7.0)\n", - "Requirement already satisfied: importlib-resources in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (6.1.1)\n", - "Requirement already satisfied: grpcio>=1.58.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.62.0)\n", - "Requirement already satisfied: bcrypt>=4.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (4.1.2)\n", - "Requirement already satisfied: typer>=0.9.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.9.0)\n", - "Requirement already satisfied: kubernetes>=28.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (29.0.0)\n", - "Requirement already satisfied: mmh3>=4.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (4.1.0)\n", - "Requirement already satisfied: orjson>=3.9.12 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.9.14)\n", - "Requirement already satisfied: packaging>=17.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->llama-index-core<0.11.0,>=0.10.12->llama-index) (23.2)\n", - "Requirement already satisfied: coloredlogs in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from onnxruntime<2.0.0,>=1.17.0->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (15.0.1)\n", - "Requirement already satisfied: flatbuffers in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from onnxruntime<2.0.0,>=1.17.0->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (23.5.26)\n", - "Requirement already satisfied: protobuf in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from onnxruntime<2.0.0,>=1.17.0->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (4.25.3)\n", - "Requirement already satisfied: sympy in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from onnxruntime<2.0.0,>=1.17.0->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.12)\n", - "Requirement already satisfied: six>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas->llama-index-core<0.11.0,>=0.10.12->llama-index) (1.16.0)\n", - "Requirement already satisfied: huggingface_hub<1.0,>=0.16.4 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from tokenizers<0.16.0,>=0.15.1->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.20.3)\n", - "Requirement already satisfied: pyproject_hooks in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from build>=1.0.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.0.0)\n", - "Requirement already satisfied: tomli>=1.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from build>=1.0.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (2.0.1)\n", - "Requirement already satisfied: starlette<0.37.0,>=0.36.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from fastapi>=0.95.2->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.36.3)\n", - "Requirement already satisfied: filelock in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers<0.16.0,>=0.15.1->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.13.1)\n", - "Requirement already satisfied: google-auth>=1.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (2.28.1)\n", - "Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.7.0)\n", - "Requirement already satisfied: requests-oauthlib in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.3.1)\n", - "Requirement already satisfied: oauthlib>=3.2.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.2.2)\n", - "Requirement already satisfied: importlib-metadata<7.0,>=6.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-api>=1.2.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (6.11.0)\n", - "Requirement already satisfied: backoff<3.0.0,>=1.10.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (2.2.1)\n", - "Requirement already satisfied: googleapis-common-protos~=1.52 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.62.0)\n", - "Requirement already satisfied: opentelemetry-exporter-otlp-proto-common==1.22.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-proto==1.22.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-instrumentation-asgi==0.43b0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-instrumentation==0.43b0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-semantic-conventions==0.43b0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-util-http==0.43b0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.43b0)\n", - "Requirement already satisfied: setuptools>=16.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.43b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (69.1.0)\n", - "Requirement already satisfied: asgiref~=3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from opentelemetry-instrumentation-asgi==0.43b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.7.2)\n", - "Requirement already satisfied: monotonic>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from posthog>=2.4.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.6)\n", - "Requirement already satisfied: httptools>=0.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.6.1)\n", - "Requirement already satisfied: python-dotenv>=0.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.0.1)\n", - "Requirement already satisfied: uvloop!=0.15.0,!=0.15.1,>=0.14.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.19.0)\n", - "Requirement already satisfied: watchfiles>=0.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.21.0)\n", - "Requirement already satisfied: websockets>=10.4 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (12.0)\n", - "Requirement already satisfied: humanfriendly>=9.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from coloredlogs->onnxruntime<2.0.0,>=1.17.0->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (10.0)\n", - "Requirement already satisfied: mpmath>=0.19 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from sympy->onnxruntime<2.0.0,>=1.17.0->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (1.3.0)\n", - "Requirement already satisfied: cachetools<6.0,>=2.0.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (5.3.2)\n", - "Requirement already satisfied: pyasn1-modules>=0.2.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.3.0)\n", - "Requirement already satisfied: rsa<5,>=3.1.4 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (4.9)\n", - "Requirement already satisfied: zipp>=0.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from importlib-metadata<7.0,>=6.0->opentelemetry-api>=1.2.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (3.17.0)\n", - "Requirement already satisfied: pyasn1<0.6.0,>=0.4.6 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=28.1.0->chromadb<0.5.0,>=0.4.22->llama-index-vector-stores-chroma<0.2.0,>=0.1.1->llama-index-cli<0.2.0,>=0.1.2->llama-index) (0.5.1)\n", - "Requirement already satisfied: llama-index-vector-stores-mongodb in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (0.1.4)\n", - "Requirement already satisfied: llama-index-core<0.11.0,>=0.10.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-vector-stores-mongodb) (0.10.12)\n", - "Requirement already satisfied: pymongo<5.0.0,>=4.6.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-vector-stores-mongodb) (4.6.2)\n", - "Requirement already satisfied: PyYAML>=6.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (6.0.1)\n", - "Requirement already satisfied: SQLAlchemy>=1.4.49 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2.0.27)\n", - "Requirement already satisfied: aiohttp<4.0.0,>=3.8.6 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.9.3)\n", - "Requirement already satisfied: dataclasses-json in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (0.6.4)\n", - "Requirement already satisfied: deprecated>=1.2.9.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.2.14)\n", - "Requirement already satisfied: dirtyjson<2.0.0,>=1.0.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.0.8)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2023.10.0)\n", - "Requirement already satisfied: httpx in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (0.27.0)\n", - "Requirement already satisfied: llamaindex-py-client<0.2.0,>=0.1.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (0.1.13)\n", - "Requirement already satisfied: nest-asyncio<2.0.0,>=1.5.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.6.0)\n", - "Requirement already satisfied: networkx>=3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.1)\n", - "Requirement already satisfied: nltk<4.0.0,>=3.8.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.8.1)\n", - "Requirement already satisfied: numpy in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.24.4)\n", - "Requirement already satisfied: openai>=1.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.12.0)\n", - "Requirement already satisfied: pandas in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2.0.3)\n", - "Requirement already satisfied: pillow>=9.0.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (10.2.0)\n", - "Requirement already satisfied: requests>=2.31.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2.31.0)\n", - "Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (8.2.3)\n", - "Requirement already satisfied: tiktoken>=0.3.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (0.6.0)\n", - "Requirement already satisfied: tqdm<5.0.0,>=4.66.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (4.66.2)\n", - "Requirement already satisfied: typing-extensions>=4.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (4.9.0)\n", - "Requirement already satisfied: typing-inspect>=0.8.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (0.9.0)\n", - "Requirement already satisfied: dnspython<3.0.0,>=1.16.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pymongo<5.0.0,>=4.6.1->llama-index-vector-stores-mongodb) (2.6.1)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (4.0.3)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from deprecated>=1.2.9.3->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.16.0)\n", - "Requirement already satisfied: pydantic>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.10.11)\n", - "Requirement already satisfied: anyio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (4.3.0)\n", - "Requirement already satisfied: certifi in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2024.2.2)\n", - "Requirement already satisfied: httpcore==1.* in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.0.4)\n", - "Requirement already satisfied: idna in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.6)\n", - "Requirement already satisfied: sniffio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.3.0)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpcore==1.*->httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (0.14.0)\n", - "Requirement already satisfied: click in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (8.1.7)\n", - "Requirement already satisfied: joblib in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.3.2)\n", - "Requirement already satisfied: regex>=2021.8.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2023.12.25)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from openai>=1.1.0->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.9.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.3.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2.2.1)\n", - "Requirement already satisfied: greenlet!=0.4.17 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy>=1.4.49->SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.0.3)\n", - "Requirement already satisfied: mypy-extensions>=0.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from typing-inspect>=0.8.0->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.0.0)\n", - "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from dataclasses-json->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (3.20.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (2024.1)\n", - "Requirement already satisfied: exceptiongroup>=1.0.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from anyio->httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.2.0)\n", - "Requirement already satisfied: packaging>=17.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (23.2)\n", - "Requirement already satisfied: six>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-vector-stores-mongodb) (1.16.0)\n", - "Requirement already satisfied: llama-index-embeddings-fireworks==0.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (0.1.2)\n", - "Requirement already satisfied: llama-index-core<0.11.0,>=0.10.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-embeddings-fireworks==0.1.2) (0.10.12)\n", - "Requirement already satisfied: llama-index-llms-openai<0.2.0,>=0.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-embeddings-fireworks==0.1.2) (0.1.6)\n", - "Requirement already satisfied: PyYAML>=6.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (6.0.1)\n", - "Requirement already satisfied: SQLAlchemy>=1.4.49 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2.0.27)\n", - "Requirement already satisfied: aiohttp<4.0.0,>=3.8.6 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.9.3)\n", - "Requirement already satisfied: dataclasses-json in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (0.6.4)\n", - "Requirement already satisfied: deprecated>=1.2.9.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.2.14)\n", - "Requirement already satisfied: dirtyjson<2.0.0,>=1.0.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.0.8)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2023.10.0)\n", - "Requirement already satisfied: httpx in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (0.27.0)\n", - "Requirement already satisfied: llamaindex-py-client<0.2.0,>=0.1.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (0.1.13)\n", - "Requirement already satisfied: nest-asyncio<2.0.0,>=1.5.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.6.0)\n", - "Requirement already satisfied: networkx>=3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.1)\n", - "Requirement already satisfied: nltk<4.0.0,>=3.8.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.8.1)\n", - "Requirement already satisfied: numpy in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.24.4)\n", - "Requirement already satisfied: openai>=1.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.12.0)\n", - "Requirement already satisfied: pandas in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2.0.3)\n", - "Requirement already satisfied: pillow>=9.0.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (10.2.0)\n", - "Requirement already satisfied: requests>=2.31.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2.31.0)\n", - "Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (8.2.3)\n", - "Requirement already satisfied: tiktoken>=0.3.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (0.6.0)\n", - "Requirement already satisfied: tqdm<5.0.0,>=4.66.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (4.66.2)\n", - "Requirement already satisfied: typing-extensions>=4.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (4.9.0)\n", - "Requirement already satisfied: typing-inspect>=0.8.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (0.9.0)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (4.0.3)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from deprecated>=1.2.9.3->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.16.0)\n", - "Requirement already satisfied: pydantic>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.10.11)\n", - "Requirement already satisfied: anyio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (4.3.0)\n", - "Requirement already satisfied: certifi in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2024.2.2)\n", - "Requirement already satisfied: httpcore==1.* in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.0.4)\n", - "Requirement already satisfied: idna in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.6)\n", - "Requirement already satisfied: sniffio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.3.0)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpcore==1.*->httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (0.14.0)\n", - "Requirement already satisfied: click in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (8.1.7)\n", - "Requirement already satisfied: joblib in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.3.2)\n", - "Requirement already satisfied: regex>=2021.8.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2023.12.25)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from openai>=1.1.0->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.9.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.3.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2.2.1)\n", - "Requirement already satisfied: greenlet!=0.4.17 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy>=1.4.49->SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.0.3)\n", - "Requirement already satisfied: mypy-extensions>=0.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from typing-inspect>=0.8.0->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.0.0)\n", - "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from dataclasses-json->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (3.20.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (2024.1)\n", - "Requirement already satisfied: exceptiongroup>=1.0.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from anyio->httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.2.0)\n", - "Requirement already satisfied: packaging>=17.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (23.2)\n", - "Requirement already satisfied: six>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-embeddings-fireworks==0.1.2) (1.16.0)\n", - "Requirement already satisfied: llama-index-llms-fireworks in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (0.1.1)\n", - "Requirement already satisfied: llama-index-core<0.11.0,>=0.10.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-llms-fireworks) (0.10.12)\n", - "Requirement already satisfied: llama-index-llms-openai<0.2.0,>=0.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-llms-fireworks) (0.1.6)\n", - "Requirement already satisfied: PyYAML>=6.0.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (6.0.1)\n", - "Requirement already satisfied: SQLAlchemy>=1.4.49 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2.0.27)\n", - "Requirement already satisfied: aiohttp<4.0.0,>=3.8.6 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.9.3)\n", - "Requirement already satisfied: dataclasses-json in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (0.6.4)\n", - "Requirement already satisfied: deprecated>=1.2.9.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.2.14)\n", - "Requirement already satisfied: dirtyjson<2.0.0,>=1.0.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.0.8)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2023.10.0)\n", - "Requirement already satisfied: httpx in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (0.27.0)\n", - "Requirement already satisfied: llamaindex-py-client<0.2.0,>=0.1.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (0.1.13)\n", - "Requirement already satisfied: nest-asyncio<2.0.0,>=1.5.8 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.6.0)\n", - "Requirement already satisfied: networkx>=3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.1)\n", - "Requirement already satisfied: nltk<4.0.0,>=3.8.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.8.1)\n", - "Requirement already satisfied: numpy in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.24.4)\n", - "Requirement already satisfied: openai>=1.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.12.0)\n", - "Requirement already satisfied: pandas in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2.0.3)\n", - "Requirement already satisfied: pillow>=9.0.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (10.2.0)\n", - "Requirement already satisfied: requests>=2.31.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2.31.0)\n", - "Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (8.2.3)\n", - "Requirement already satisfied: tiktoken>=0.3.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (0.6.0)\n", - "Requirement already satisfied: tqdm<5.0.0,>=4.66.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (4.66.2)\n", - "Requirement already satisfied: typing-extensions>=4.5.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (4.9.0)\n", - "Requirement already satisfied: typing-inspect>=0.8.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (0.9.0)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (4.0.3)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from deprecated>=1.2.9.3->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.16.0)\n", - "Requirement already satisfied: pydantic>=1.10 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.10.11)\n", - "Requirement already satisfied: anyio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (4.3.0)\n", - "Requirement already satisfied: certifi in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2024.2.2)\n", - "Requirement already satisfied: httpcore==1.* in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.0.4)\n", - "Requirement already satisfied: idna in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.6)\n", - "Requirement already satisfied: sniffio in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.3.0)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from httpcore==1.*->httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (0.14.0)\n", - "Requirement already satisfied: click in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (8.1.7)\n", - "Requirement already satisfied: joblib in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.3.2)\n", - "Requirement already satisfied: regex>=2021.8.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from nltk<4.0.0,>=3.8.1->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2023.12.25)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from openai>=1.1.0->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.9.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.3.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.31.0->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2.2.1)\n", - "Requirement already satisfied: greenlet!=0.4.17 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from SQLAlchemy>=1.4.49->SQLAlchemy[asyncio]>=1.4.49->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.0.3)\n", - "Requirement already satisfied: mypy-extensions>=0.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from typing-inspect>=0.8.0->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.0.0)\n", - "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from dataclasses-json->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (3.20.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (2024.1)\n", - "Requirement already satisfied: exceptiongroup>=1.0.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from anyio->httpx->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.2.0)\n", - "Requirement already satisfied: packaging>=17.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (23.2)\n", - "Requirement already satisfied: six>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas->llama-index-core<0.11.0,>=0.10.1->llama-index-llms-fireworks) (1.16.0)\n", - "Requirement already satisfied: pymongo in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (4.6.2)\n", - "Requirement already satisfied: dnspython<3.0.0,>=1.16.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pymongo) (2.6.1)\n", - "Requirement already satisfied: datasets in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (2.17.1)\n", - "Requirement already satisfied: filelock in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (3.13.1)\n", - "Requirement already satisfied: numpy>=1.17 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (1.24.4)\n", - "Requirement already satisfied: pyarrow>=12.0.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (15.0.0)\n", - "Requirement already satisfied: pyarrow-hotfix in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (0.6)\n", - "Requirement already satisfied: dill<0.3.9,>=0.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (0.3.8)\n", - "Requirement already satisfied: pandas in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (2.0.3)\n", - "Requirement already satisfied: requests>=2.19.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (2.31.0)\n", - "Requirement already satisfied: tqdm>=4.62.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (4.66.2)\n", - "Requirement already satisfied: xxhash in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (3.4.1)\n", - "Requirement already satisfied: multiprocess in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (0.70.16)\n", - "Requirement already satisfied: fsspec<=2023.10.0,>=2023.1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from fsspec[http]<=2023.10.0,>=2023.1.0->datasets) (2023.10.0)\n", - "Requirement already satisfied: aiohttp in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (3.9.3)\n", - "Requirement already satisfied: huggingface-hub>=0.19.4 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (0.20.3)\n", - "Requirement already satisfied: packaging in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (23.2)\n", - "Requirement already satisfied: pyyaml>=5.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from datasets) (6.0.1)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp->datasets) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp->datasets) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp->datasets) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp->datasets) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp->datasets) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from aiohttp->datasets) (4.0.3)\n", - "Requirement already satisfied: typing-extensions>=3.7.4.3 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from huggingface-hub>=0.19.4->datasets) (4.9.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.19.0->datasets) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.19.0->datasets) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.19.0->datasets) (2.2.1)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from requests>=2.19.0->datasets) (2024.2.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->datasets) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->datasets) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas->datasets) (2024.1)\n", - "Requirement already satisfied: six>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas->datasets) (1.16.0)\n", - "Requirement already satisfied: pandas in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (2.0.3)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas) (2024.1)\n", - "Requirement already satisfied: numpy>=1.21.0 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from pandas) (1.24.4)\n", - "Requirement already satisfied: six>=1.5 in /mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)\n" - ] - } - ], + "outputs": [], "source": [ "!pip install -q llama-index llama-index-vector-stores-mongodb llama-index-embeddings-fireworks==0.1.2 llama-index-llms-fireworks\n", "!pip install -q pymongo datasets pandas" @@ -369,6 +29,14 @@ "execution_count": null, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/mnt/disks/data/llama_index/.venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, { "data": { "text/html": [ @@ -596,23 +264,6 @@ "dataset_df.head(5)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Remove data point where fullplot coloumn is missing\n", - "# dataset_df = dataset_df.dropna(subset=[\"fullplot\"])\n", - "# print(\"\\nNumber of missing values in each column after removal:\")\n", - "# print(dataset_df.isnull().sum())\n", - "\n", - "# # Remove the plot_embedding from each data point in the dataset as we are going to create new embeddings with the new OpenAI emebedding Model \"text-embedding-3-small\"\n", - "# dataset_df = dataset_df.drop(columns=[\"plot_embedding\"])\n", - "\n", - "# dataset_df.head(5)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -624,7 +275,7 @@ "from llama_index.embeddings.fireworks import FireworksEmbedding\n", "\n", "embed_model = FireworksEmbedding(\n", - " embed_batch_size=1024,\n", + " embed_batch_size=512,\n", " model_name=\"nomic-ai/nomic-embed-text-v1.5\",\n", " api_key=fw_api_key,\n", ")\n", @@ -659,15 +310,14 @@ "_id=>{'$oid': '6095a34a7c34416a90d3206b'}\n", "name=>\"Baby Bo'S Burritos\"\n", "menu=>null\n", - "TakeOut=>True\n", - "location=>{'coordinates': [-73.975981, 40.745132], 'type': 'Point'}\n", + "TakeOut=>true\n", "PriceRange=>1.0\n", "HappyHour=>null\n", "review_count=>10\n", "sponsored=>None\n", "stars=>2.5\n", "-----\n", - "Content: {\"restaurant_id\": \"40366661\", \"attributes\": \"{\\\"Alcohol\\\": \\\"'none'\\\", \\\"Ambience\\\": \\\"{'romantic': False, 'intimate': False, 'classy': False, 'hipster': False, 'divey': False, 'touristy': False, 'trendy': False, 'upscale': False, 'casual': False}\\\", \\\"BYOB\\\": null, \\\"BestNights\\\": null, \\\"BikeParking\\\": null, \\\"BusinessAcceptsBitcoin\\\": null, \\\"BusinessAcceptsCreditCards\\\": null, \\\"BusinessParking\\\": \\\"None\\\", \\\"Caters\\\": \\\"True\\\", \\\"DriveThru\\\": null, \\\"GoodForDancing\\\": null, \\\"GoodForKids\\\": \\\"True\\\", \\\"GoodForMeal\\\": null, \\\"HasTV\\\": \\\"True\\\", \\\"Music\\\": null, \\\"NoiseLevel\\\": \\\"'average'\\\", \\\"RestaurantsAttire\\\": \\\"'casual'\\\", \\\"RestaurantsDelivery\\\": \\\"True\\\", \\\"RestaurantsGoodForGroups\\\": \\\"True\\\", \\\"RestaurantsReservations\\\": \\\"True\\\", \\\"RestaurantsTableService\\\": \\\"False\\\", \\\"WheelchairAccessible\\\": \\\"True\\\", \\\"WiFi\\\": \\\"'free'\\\"}\", \"cuisine\": \"\\\"Tex-Mex\\\"\", \"DogsAllowed\": null, \"OutdoorSeating\": true, \"borough\": \"\\\"Manhattan\\\"\", \"address\": \"{\\\"building\\\": \\\"627\\\", \\\"coord\\\": [-73.975981, 40.745132], \\\"street\\\": \\\"2 Avenue\\\", \\\"zipcode\\\": \\\"10016\\\"}\", \"_id\": {\"$oid\": \"6095a34a7c34416a90d3206b\"}, \"name\": \"\\\"Baby Bo'S Burritos\\\"\", \"menu\": \"null\", \"TakeOut\": true, \"location\": {\"coordinates\": [-73.975981, 40.745132], \"type\": \"Point\"}, \"PriceRange\": \"1.0\", \"HappyHour\": \"null\", \"review_count\": \"10\", \"sponsored\": null, \"stars\": 2.5}\n", + "Content: {\"restaurant_id\": \"40366661\", \"attributes\": \"{\\\"Alcohol\\\": \\\"'none'\\\", \\\"Ambience\\\": \\\"{'romantic': False, 'intimate': False, 'classy': False, 'hipster': False, 'divey': False, 'touristy': False, 'trendy': False, 'upscale': False, 'casual': False}\\\", \\\"BYOB\\\": null, \\\"BestNights\\\": null, \\\"BikeParking\\\": null, \\\"BusinessAcceptsBitcoin\\\": null, \\\"BusinessAcceptsCreditCards\\\": null, \\\"BusinessParking\\\": \\\"None\\\", \\\"Caters\\\": \\\"True\\\", \\\"DriveThru\\\": null, \\\"GoodForDancing\\\": null, \\\"GoodForKids\\\": \\\"True\\\", \\\"GoodForMeal\\\": null, \\\"HasTV\\\": \\\"True\\\", \\\"Music\\\": null, \\\"NoiseLevel\\\": \\\"'average'\\\", \\\"RestaurantsAttire\\\": \\\"'casual'\\\", \\\"RestaurantsDelivery\\\": \\\"True\\\", \\\"RestaurantsGoodForGroups\\\": \\\"True\\\", \\\"RestaurantsReservations\\\": \\\"True\\\", \\\"RestaurantsTableService\\\": \\\"False\\\", \\\"WheelchairAccessible\\\": \\\"True\\\", \\\"WiFi\\\": \\\"'free'\\\"}\", \"cuisine\": \"\\\"Tex-Mex\\\"\", \"DogsAllowed\": null, \"OutdoorSeating\": true, \"borough\": \"\\\"Manhattan\\\"\", \"address\": \"{\\\"building\\\": \\\"627\\\", \\\"coord\\\": [-73.975981, 40.745132], \\\"street\\\": \\\"2 Avenue\\\", \\\"zipcode\\\": \\\"10016\\\"}\", \"_id\": {\"$oid\": \"6095a34a7c34416a90d3206b\"}, \"name\": \"\\\"Baby Bo'S Burritos\\\"\", \"menu\": \"null\", \"TakeOut\": \"true\", \"PriceRange\": \"1.0\", \"HappyHour\": \"null\", \"review_count\": \"10\", \"sponsored\": null, \"stars\": 2.5}\n", "\n", "The Embedding model sees this: \n", " Metadata: restaurant_id=>40366661\n", @@ -680,15 +330,14 @@ "_id=>{'$oid': '6095a34a7c34416a90d3206b'}\n", "name=>\"Baby Bo'S Burritos\"\n", "menu=>null\n", - "TakeOut=>True\n", - "location=>{'coordinates': [-73.975981, 40.745132], 'type': 'Point'}\n", + "TakeOut=>true\n", "PriceRange=>1.0\n", "HappyHour=>null\n", "review_count=>10\n", "sponsored=>None\n", "stars=>2.5\n", "-----\n", - "Content: {\"restaurant_id\": \"40366661\", \"attributes\": \"{\\\"Alcohol\\\": \\\"'none'\\\", \\\"Ambience\\\": \\\"{'romantic': False, 'intimate': False, 'classy': False, 'hipster': False, 'divey': False, 'touristy': False, 'trendy': False, 'upscale': False, 'casual': False}\\\", \\\"BYOB\\\": null, \\\"BestNights\\\": null, \\\"BikeParking\\\": null, \\\"BusinessAcceptsBitcoin\\\": null, \\\"BusinessAcceptsCreditCards\\\": null, \\\"BusinessParking\\\": \\\"None\\\", \\\"Caters\\\": \\\"True\\\", \\\"DriveThru\\\": null, \\\"GoodForDancing\\\": null, \\\"GoodForKids\\\": \\\"True\\\", \\\"GoodForMeal\\\": null, \\\"HasTV\\\": \\\"True\\\", \\\"Music\\\": null, \\\"NoiseLevel\\\": \\\"'average'\\\", \\\"RestaurantsAttire\\\": \\\"'casual'\\\", \\\"RestaurantsDelivery\\\": \\\"True\\\", \\\"RestaurantsGoodForGroups\\\": \\\"True\\\", \\\"RestaurantsReservations\\\": \\\"True\\\", \\\"RestaurantsTableService\\\": \\\"False\\\", \\\"WheelchairAccessible\\\": \\\"True\\\", \\\"WiFi\\\": \\\"'free'\\\"}\", \"cuisine\": \"\\\"Tex-Mex\\\"\", \"DogsAllowed\": null, \"OutdoorSeating\": true, \"borough\": \"\\\"Manhattan\\\"\", \"address\": \"{\\\"building\\\": \\\"627\\\", \\\"coord\\\": [-73.975981, 40.745132], \\\"street\\\": \\\"2 Avenue\\\", \\\"zipcode\\\": \\\"10016\\\"}\", \"_id\": {\"$oid\": \"6095a34a7c34416a90d3206b\"}, \"name\": \"\\\"Baby Bo'S Burritos\\\"\", \"menu\": \"null\", \"TakeOut\": true, \"location\": {\"coordinates\": [-73.975981, 40.745132], \"type\": \"Point\"}, \"PriceRange\": \"1.0\", \"HappyHour\": \"null\", \"review_count\": \"10\", \"sponsored\": null, \"stars\": 2.5}\n" + "Content: {\"restaurant_id\": \"40366661\", \"attributes\": \"{\\\"Alcohol\\\": \\\"'none'\\\", \\\"Ambience\\\": \\\"{'romantic': False, 'intimate': False, 'classy': False, 'hipster': False, 'divey': False, 'touristy': False, 'trendy': False, 'upscale': False, 'casual': False}\\\", \\\"BYOB\\\": null, \\\"BestNights\\\": null, \\\"BikeParking\\\": null, \\\"BusinessAcceptsBitcoin\\\": null, \\\"BusinessAcceptsCreditCards\\\": null, \\\"BusinessParking\\\": \\\"None\\\", \\\"Caters\\\": \\\"True\\\", \\\"DriveThru\\\": null, \\\"GoodForDancing\\\": null, \\\"GoodForKids\\\": \\\"True\\\", \\\"GoodForMeal\\\": null, \\\"HasTV\\\": \\\"True\\\", \\\"Music\\\": null, \\\"NoiseLevel\\\": \\\"'average'\\\", \\\"RestaurantsAttire\\\": \\\"'casual'\\\", \\\"RestaurantsDelivery\\\": \\\"True\\\", \\\"RestaurantsGoodForGroups\\\": \\\"True\\\", \\\"RestaurantsReservations\\\": \\\"True\\\", \\\"RestaurantsTableService\\\": \\\"False\\\", \\\"WheelchairAccessible\\\": \\\"True\\\", \\\"WiFi\\\": \\\"'free'\\\"}\", \"cuisine\": \"\\\"Tex-Mex\\\"\", \"DogsAllowed\": null, \"OutdoorSeating\": true, \"borough\": \"\\\"Manhattan\\\"\", \"address\": \"{\\\"building\\\": \\\"627\\\", \\\"coord\\\": [-73.975981, 40.745132], \\\"street\\\": \\\"2 Avenue\\\", \\\"zipcode\\\": \\\"10016\\\"}\", \"_id\": {\"$oid\": \"6095a34a7c34416a90d3206b\"}, \"name\": \"\\\"Baby Bo'S Burritos\\\"\", \"menu\": \"null\", \"TakeOut\": \"true\", \"PriceRange\": \"1.0\", \"HappyHour\": \"null\", \"review_count\": \"10\", \"sponsored\": null, \"stars\": 2.5}\n" ] } ], @@ -715,21 +364,16 @@ " document[\"PriceRange\"] = json.dumps(document[\"PriceRange\"])\n", " document[\"HappyHour\"] = json.dumps(document[\"HappyHour\"])\n", " document[\"review_count\"] = json.dumps(document[\"review_count\"])\n", + " document[\"TakeOut\"] = json.dumps(document[\"TakeOut\"])\n", + " # these two fields are not relevant to the question we want to answer,\n", + " # so I will skip it for now\n", " del document[\"embedding\"]\n", + " del document[\"location\"]\n", "\n", " # Create a Document object with the text and excluded metadata for llm and embedding models\n", " llama_document = Document(\n", " text=json.dumps(document),\n", " metadata=document,\n", - " # excluded_llm_metadata_keys=[\"embedding\"],\n", - " # excluded_embed_metadata_keys=[\n", - " # \"fullplot\",\n", - " # \"metacritic\",\n", - " # \"poster\",\n", - " # \"num_mflix_comments\",\n", - " # \"runtime\",\n", - " # \"rated\",\n", - " # ],\n", " metadata_template=\"{key}=>{value}\",\n", " text_template=\"Metadata: {metadata_str}\\n-----\\nContent: {content}\",\n", " )\n", @@ -755,7 +399,7 @@ { "data": { "text/plain": [ - "Document(id_='a4e02dc9-3370-4bbd-8207-b7cb84f802ea', embedding=None, metadata={'restaurant_id': '40366661', 'attributes': '{\"Alcohol\": \"\\'none\\'\", \"Ambience\": \"{\\'romantic\\': False, \\'intimate\\': False, \\'classy\\': False, \\'hipster\\': False, \\'divey\\': False, \\'touristy\\': False, \\'trendy\\': False, \\'upscale\\': False, \\'casual\\': False}\", \"BYOB\": null, \"BestNights\": null, \"BikeParking\": null, \"BusinessAcceptsBitcoin\": null, \"BusinessAcceptsCreditCards\": null, \"BusinessParking\": \"None\", \"Caters\": \"True\", \"DriveThru\": null, \"GoodForDancing\": null, \"GoodForKids\": \"True\", \"GoodForMeal\": null, \"HasTV\": \"True\", \"Music\": null, \"NoiseLevel\": \"\\'average\\'\", \"RestaurantsAttire\": \"\\'casual\\'\", \"RestaurantsDelivery\": \"True\", \"RestaurantsGoodForGroups\": \"True\", \"RestaurantsReservations\": \"True\", \"RestaurantsTableService\": \"False\", \"WheelchairAccessible\": \"True\", \"WiFi\": \"\\'free\\'\"}', 'cuisine': '\"Tex-Mex\"', 'DogsAllowed': None, 'OutdoorSeating': True, 'borough': '\"Manhattan\"', 'address': '{\"building\": \"627\", \"coord\": [-73.975981, 40.745132], \"street\": \"2 Avenue\", \"zipcode\": \"10016\"}', '_id': {'$oid': '6095a34a7c34416a90d3206b'}, 'name': '\"Baby Bo\\'S Burritos\"', 'menu': 'null', 'TakeOut': True, 'location': {'coordinates': [-73.975981, 40.745132], 'type': 'Point'}, 'PriceRange': '1.0', 'HappyHour': 'null', 'review_count': '10', 'sponsored': None, 'stars': 2.5}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text='{\"restaurant_id\": \"40366661\", \"attributes\": \"{\\\\\"Alcohol\\\\\": \\\\\"\\'none\\'\\\\\", \\\\\"Ambience\\\\\": \\\\\"{\\'romantic\\': False, \\'intimate\\': False, \\'classy\\': False, \\'hipster\\': False, \\'divey\\': False, \\'touristy\\': False, \\'trendy\\': False, \\'upscale\\': False, \\'casual\\': False}\\\\\", \\\\\"BYOB\\\\\": null, \\\\\"BestNights\\\\\": null, \\\\\"BikeParking\\\\\": null, \\\\\"BusinessAcceptsBitcoin\\\\\": null, \\\\\"BusinessAcceptsCreditCards\\\\\": null, \\\\\"BusinessParking\\\\\": \\\\\"None\\\\\", \\\\\"Caters\\\\\": \\\\\"True\\\\\", \\\\\"DriveThru\\\\\": null, \\\\\"GoodForDancing\\\\\": null, \\\\\"GoodForKids\\\\\": \\\\\"True\\\\\", \\\\\"GoodForMeal\\\\\": null, \\\\\"HasTV\\\\\": \\\\\"True\\\\\", \\\\\"Music\\\\\": null, \\\\\"NoiseLevel\\\\\": \\\\\"\\'average\\'\\\\\", \\\\\"RestaurantsAttire\\\\\": \\\\\"\\'casual\\'\\\\\", \\\\\"RestaurantsDelivery\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsGoodForGroups\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsReservations\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsTableService\\\\\": \\\\\"False\\\\\", \\\\\"WheelchairAccessible\\\\\": \\\\\"True\\\\\", \\\\\"WiFi\\\\\": \\\\\"\\'free\\'\\\\\"}\", \"cuisine\": \"\\\\\"Tex-Mex\\\\\"\", \"DogsAllowed\": null, \"OutdoorSeating\": true, \"borough\": \"\\\\\"Manhattan\\\\\"\", \"address\": \"{\\\\\"building\\\\\": \\\\\"627\\\\\", \\\\\"coord\\\\\": [-73.975981, 40.745132], \\\\\"street\\\\\": \\\\\"2 Avenue\\\\\", \\\\\"zipcode\\\\\": \\\\\"10016\\\\\"}\", \"_id\": {\"$oid\": \"6095a34a7c34416a90d3206b\"}, \"name\": \"\\\\\"Baby Bo\\'S Burritos\\\\\"\", \"menu\": \"null\", \"TakeOut\": true, \"location\": {\"coordinates\": [-73.975981, 40.745132], \"type\": \"Point\"}, \"PriceRange\": \"1.0\", \"HappyHour\": \"null\", \"review_count\": \"10\", \"sponsored\": null, \"stars\": 2.5}', start_char_idx=None, end_char_idx=None, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n')" + "Document(id_='93d3f08d-85f3-494d-a057-19bc834abc29', embedding=None, metadata={'restaurant_id': '40366661', 'attributes': '{\"Alcohol\": \"\\'none\\'\", \"Ambience\": \"{\\'romantic\\': False, \\'intimate\\': False, \\'classy\\': False, \\'hipster\\': False, \\'divey\\': False, \\'touristy\\': False, \\'trendy\\': False, \\'upscale\\': False, \\'casual\\': False}\", \"BYOB\": null, \"BestNights\": null, \"BikeParking\": null, \"BusinessAcceptsBitcoin\": null, \"BusinessAcceptsCreditCards\": null, \"BusinessParking\": \"None\", \"Caters\": \"True\", \"DriveThru\": null, \"GoodForDancing\": null, \"GoodForKids\": \"True\", \"GoodForMeal\": null, \"HasTV\": \"True\", \"Music\": null, \"NoiseLevel\": \"\\'average\\'\", \"RestaurantsAttire\": \"\\'casual\\'\", \"RestaurantsDelivery\": \"True\", \"RestaurantsGoodForGroups\": \"True\", \"RestaurantsReservations\": \"True\", \"RestaurantsTableService\": \"False\", \"WheelchairAccessible\": \"True\", \"WiFi\": \"\\'free\\'\"}', 'cuisine': '\"Tex-Mex\"', 'DogsAllowed': None, 'OutdoorSeating': True, 'borough': '\"Manhattan\"', 'address': '{\"building\": \"627\", \"coord\": [-73.975981, 40.745132], \"street\": \"2 Avenue\", \"zipcode\": \"10016\"}', '_id': {'$oid': '6095a34a7c34416a90d3206b'}, 'name': '\"Baby Bo\\'S Burritos\"', 'menu': 'null', 'TakeOut': 'true', 'PriceRange': '1.0', 'HappyHour': 'null', 'review_count': '10', 'sponsored': None, 'stars': 2.5}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text='{\"restaurant_id\": \"40366661\", \"attributes\": \"{\\\\\"Alcohol\\\\\": \\\\\"\\'none\\'\\\\\", \\\\\"Ambience\\\\\": \\\\\"{\\'romantic\\': False, \\'intimate\\': False, \\'classy\\': False, \\'hipster\\': False, \\'divey\\': False, \\'touristy\\': False, \\'trendy\\': False, \\'upscale\\': False, \\'casual\\': False}\\\\\", \\\\\"BYOB\\\\\": null, \\\\\"BestNights\\\\\": null, \\\\\"BikeParking\\\\\": null, \\\\\"BusinessAcceptsBitcoin\\\\\": null, \\\\\"BusinessAcceptsCreditCards\\\\\": null, \\\\\"BusinessParking\\\\\": \\\\\"None\\\\\", \\\\\"Caters\\\\\": \\\\\"True\\\\\", \\\\\"DriveThru\\\\\": null, \\\\\"GoodForDancing\\\\\": null, \\\\\"GoodForKids\\\\\": \\\\\"True\\\\\", \\\\\"GoodForMeal\\\\\": null, \\\\\"HasTV\\\\\": \\\\\"True\\\\\", \\\\\"Music\\\\\": null, \\\\\"NoiseLevel\\\\\": \\\\\"\\'average\\'\\\\\", \\\\\"RestaurantsAttire\\\\\": \\\\\"\\'casual\\'\\\\\", \\\\\"RestaurantsDelivery\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsGoodForGroups\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsReservations\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsTableService\\\\\": \\\\\"False\\\\\", \\\\\"WheelchairAccessible\\\\\": \\\\\"True\\\\\", \\\\\"WiFi\\\\\": \\\\\"\\'free\\'\\\\\"}\", \"cuisine\": \"\\\\\"Tex-Mex\\\\\"\", \"DogsAllowed\": null, \"OutdoorSeating\": true, \"borough\": \"\\\\\"Manhattan\\\\\"\", \"address\": \"{\\\\\"building\\\\\": \\\\\"627\\\\\", \\\\\"coord\\\\\": [-73.975981, 40.745132], \\\\\"street\\\\\": \\\\\"2 Avenue\\\\\", \\\\\"zipcode\\\\\": \\\\\"10016\\\\\"}\", \"_id\": {\"$oid\": \"6095a34a7c34416a90d3206b\"}, \"name\": \"\\\\\"Baby Bo\\'S Burritos\\\\\"\", \"menu\": \"null\", \"TakeOut\": \"true\", \"PriceRange\": \"1.0\", \"HappyHour\": \"null\", \"review_count\": \"10\", \"sponsored\": null, \"stars\": 2.5}', start_char_idx=None, end_char_idx=None, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n')" ] }, "execution_count": null, @@ -777,10 +421,24 @@ "\n", "parser = SentenceSplitter()\n", "nodes = parser.get_nodes_from_documents(llama_documents)\n", + "# 25k nodes takes about 10 minutes, will trim it down to 2.5k\n", + "new_nodes = nodes[:2500]\n", "\n", "# There are 25k documents, so we need to do batching. Fortunately LlamaIndex provides good batching\n", "# for embedding models, and we are going to rely on the __call__ method for the model to handle this\n", - "node_embeddings = embed_model(nodes)" + "node_embeddings = embed_model(new_nodes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for idx, n in enumerate(new_nodes):\n", + " n.embedding = node_embeddings[idx].embedding\n", + " if \"_id\" in n.metadata:\n", + " del n.metadata[\"_id\"]" ] }, { @@ -813,7 +471,6 @@ ], "source": [ "import pymongo\n", - "from google.colab import userdata\n", "\n", "\n", "def get_mongo_client(mongo_uri):\n", @@ -827,14 +484,18 @@ " return None\n", "\n", "\n", - "mongo_uri = userdata.get(\"MONGO_URI\")\n", + "# set up Fireworks.ai Key\n", + "import os\n", + "import getpass\n", + "\n", + "mongo_uri = getpass.getpass(\"MONGO_URI:\")\n", "if not mongo_uri:\n", - " print(\"MONGO_URI not set in environment variables\")\n", + " print(\"MONGO_URI not set\")\n", "\n", "mongo_client = get_mongo_client(mongo_uri)\n", "\n", - "DB_NAME = \"movies\"\n", - "COLLECTION_NAME = \"movies_records\"\n", + "DB_NAME = \"whatscooking\"\n", + "COLLECTION_NAME = \"restaurants\"\n", "\n", "db = mongo_client[DB_NAME]\n", "collection = db[COLLECTION_NAME]" @@ -848,7 +509,7 @@ { "data": { "text/plain": [ - "DeleteResult({'n': 0, 'electionId': ObjectId('7fffffff000000000000000a'), 'opTime': {'ts': Timestamp(1708000722, 1), 't': 10}, 'ok': 1.0, '$clusterTime': {'clusterTime': Timestamp(1708000722, 1), 'signature': {'hash': b'\\xd8\\x1a\\xaci\\xf5EN+\\xe2\\xd1\\xb3y8.${u5P\\xf3', 'keyId': 7320226449804230661}}, 'operationTime': Timestamp(1708000722, 1)}, acknowledged=True)" + "DeleteResult({'n': 0, 'electionId': ObjectId('7fffffff00000000000001ce'), 'opTime': {'ts': Timestamp(1708970193, 3), 't': 462}, 'ok': 1.0, '$clusterTime': {'clusterTime': Timestamp(1708970193, 3), 'signature': {'hash': b'\\x9a3H8\\xa1\\x1b\\xb6\\xbb\\xa9\\xc3x\\x17\\x1c\\xeb\\xe9\\x03\\xaa\\xf8\\xf17', 'keyId': 7294687148333072386}}, 'operationTime': Timestamp(1708970193, 3)}, acknowledged=True)" ] }, "execution_count": null, @@ -876,7 +537,14 @@ " collection_name=COLLECTION_NAME,\n", " index_name=\"vector_index\",\n", ")\n", - "vector_store.add(nodes)" + "vector_store.add(new_nodes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# now make sure you create the search index with the right name here" ] }, { @@ -890,6 +558,23 @@ "index = VectorStoreIndex.from_vector_store(vector_store)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install -q matplotlib" + ] + }, { "cell_type": "code", "execution_count": null, @@ -898,7 +583,13 @@ { "data": { "text/markdown": [ - "**`Final Response:`** The movie \"Romancing the Stone\" would be a suitable romantic movie for the Christmas season. It is a romantic adventure film that follows a romance writer who sets off on a dangerous adventure to rescue her kidnapped sister. The movie has elements of romance, adventure, and comedy, making it an entertaining choice for the holiday season. Additionally, the movie has received positive reviews and has been nominated for awards, indicating its quality." + "**`Final Response:`** Based on the context provided, two restaurant options that don't serve alcohol are:\n", + "\n", + "1. \"Academy Restauraunt\" in Brooklyn, which serves American cuisine and has a variety of dishes such as Mozzarella sticks, Cheeseburger, Baked potato, Breadsticks, Caesar salad, Chicken parmesan, Pigs in a blanket, Chicken soup, Mac & cheese, Mushroom swiss burger, Spaghetti with meatballs, and Mashed potatoes.\n", + "\n", + "2. \"Gabriel'S Bar & Grill\" in Manhattan, which specializes in Italian cuisine and offers dishes like Cheese Ravioli, Neapolitan Pizza, assorted gelato, Vegetarian Baked Ziti, Vegetarian Broccoli Pizza, Lasagna, Buca Trio Platter, Spinach Ravioli, Pasta with ricotta cheese, Spaghetti, Fried calamari, and Alfredo Pizza.\n", + "\n", + "Both restaurants offer outdoor seating, are kid-friendly, and have a casual dress code. They also provide take-out service and have happy hour promotions." ], "text/plain": [ "" @@ -911,9 +602,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "[NodeWithScore(node=TextNode(id_='c6bbc236-e21d-49ab-b43d-db920b4946e6', embedding=None, metadata={'awards': '{\"nominations\": 2, \"text\": \"Nominated for 1 Oscar. Another 6 wins & 2 nominations.\", \"wins\": 7}', 'metacritic': None, 'rated': 'PG', 'fullplot': \"Joan Wilder, a mousy romance novelist, receives a treasure map in the mail from her recently murdered brother-in-law. Meanwhile, her sister Elaine is kidnapped in Colombia and the two criminals responsible demand that she travel to Colombia to exchange the map for her sister. Joan does, and quickly becomes lost in the jungle after being waylayed by Zolo, a vicious and corrupt Colombian cop who will stop at nothing to obtain the map. There, she meets an irreverent soldier-of-fortune named Jack Colton who agrees to bring her back to civilization. Together, they embark upon an adventure that could be straight out of Joan's novels.\", 'title': 'Romancing the Stone', 'writers': '[\"Diane Thomas\"]', 'languages': '[\"English\", \"Spanish\", \"French\"]', 'plot': 'A romance writer sets off to Colombia to ransom her kidnapped sister, and soon finds herself in the middle of a dangerous adventure.', 'runtime': 106.0, 'countries': '[\"USA\", \"Mexico\"]', 'genres': '[\"Action\", \"Adventure\", \"Comedy\"]', 'directors': '[\"Robert Zemeckis\"]', 'cast': '[\"Michael Douglas\", \"Kathleen Turner\", \"Danny DeVito\", \"Zack Norman\"]', 'type': 'movie', 'imdb': '{\"id\": 88011, \"rating\": 6.9, \"votes\": 59403}', 'poster': 'https://m.media-amazon.com/images/M/MV5BMDAwNjljMzEtMTc3Yy00NDg2LThjNDAtNjc0NGYyYjM2M2I1XkEyXkFqcGdeQXVyNDE5MTU2MDE@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 0}, excluded_embed_metadata_keys=['fullplot', 'metacritic', 'poster', 'num_mflix_comments', 'runtime', 'rated'], excluded_llm_metadata_keys=['fullplot', 'metacritic'], relationships={: RelatedNodeInfo(node_id='e50144b0-96ba-4a5a-b90a-3a2419f5b380', node_type=, metadata={'awards': '{\"nominations\": 2, \"text\": \"Nominated for 1 Oscar. Another 6 wins & 2 nominations.\", \"wins\": 7}', 'metacritic': None, 'rated': 'PG', 'fullplot': \"Joan Wilder, a mousy romance novelist, receives a treasure map in the mail from her recently murdered brother-in-law. Meanwhile, her sister Elaine is kidnapped in Colombia and the two criminals responsible demand that she travel to Colombia to exchange the map for her sister. Joan does, and quickly becomes lost in the jungle after being waylayed by Zolo, a vicious and corrupt Colombian cop who will stop at nothing to obtain the map. There, she meets an irreverent soldier-of-fortune named Jack Colton who agrees to bring her back to civilization. Together, they embark upon an adventure that could be straight out of Joan's novels.\", 'title': 'Romancing the Stone', 'writers': '[\"Diane Thomas\"]', 'languages': '[\"English\", \"Spanish\", \"French\"]', 'plot': 'A romance writer sets off to Colombia to ransom her kidnapped sister, and soon finds herself in the middle of a dangerous adventure.', 'runtime': 106.0, 'countries': '[\"USA\", \"Mexico\"]', 'genres': '[\"Action\", \"Adventure\", \"Comedy\"]', 'directors': '[\"Robert Zemeckis\"]', 'cast': '[\"Michael Douglas\", \"Kathleen Turner\", \"Danny DeVito\", \"Zack Norman\"]', 'type': 'movie', 'imdb': '{\"id\": 88011, \"rating\": 6.9, \"votes\": 59403}', 'poster': 'https://m.media-amazon.com/images/M/MV5BMDAwNjljMzEtMTc3Yy00NDg2LThjNDAtNjc0NGYyYjM2M2I1XkEyXkFqcGdeQXVyNDE5MTU2MDE@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 0}, hash='b984e4f203b7b67eae14afa890718adb800a5816661ac2edf412aa96fd7dc10b'), : RelatedNodeInfo(node_id='f895e43a-038a-4a1c-8a82-0e22868e35d7', node_type=, metadata={'awards': '{\"nominations\": 1, \"text\": \"1 nomination.\", \"wins\": 0}', 'metacritic': None, 'rated': 'R', 'fullplot': \"Chicago psychiatrist Judd Stevens (Roger Moore) is suspected of murdering one of his patients when the man turns up stabbed to death in the middle of the city. After repeated attempts to convince cops Rod Steiger and Elliott Gould of his innocence, Dr.Stevens is forced to go after the real villains himself, and he finds himself up against one of the city's most notorious Mafia kingpins.\", 'title': 'The Naked Face', 'writers': '[\"Bryan Forbes\", \"Sidney Sheldon (novel)\"]', 'languages': '[\"English\"]', 'plot': 'Chicago psychiatrist Judd Stevens (Roger Moore) is suspected of murdering one of his patients when the man turns up stabbed to death in the middle of the city. After repeated attempts to ...', 'runtime': 103.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Mystery\", \"Thriller\"]', 'directors': '[\"Bryan Forbes\"]', 'cast': '[\"Roger Moore\", \"Rod Steiger\", \"Elliott Gould\", \"Art Carney\"]', 'type': 'movie', 'imdb': '{\"id\": 87777, \"rating\": 5.3, \"votes\": 654}', 'poster': 'https://m.media-amazon.com/images/M/MV5BMTg0NDM4MTY0NV5BMl5BanBnXkFtZTcwNTcwOTc2NA@@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 1}, hash='066e2b3d12c5fab61175f52dd625ec41fb1fce1fe6fe4c892774227c576fdbbd'), : RelatedNodeInfo(node_id='e31f1142-c6b6-4183-b14b-1634166b9d1f', node_type=, metadata={}, hash='9b9127e21d18792749a7a35321e04d29b8d77f7b454b0133205f9de1090038b4')}, text=\"Joan Wilder, a mousy romance novelist, receives a treasure map in the mail from her recently murdered brother-in-law. Meanwhile, her sister Elaine is kidnapped in Colombia and the two criminals responsible demand that she travel to Colombia to exchange the map for her sister. Joan does, and quickly becomes lost in the jungle after being waylayed by Zolo, a vicious and corrupt Colombian cop who will stop at nothing to obtain the map. There, she meets an irreverent soldier-of-fortune named Jack Colton who agrees to bring her back to civilization. Together, they embark upon an adventure that could be straight out of Joan's novels.\", start_char_idx=0, end_char_idx=635, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n'), score=0.7502920627593994),\n", - " NodeWithScore(node=TextNode(id_='5c7cef95-79e3-4c96-a009-4154ea125240', embedding=None, metadata={'awards': '{\"nominations\": 2, \"text\": \"Nominated for 2 Oscars. Another 1 win & 2 nominations.\", \"wins\": 3}', 'metacritic': 64.0, 'rated': 'PG-13', 'fullplot': 'In 1880, four men travel together to the city of Silverado. They come across with many dangers before they finally engage the \"bad guys\" and bring peace and equality back to the city.', 'title': 'Silverado', 'writers': '[\"Lawrence Kasdan\", \"Mark Kasdan\"]', 'languages': '[\"English\"]', 'plot': 'A misfit bunch of friends come together to right the injustices which exist in a small town.', 'runtime': 133.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Crime\", \"Drama\"]', 'directors': '[\"Lawrence Kasdan\"]', 'cast': '[\"Kevin Kline\", \"Scott Glenn\", \"Kevin Costner\", \"Danny Glover\"]', 'type': 'movie', 'imdb': '{\"id\": 90022, \"rating\": 7.2, \"votes\": 26415}', 'poster': 'https://m.media-amazon.com/images/M/MV5BYTljNTE5YmUtMGEyZi00ZjI4LWEzYjUtZDY2YWEwNzVmZjRkXkEyXkFqcGdeQXVyNTI4MjkwNjA@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 1}, excluded_embed_metadata_keys=['fullplot', 'metacritic', 'poster', 'num_mflix_comments', 'runtime', 'rated'], excluded_llm_metadata_keys=['fullplot', 'metacritic'], relationships={: RelatedNodeInfo(node_id='decbc30c-c17e-4ba4-bd1e-72dce4ce383a', node_type=, metadata={'awards': '{\"nominations\": 2, \"text\": \"Nominated for 2 Oscars. Another 1 win & 2 nominations.\", \"wins\": 3}', 'metacritic': 64.0, 'rated': 'PG-13', 'fullplot': 'In 1880, four men travel together to the city of Silverado. They come across with many dangers before they finally engage the \"bad guys\" and bring peace and equality back to the city.', 'title': 'Silverado', 'writers': '[\"Lawrence Kasdan\", \"Mark Kasdan\"]', 'languages': '[\"English\"]', 'plot': 'A misfit bunch of friends come together to right the injustices which exist in a small town.', 'runtime': 133.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Crime\", \"Drama\"]', 'directors': '[\"Lawrence Kasdan\"]', 'cast': '[\"Kevin Kline\", \"Scott Glenn\", \"Kevin Costner\", \"Danny Glover\"]', 'type': 'movie', 'imdb': '{\"id\": 90022, \"rating\": 7.2, \"votes\": 26415}', 'poster': 'https://m.media-amazon.com/images/M/MV5BYTljNTE5YmUtMGEyZi00ZjI4LWEzYjUtZDY2YWEwNzVmZjRkXkEyXkFqcGdeQXVyNTI4MjkwNjA@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 1}, hash='80b77d835c7dfad9d57d300cf69ba388704e6f282f49dc23106489db03b8b441'), : RelatedNodeInfo(node_id='1c04fb7f-ff8f-4e8c-84f6-74c57251446a', node_type=, metadata={'awards': '{\"nominations\": 5, \"text\": \"Nominated for 3 Oscars. Another 2 wins & 5 nominations.\", \"wins\": 5}', 'metacritic': None, 'rated': 'R', 'fullplot': 'A hardened convict and a younger prisoner escape from a brutal prison in the middle of winter only to find themselves on an out-of-control train with a female railway worker while being pursued by the vengeful head of security.', 'title': 'Runaway Train', 'writers': '[\"Djordje Milicevic (screenplay)\", \"Paul Zindel (screenplay)\", \"Edward Bunker (screenplay)\", \"Akira Kurosawa (based on a screenplay by)\"]', 'languages': '[\"English\"]', 'plot': 'Two escaped convicts and a female railway worker find themselves trapped on a train with no brakes and nobody driving.', 'runtime': 111.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Adventure\", \"Drama\"]', 'directors': '[\"Andrey Konchalovskiy\"]', 'cast': '[\"Jon Voight\", \"Eric Roberts\", \"Rebecca De Mornay\", \"Kyle T. Heffner\"]', 'type': 'movie', 'imdb': '{\"id\": 89941, \"rating\": 7.3, \"votes\": 19652}', 'poster': 'https://m.media-amazon.com/images/M/MV5BODQyYWU1NGUtNjEzYS00YmNhLTk1YWEtZDdlZGQzMTI4MTI1XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 0}, hash='378c16de972df97080db94775cd46e57f6a0dd5a7472b357e0285eed2e3b7775'), : RelatedNodeInfo(node_id='5df9410b-6597-45f4-95d5-fee1db8737b1', node_type=, metadata={}, hash='77e93faace9b0e102635d3ca997ff27bc03dbba66eaa2d830f0634289d16d927')}, text='In 1880, four men travel together to the city of Silverado. They come across with many dangers before they finally engage the \"bad guys\" and bring peace and equality back to the city.', start_char_idx=0, end_char_idx=183, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n'), score=0.7419796586036682),\n", - " NodeWithScore(node=TextNode(id_='ff28e815-5db5-4963-a9b8-99c64716eb00', embedding=None, metadata={'awards': '{\"nominations\": 1, \"text\": \"1 nomination.\", \"wins\": 0}', 'metacritic': None, 'rated': 'PASSED', 'fullplot': \"Dick Powell stars as Haven, a government private investigator assigned to investigate the murders of two cavalrymen. Travelling incognito, Haven arrives in a small frontier outpost, where saloon singer Charlie controls all illegal activities. After making short work of Charlie's burly henchman, Haven gets a job at her gambling emporium, biding his time and gathering evidence against the gorgeous crime chieftain Cast as a philosophical bartender, Burl Ives is afforded at least one opportunity to sing.\", 'title': 'Station West', 'writers': '[\"Frank Fenton (screenplay)\", \"Winston Miller (screenplay)\", \"Luke Short (novel)\"]', 'languages': '[\"English\"]', 'plot': 'When two US cavalrymen transporting a gold shipment get killed, US Army Intelligence investigator John Haven goes undercover to a mining and logging town to find the killers.', 'runtime': 87.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Mystery\", \"Romance\"]', 'directors': '[\"Sidney Lanfield\"]', 'cast': '[\"Dick Powell\", \"Jane Greer\", \"Agnes Moorehead\", \"Burl Ives\"]', 'type': 'movie', 'imdb': '{\"id\": 40835, \"rating\": 6.8, \"votes\": 578}', 'poster': 'https://m.media-amazon.com/images/M/MV5BN2U3YWJjOWItOWY3Yy00NTMxLTkxMGUtOTQ1MzEzODM2MjRjXkEyXkFqcGdeQXVyNTk1MTk0MDI@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 1}, excluded_embed_metadata_keys=['fullplot', 'metacritic', 'poster', 'num_mflix_comments', 'runtime', 'rated'], excluded_llm_metadata_keys=['fullplot', 'metacritic'], relationships={: RelatedNodeInfo(node_id='b04254ab-2edb-47c1-9412-646575747ca8', node_type=, metadata={'awards': '{\"nominations\": 1, \"text\": \"1 nomination.\", \"wins\": 0}', 'metacritic': None, 'rated': 'PASSED', 'fullplot': \"Dick Powell stars as Haven, a government private investigator assigned to investigate the murders of two cavalrymen. Travelling incognito, Haven arrives in a small frontier outpost, where saloon singer Charlie controls all illegal activities. After making short work of Charlie's burly henchman, Haven gets a job at her gambling emporium, biding his time and gathering evidence against the gorgeous crime chieftain Cast as a philosophical bartender, Burl Ives is afforded at least one opportunity to sing.\", 'title': 'Station West', 'writers': '[\"Frank Fenton (screenplay)\", \"Winston Miller (screenplay)\", \"Luke Short (novel)\"]', 'languages': '[\"English\"]', 'plot': 'When two US cavalrymen transporting a gold shipment get killed, US Army Intelligence investigator John Haven goes undercover to a mining and logging town to find the killers.', 'runtime': 87.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Mystery\", \"Romance\"]', 'directors': '[\"Sidney Lanfield\"]', 'cast': '[\"Dick Powell\", \"Jane Greer\", \"Agnes Moorehead\", \"Burl Ives\"]', 'type': 'movie', 'imdb': '{\"id\": 40835, \"rating\": 6.8, \"votes\": 578}', 'poster': 'https://m.media-amazon.com/images/M/MV5BN2U3YWJjOWItOWY3Yy00NTMxLTkxMGUtOTQ1MzEzODM2MjRjXkEyXkFqcGdeQXVyNTk1MTk0MDI@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 1}, hash='90f541ac96dcffa4ac639e6ac25da415471164bf8d7930a29b6aed406d631ede'), : RelatedNodeInfo(node_id='a48d8737-8615-48c1-9d4a-1ee127e34fb9', node_type=, metadata={'awards': '{\"nominations\": 1, \"text\": \"1 nomination.\", \"wins\": 0}', 'metacritic': None, 'rated': 'PASSED', 'fullplot': 'Jefty, owner of a roadhouse in a backwoods town, hires sultry, tough-talking torch singer Lily Stevens against the advice of his manager Pete Morgan. Jefty is smitten with Lily, who in turn exerts her charms on the more resistant Pete. When Pete finally falls for her and she turns down Jefty\\'s marriage proposal, they must face Jefty\\'s murderous jealousy and his twisted plots to \"punish\" the two.', 'title': 'Road House', 'writers': '[\"Edward Chodorov (screen play)\", \"Margaret Gruen (story)\", \"Oscar Saul (story)\"]', 'languages': '[\"English\"]', 'plot': 'A night club owner becomes infatuated with a torch singer and frames his best friend/manager for embezzlement when the chanteuse falls in love with him.', 'runtime': 95.0, 'countries': '[\"USA\"]', 'genres': '[\"Action\", \"Drama\", \"Film-Noir\"]', 'directors': '[\"Jean Negulesco\"]', 'cast': '[\"Ida Lupino\", \"Cornel Wilde\", \"Celeste Holm\", \"Richard Widmark\"]', 'type': 'movie', 'imdb': '{\"id\": 40740, \"rating\": 7.3, \"votes\": 1353}', 'poster': 'https://m.media-amazon.com/images/M/MV5BMjc1ZTNkM2UtYzY3Yi00ZWZmLTljYmEtNjYxZDNmYzk2ZjkzXkEyXkFqcGdeQXVyMjUxODE0MDY@._V1_SY1000_SX677_AL_.jpg', 'num_mflix_comments': 2}, hash='040b4a77fcc8fbb5347620e99a217d67b85dcdbd370d91bd23877722a499079f'), : RelatedNodeInfo(node_id='75f37fbc-d75e-4a76-b86f-f15d9260afd1', node_type=, metadata={}, hash='9941706d03783561f3fc3200c26527493a62307f8532dcda60b20948c886b330')}, text=\"Dick Powell stars as Haven, a government private investigator assigned to investigate the murders of two cavalrymen. Travelling incognito, Haven arrives in a small frontier outpost, where saloon singer Charlie controls all illegal activities. After making short work of Charlie's burly henchman, Haven gets a job at her gambling emporium, biding his time and gathering evidence against the gorgeous crime chieftain Cast as a philosophical bartender, Burl Ives is afforded at least one opportunity to sing.\", start_char_idx=0, end_char_idx=505, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n'), score=0.7337073087692261)]\n" + "[NodeWithScore(node=TextNode(id_='5405e68c-19f2-4a65-95d7-f880fa6a8deb', embedding=None, metadata={'restaurant_id': '40385767', 'attributes': '{\"Alcohol\": \"u\\'beer_and_wine\\'\", \"Ambience\": \"{\\'touristy\\': False, \\'hipster\\': False, \\'romantic\\': False, \\'divey\\': False, \\'intimate\\': None, \\'trendy\\': None, \\'upscale\\': False, \\'classy\\': False, \\'casual\\': True}\", \"BYOB\": null, \"BestNights\": \"{\\'monday\\': False, \\'tuesday\\': False, \\'friday\\': True, \\'wednesday\\': False, \\'thursday\\': False, \\'sunday\\': False, \\'saturday\\': True}\", \"BikeParking\": \"True\", \"BusinessAcceptsBitcoin\": \"False\", \"BusinessAcceptsCreditCards\": \"True\", \"BusinessParking\": \"{\\'garage\\': False, \\'street\\': False, \\'validated\\': False, \\'lot\\': True, \\'valet\\': False}\", \"Caters\": \"True\", \"DriveThru\": null, \"GoodForDancing\": \"False\", \"GoodForKids\": \"True\", \"GoodForMeal\": \"{\\'dessert\\': False, \\'latenight\\': False, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': False, \\'breakfast\\': False}\", \"HasTV\": \"True\", \"Music\": \"{\\'dj\\': False, \\'background_music\\': False, \\'no_music\\': False, \\'jukebox\\': False, \\'live\\': False, \\'video\\': False, \\'karaoke\\': False}\", \"NoiseLevel\": \"u\\'average\\'\", \"RestaurantsAttire\": \"u\\'casual\\'\", \"RestaurantsDelivery\": \"None\", \"RestaurantsGoodForGroups\": \"True\", \"RestaurantsReservations\": \"True\", \"RestaurantsTableService\": \"True\", \"WheelchairAccessible\": \"True\", \"WiFi\": \"u\\'free\\'\"}', 'cuisine': '\"American\"', 'DogsAllowed': True, 'OutdoorSeating': True, 'borough': '\"Brooklyn\"', 'address': '{\"building\": \"69\", \"coord\": [-73.9757464, 40.687295], \"street\": \"Lafayette Avenue\", \"zipcode\": \"11217\"}', 'name': '\"Academy Restauraunt\"', 'menu': '[\"Mozzarella sticks\", \"Cheeseburger\", \"Baked potato\", \"Breadsticks\", \"Caesar salad\", \"Chicken parmesan\", \"Pigs in a blanket\", \"Chicken soup\", \"Mac & cheese\", \"Mushroom swiss burger\", \"Spaghetti with meatballs\", \"Mashed potatoes\"]', 'TakeOut': 'true', 'PriceRange': '2.0', 'HappyHour': 'true', 'review_count': '173', 'sponsored': None, 'stars': 4.5}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={: RelatedNodeInfo(node_id='bbfc4bf5-d9c3-4f3b-8c1f-ddcf94f3b5df', node_type=, metadata={'restaurant_id': '40385767', 'attributes': '{\"Alcohol\": \"u\\'beer_and_wine\\'\", \"Ambience\": \"{\\'touristy\\': False, \\'hipster\\': False, \\'romantic\\': False, \\'divey\\': False, \\'intimate\\': None, \\'trendy\\': None, \\'upscale\\': False, \\'classy\\': False, \\'casual\\': True}\", \"BYOB\": null, \"BestNights\": \"{\\'monday\\': False, \\'tuesday\\': False, \\'friday\\': True, \\'wednesday\\': False, \\'thursday\\': False, \\'sunday\\': False, \\'saturday\\': True}\", \"BikeParking\": \"True\", \"BusinessAcceptsBitcoin\": \"False\", \"BusinessAcceptsCreditCards\": \"True\", \"BusinessParking\": \"{\\'garage\\': False, \\'street\\': False, \\'validated\\': False, \\'lot\\': True, \\'valet\\': False}\", \"Caters\": \"True\", \"DriveThru\": null, \"GoodForDancing\": \"False\", \"GoodForKids\": \"True\", \"GoodForMeal\": \"{\\'dessert\\': False, \\'latenight\\': False, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': False, \\'breakfast\\': False}\", \"HasTV\": \"True\", \"Music\": \"{\\'dj\\': False, \\'background_music\\': False, \\'no_music\\': False, \\'jukebox\\': False, \\'live\\': False, \\'video\\': False, \\'karaoke\\': False}\", \"NoiseLevel\": \"u\\'average\\'\", \"RestaurantsAttire\": \"u\\'casual\\'\", \"RestaurantsDelivery\": \"None\", \"RestaurantsGoodForGroups\": \"True\", \"RestaurantsReservations\": \"True\", \"RestaurantsTableService\": \"True\", \"WheelchairAccessible\": \"True\", \"WiFi\": \"u\\'free\\'\"}', 'cuisine': '\"American\"', 'DogsAllowed': True, 'OutdoorSeating': True, 'borough': '\"Brooklyn\"', 'address': '{\"building\": \"69\", \"coord\": [-73.9757464, 40.687295], \"street\": \"Lafayette Avenue\", \"zipcode\": \"11217\"}', '_id': {'$oid': '6095a34a7c34416a90d322d1'}, 'name': '\"Academy Restauraunt\"', 'menu': '[\"Mozzarella sticks\", \"Cheeseburger\", \"Baked potato\", \"Breadsticks\", \"Caesar salad\", \"Chicken parmesan\", \"Pigs in a blanket\", \"Chicken soup\", \"Mac & cheese\", \"Mushroom swiss burger\", \"Spaghetti with meatballs\", \"Mashed potatoes\"]', 'TakeOut': 'true', 'PriceRange': '2.0', 'HappyHour': 'true', 'review_count': '173', 'sponsored': None, 'stars': 4.5}, hash='df7870b3103572b05e98091e4d4b52b238175eb08558831b621b6832c0472c2e'), : RelatedNodeInfo(node_id='5fbb14fe-c8a8-4c4c-930d-2e07e4f77b47', node_type=, metadata={'restaurant_id': '40377111', 'attributes': '{\"Alcohol\": null, \"Ambience\": null, \"BYOB\": null, \"BestNights\": null, \"BikeParking\": \"True\", \"BusinessAcceptsBitcoin\": null, \"BusinessAcceptsCreditCards\": \"False\", \"BusinessParking\": \"{\\'garage\\': False, \\'street\\': True, \\'validated\\': False, \\'lot\\': False, \\'valet\\': False}\", \"Caters\": null, \"DriveThru\": \"True\", \"GoodForDancing\": null, \"GoodForKids\": null, \"GoodForMeal\": null, \"HasTV\": null, \"Music\": null, \"NoiseLevel\": null, \"RestaurantsAttire\": null, \"RestaurantsDelivery\": \"True\", \"RestaurantsGoodForGroups\": null, \"RestaurantsReservations\": null, \"RestaurantsTableService\": null, \"WheelchairAccessible\": null, \"WiFi\": null}', 'cuisine': '\"American\"', 'DogsAllowed': None, 'OutdoorSeating': None, 'borough': '\"Manhattan\"', 'address': '{\"building\": \"1207\", \"coord\": [-73.9592644, 40.8088612], \"street\": \"Amsterdam Avenue\", \"zipcode\": \"10027\"}', '_id': {'$oid': '6095a34a7c34416a90d321d6'}, 'name': '\"Amsterdam Restaurant & Tapas Lounge\"', 'menu': '[\"Green salad\", \"Cheddar Biscuits\", \"Lasagna\", \"Chicken parmesan\", \"Chicken soup\", \"Pigs in a blanket\", \"Caesar salad\", \"French fries\", \"Baked potato\", \"Mushroom swiss burger\", \"Grilled cheese sandwich\", \"Fried chicken\"]', 'TakeOut': 'true', 'PriceRange': '1.0', 'HappyHour': 'null', 'review_count': '6', 'sponsored': None, 'stars': 5.0}, hash='1261332dd67be495d0639f41b5f6462f87a41aabe20367502ef28074bf13e561'), : RelatedNodeInfo(node_id='10ad1a23-3237-4b68-808d-58fd7b7e5cb6', node_type=, metadata={}, hash='bc64dca2f9210693c3d5174aec305f25b68d080be65a0ae52f9a560f99992bb0')}, text='{\"restaurant_id\": \"40385767\", \"attributes\": \"{\\\\\"Alcohol\\\\\": \\\\\"u\\'beer_and_wine\\'\\\\\", \\\\\"Ambience\\\\\": \\\\\"{\\'touristy\\': False, \\'hipster\\': False, \\'romantic\\': False, \\'divey\\': False, \\'intimate\\': None, \\'trendy\\': None, \\'upscale\\': False, \\'classy\\': False, \\'casual\\': True}\\\\\", \\\\\"BYOB\\\\\": null, \\\\\"BestNights\\\\\": \\\\\"{\\'monday\\': False, \\'tuesday\\': False, \\'friday\\': True, \\'wednesday\\': False, \\'thursday\\': False, \\'sunday\\': False, \\'saturday\\': True}\\\\\", \\\\\"BikeParking\\\\\": \\\\\"True\\\\\", \\\\\"BusinessAcceptsBitcoin\\\\\": \\\\\"False\\\\\", \\\\\"BusinessAcceptsCreditCards\\\\\": \\\\\"True\\\\\", \\\\\"BusinessParking\\\\\": \\\\\"{\\'garage\\': False, \\'street\\': False, \\'validated\\': False, \\'lot\\': True, \\'valet\\': False}\\\\\", \\\\\"Caters\\\\\": \\\\\"True\\\\\", \\\\\"DriveThru\\\\\": null, \\\\\"GoodForDancing\\\\\": \\\\\"False\\\\\", \\\\\"GoodForKids\\\\\": \\\\\"True\\\\\", \\\\\"GoodForMeal\\\\\": \\\\\"{\\'dessert\\': False, \\'latenight\\': False, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': False, \\'breakfast\\': False}\\\\\", \\\\\"HasTV\\\\\": \\\\\"True\\\\\", \\\\\"Music\\\\\": \\\\\"{\\'dj\\': False, \\'background_music\\': False, \\'no_music\\': False, \\'jukebox\\': False, \\'live\\': False, \\'video\\': False, \\'karaoke\\': False}\\\\\", \\\\\"NoiseLevel\\\\\": \\\\\"u\\'average\\'\\\\\", \\\\\"RestaurantsAttire\\\\\": \\\\\"u\\'casual\\'\\\\\", \\\\\"RestaurantsDelivery\\\\\": \\\\\"None\\\\\", \\\\\"RestaurantsGoodForGroups\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsReservations\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsTableService\\\\\": \\\\\"True\\\\\", \\\\\"WheelchairAccessible\\\\\": \\\\\"True\\\\\", \\\\\"WiFi\\\\\": \\\\\"u\\'free\\'\\\\\"}\", \"cuisine\": \"\\\\\"American\\\\\"\", \"DogsAllowed\": true, \"OutdoorSeating\": true, \"borough\": \"\\\\\"Brooklyn\\\\\"\",', start_char_idx=0, end_char_idx=1415, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n'), score=0.7296431064605713),\n", + " NodeWithScore(node=TextNode(id_='9cd153ba-2ab8-40aa-90f0-9da5ae24c632', embedding=None, metadata={'restaurant_id': '40392690', 'attributes': '{\"Alcohol\": \"u\\'full_bar\\'\", \"Ambience\": \"{\\'touristy\\': None, \\'hipster\\': True, \\'romantic\\': False, \\'divey\\': False, \\'intimate\\': None, \\'trendy\\': True, \\'upscale\\': None, \\'classy\\': True, \\'casual\\': True}\", \"BYOB\": \"False\", \"BestNights\": \"{\\'monday\\': False, \\'tuesday\\': False, \\'friday\\': True, \\'wednesday\\': False, \\'thursday\\': False, \\'sunday\\': False, \\'saturday\\': False}\", \"BikeParking\": \"True\", \"BusinessAcceptsBitcoin\": null, \"BusinessAcceptsCreditCards\": \"True\", \"BusinessParking\": \"{\\'garage\\': False, \\'street\\': True, \\'validated\\': False, \\'lot\\': False, \\'valet\\': False}\", \"Caters\": \"True\", \"DriveThru\": \"False\", \"GoodForDancing\": \"False\", \"GoodForKids\": \"True\", \"GoodForMeal\": \"{\\'dessert\\': None, \\'latenight\\': None, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': False, \\'breakfast\\': False}\", \"HasTV\": \"False\", \"Music\": \"{\\'dj\\': False, \\'background_music\\': False, \\'no_music\\': False, \\'jukebox\\': False, \\'live\\': False, \\'video\\': False, \\'karaoke\\': False}\", \"NoiseLevel\": \"u\\'average\\'\", \"RestaurantsAttire\": \"\\'casual\\'\", \"RestaurantsDelivery\": \"True\", \"RestaurantsGoodForGroups\": \"True\", \"RestaurantsReservations\": \"False\", \"RestaurantsTableService\": \"True\", \"WheelchairAccessible\": \"True\", \"WiFi\": \"\\'free\\'\"}', 'cuisine': '\"Italian\"', 'DogsAllowed': True, 'OutdoorSeating': True, 'borough': '\"Manhattan\"', 'address': '{\"building\": \"11\", \"coord\": [-73.9828696, 40.7693649], \"street\": \"West 60 Street\", \"zipcode\": \"10023\"}', 'name': '\"Gabriel\\'S Bar & Grill\"', 'menu': '[\"Cheese Ravioli\", \"Neapolitan Pizza\", \"assorted gelato\", \"Vegetarian Baked Ziti\", \"Vegetarian Broccoli Pizza\", \"Lasagna\", \"Buca Trio Platter\", \"Spinach Ravioli\", \"Pasta with ricotta cheese\", \"Spaghetti\", \"Fried calimari\", \"Alfredo Pizza\"]', 'TakeOut': 'true', 'PriceRange': '2.0', 'HappyHour': 'true', 'review_count': '333', 'sponsored': None, 'stars': 4.0}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={: RelatedNodeInfo(node_id='77584933-8286-4277-bc56-bed76adcfd37', node_type=, metadata={'restaurant_id': '40392690', 'attributes': '{\"Alcohol\": \"u\\'full_bar\\'\", \"Ambience\": \"{\\'touristy\\': None, \\'hipster\\': True, \\'romantic\\': False, \\'divey\\': False, \\'intimate\\': None, \\'trendy\\': True, \\'upscale\\': None, \\'classy\\': True, \\'casual\\': True}\", \"BYOB\": \"False\", \"BestNights\": \"{\\'monday\\': False, \\'tuesday\\': False, \\'friday\\': True, \\'wednesday\\': False, \\'thursday\\': False, \\'sunday\\': False, \\'saturday\\': False}\", \"BikeParking\": \"True\", \"BusinessAcceptsBitcoin\": null, \"BusinessAcceptsCreditCards\": \"True\", \"BusinessParking\": \"{\\'garage\\': False, \\'street\\': True, \\'validated\\': False, \\'lot\\': False, \\'valet\\': False}\", \"Caters\": \"True\", \"DriveThru\": \"False\", \"GoodForDancing\": \"False\", \"GoodForKids\": \"True\", \"GoodForMeal\": \"{\\'dessert\\': None, \\'latenight\\': None, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': False, \\'breakfast\\': False}\", \"HasTV\": \"False\", \"Music\": \"{\\'dj\\': False, \\'background_music\\': False, \\'no_music\\': False, \\'jukebox\\': False, \\'live\\': False, \\'video\\': False, \\'karaoke\\': False}\", \"NoiseLevel\": \"u\\'average\\'\", \"RestaurantsAttire\": \"\\'casual\\'\", \"RestaurantsDelivery\": \"True\", \"RestaurantsGoodForGroups\": \"True\", \"RestaurantsReservations\": \"False\", \"RestaurantsTableService\": \"True\", \"WheelchairAccessible\": \"True\", \"WiFi\": \"\\'free\\'\"}', 'cuisine': '\"Italian\"', 'DogsAllowed': True, 'OutdoorSeating': True, 'borough': '\"Manhattan\"', 'address': '{\"building\": \"11\", \"coord\": [-73.9828696, 40.7693649], \"street\": \"West 60 Street\", \"zipcode\": \"10023\"}', '_id': {'$oid': '6095a34b7c34416a90d3243a'}, 'name': '\"Gabriel\\'S Bar & Grill\"', 'menu': '[\"Cheese Ravioli\", \"Neapolitan Pizza\", \"assorted gelato\", \"Vegetarian Baked Ziti\", \"Vegetarian Broccoli Pizza\", \"Lasagna\", \"Buca Trio Platter\", \"Spinach Ravioli\", \"Pasta with ricotta cheese\", \"Spaghetti\", \"Fried calimari\", \"Alfredo Pizza\"]', 'TakeOut': 'true', 'PriceRange': '2.0', 'HappyHour': 'true', 'review_count': '333', 'sponsored': None, 'stars': 4.0}, hash='c4dcc57a697cd2fe3047a280573c0f54bc5236e1d5af2228737af77613c9dbf7'), : RelatedNodeInfo(node_id='6e1ead27-3679-48fb-b160-b47db523a3ce', node_type=, metadata={'restaurant_id': '40392496', 'attributes': '{\"Alcohol\": \"u\\'none\\'\", \"Ambience\": \"{\\'touristy\\': False, \\'hipster\\': False, \\'romantic\\': False, \\'intimate\\': None, \\'trendy\\': False, \\'upscale\\': False, \\'classy\\': False, \\'casual\\': True}\", \"BYOB\": null, \"BestNights\": null, \"BikeParking\": \"True\", \"BusinessAcceptsBitcoin\": null, \"BusinessAcceptsCreditCards\": null, \"BusinessParking\": \"{\\'garage\\': False, \\'street\\': True, \\'validated\\': False, \\'lot\\': False, \\'valet\\': False}\", \"Caters\": \"False\", \"DriveThru\": null, \"GoodForDancing\": null, \"GoodForKids\": \"True\", \"GoodForMeal\": \"{\\'dessert\\': False, \\'latenight\\': False, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': None, \\'breakfast\\': False}\", \"HasTV\": \"True\", \"Music\": null, \"NoiseLevel\": \"u\\'average\\'\", \"RestaurantsAttire\": \"u\\'casual\\'\", \"RestaurantsDelivery\": \"True\", \"RestaurantsGoodForGroups\": \"False\", \"RestaurantsReservations\": \"False\", \"RestaurantsTableService\": \"True\", \"WheelchairAccessible\": null, \"WiFi\": \"\\'free\\'\"}', 'cuisine': '\"English\"', 'DogsAllowed': True, 'OutdoorSeating': True, 'borough': '\"Manhattan\"', 'address': '{\"building\": \"253\", \"coord\": [-74.0034571, 40.736351], \"street\": \"West 11 Street\", \"zipcode\": \"10014\"}', '_id': {'$oid': '6095a34b7c34416a90d32435'}, 'name': '\"Tartine\"', 'menu': 'null', 'TakeOut': 'true', 'PriceRange': '2.0', 'HappyHour': 'true', 'review_count': '436', 'sponsored': None, 'stars': 4.5}, hash='146bffad5c816926ec1008d966caab7c0df675251ccca5de860f8a2160bb7a34'), : RelatedNodeInfo(node_id='6640911b-3d8e-4bad-a016-4c3d91444b0c', node_type=, metadata={}, hash='39984a7534d6755344f0887e0d6a200eaab562a7dc492afe292040c0022282bd')}, text='{\"restaurant_id\": \"40392690\", \"attributes\": \"{\\\\\"Alcohol\\\\\": \\\\\"u\\'full_bar\\'\\\\\", \\\\\"Ambience\\\\\": \\\\\"{\\'touristy\\': None, \\'hipster\\': True, \\'romantic\\': False, \\'divey\\': False, \\'intimate\\': None, \\'trendy\\': True, \\'upscale\\': None, \\'classy\\': True, \\'casual\\': True}\\\\\", \\\\\"BYOB\\\\\": \\\\\"False\\\\\", \\\\\"BestNights\\\\\": \\\\\"{\\'monday\\': False, \\'tuesday\\': False, \\'friday\\': True, \\'wednesday\\': False, \\'thursday\\': False, \\'sunday\\': False, \\'saturday\\': False}\\\\\", \\\\\"BikeParking\\\\\": \\\\\"True\\\\\", \\\\\"BusinessAcceptsBitcoin\\\\\": null, \\\\\"BusinessAcceptsCreditCards\\\\\": \\\\\"True\\\\\", \\\\\"BusinessParking\\\\\": \\\\\"{\\'garage\\': False, \\'street\\': True, \\'validated\\': False, \\'lot\\': False, \\'valet\\': False}\\\\\", \\\\\"Caters\\\\\": \\\\\"True\\\\\", \\\\\"DriveThru\\\\\": \\\\\"False\\\\\", \\\\\"GoodForDancing\\\\\": \\\\\"False\\\\\", \\\\\"GoodForKids\\\\\": \\\\\"True\\\\\", \\\\\"GoodForMeal\\\\\": \\\\\"{\\'dessert\\': None, \\'latenight\\': None, \\'lunch\\': True, \\'dinner\\': True, \\'brunch\\': False, \\'breakfast\\': False}\\\\\", \\\\\"HasTV\\\\\": \\\\\"False\\\\\", \\\\\"Music\\\\\": \\\\\"{\\'dj\\': False, \\'background_music\\': False, \\'no_music\\': False, \\'jukebox\\': False, \\'live\\': False, \\'video\\': False, \\'karaoke\\': False}\\\\\", \\\\\"NoiseLevel\\\\\": \\\\\"u\\'average\\'\\\\\", \\\\\"RestaurantsAttire\\\\\": \\\\\"\\'casual\\'\\\\\", \\\\\"RestaurantsDelivery\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsGoodForGroups\\\\\": \\\\\"True\\\\\", \\\\\"RestaurantsReservations\\\\\": \\\\\"False\\\\\", \\\\\"RestaurantsTableService\\\\\": \\\\\"True\\\\\", \\\\\"WheelchairAccessible\\\\\": \\\\\"True\\\\\", \\\\\"WiFi\\\\\": \\\\\"\\'free\\'\\\\\"}\", \"cuisine\": \"\\\\\"Italian\\\\\"\", \"DogsAllowed\": true, \"OutdoorSeating\": true,', start_char_idx=0, end_char_idx=1382, text_template='Metadata: {metadata_str}\\n-----\\nContent: {content}', metadata_template='{key}=>{value}', metadata_seperator='\\n'), score=0.7284677028656006)]\n" ] } ], @@ -921,9 +611,9 @@ "import pprint\n", "from llama_index.core.response.notebook_utils import display_response\n", "\n", - "query_engine = index.as_query_engine(similarity_top_k=3)\n", + "query_engine = index.as_query_engine()\n", "\n", - "query = \"Recommend a restaurants suitable for the christmas season and justify your selecton\"\n", + "query = \"search query: Anything that doesn't have alcohol in it\"\n", "\n", "response = query_engine.query(query)\n", "display_response(response)\n", diff --git a/docs/getting_started/v0_10_0_migration.md b/docs/getting_started/v0_10_0_migration.md index b25cfb29022e2..27d5bc6390134 100644 --- a/docs/getting_started/v0_10_0_migration.md +++ b/docs/getting_started/v0_10_0_migration.md @@ -2,9 +2,9 @@ With the introduction of LlamaIndex v0.10.0, there were several changes -- integrations have separate `pip installs (See the [full registry](https://pretty-sodium-5e0.notion.site/ce81b247649a44e4b6b35dfb24af28a6?v=53b3c2ced7bb4c9996b81b83c9f01139)) +- integrations have separate `pip install`s (See the [full registry](https://pretty-sodium-5e0.notion.site/ce81b247649a44e4b6b35dfb24af28a6?v=53b3c2ced7bb4c9996b81b83c9f01139)) - many imports changed -- the service context was deprecated +- the `ServiceContext` was deprecated Thankfully, we've tried to make these changes as easy as possible! @@ -72,7 +72,7 @@ from llama_index.core import Settings Settings.llm = llm Settings.embed_model = embed_model -Setting.chunk_size = 512 +Settings.chunk_size = 512 ``` You can see the `ServiceContext` -> `Settings` migration guide for [more details](/module_guides/supporting_modules/service_context_migration.md). diff --git a/docs/index.rst b/docs/index.rst index 7799e24493afb..ea8fe43e671c5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,7 @@ You may choose to **fine-tune** a LLM with your data, but: - Due to the cost to train, it's **hard to update** a LLM with latest information. - **Observability** is lacking. When you ask a LLM a question, it's not obvious how the LLM arrived at its answer. -Instead of fine-tuning, one can a context augmentation pattern called `Retrieval-Augmented Generation (RAG) <./getting_started/concepts.html>`_ to obtain more accurate text generation relevant to your specific data. RAG involves the following high level steps: +Instead of fine-tuning, one can use a context augmentation pattern called `Retrieval-Augmented Generation (RAG) <./getting_started/concepts.html>`_ to obtain more accurate text generation relevant to your specific data. RAG involves the following high level steps: 1. Retrieve information from your data sources first, 2. Add it to your question as context, and @@ -36,7 +36,7 @@ In doing so, RAG overcomes all three weaknesses of the fine-tuning approach: Firstly, LlamaIndex imposes no restriction on how you use LLMs. You can still use LLMs as auto-complete, chatbots, semi-autonomous agents, and more (see Use Cases on the left). It only makes LLMs more relevant to you. -LlamaIndex provides the following tools to help you quickly standup production-ready RAG systems: +LlamaIndex provides the following tools to help you quickly stand up production-ready RAG systems: - **Data connectors** ingest your existing data from their native source and format. These could be APIs, PDFs, SQL, and (much) more. - **Data indexes** structure your data in intermediate representations that are easy and performant for LLMs to consume. @@ -70,7 +70,7 @@ We recommend starting at `how to read these docs <./getting_started/reading.html To download or contribute, find LlamaIndex on: -- Github: https://github.com/jerryjliu/llama_index +- Github: https://github.com/run-llama/llama_index - PyPi: - LlamaIndex: https://pypi.org/project/llama-index/. diff --git a/docs/module_guides/models/llms.md b/docs/module_guides/models/llms.md index 7e999730e07e5..366584f8e0cba 100644 --- a/docs/module_guides/models/llms.md +++ b/docs/module_guides/models/llms.md @@ -91,11 +91,11 @@ If you have ways to improve the setup for existing notebooks, contributions to c | Model Name | Basic Query Engines | Router Query Engine | Sub Question Query Engine | Text2SQL | Pydantic Programs | Data Agents |
Notes
| | ------------------------------------------------------------------------------------------------------------------------ | ------------------- | ------------------- | ------------------------- | -------- | ----------------- | ----------- | --------------------------------------- | -| [gpt-3.5-turbo](https://colab.research.google.com/drive/1oVqUAkn0GCBG5OCs3oMUPlNQDdpDTH_c?usp=sharing) (openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | -| [gpt-3.5-turbo-instruct](https://colab.research.google.com/drive/1DrVdx-VZ3dXwkwUVZQpacJRgX7sOa4ow?usp=sharing) (openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Tool usage in data-agents seems flakey. | -| [gpt-4](https://colab.research.google.com/drive/1RsBoT96esj1uDID-QE8xLrOboyHKp65L?usp=sharing) (openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | -| [claude-2](https://colab.research.google.com/drive/1os4BuDS3KcI8FCcUM_2cJma7oI2PGN7N?usp=sharing) (anthropic) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Prone to hallucinating tool inputs. | -| [claude-instant-1.2](https://colab.research.google.com/drive/1wt3Rt2OWBbqyeRYdiLfmB0_OIUOGit_D?usp=sharing) (anthropic) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Prone to hallucinating tool inputs. | +| [gpt-3.5-turbo](https://colab.research.google.com/drive/1vvdcf7VYNQA67NOxBHCyQvgb2Pu7iY_5?usp=sharing) (openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| [gpt-3.5-turbo-instruct](https://colab.research.google.com/drive/1Ne-VmMNYGOKUeECvkjurdKqMDpfqJQHE?usp=sharing) (openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Tool usage in data-agents seems flakey. | +| [gpt-4](https://colab.research.google.com/drive/1QUNyCVt8q5G32XHNztGw4YJ2EmEkeUe8?usp=sharing) (openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| [claude-2](https://colab.research.google.com/drive/1IuHRN67MYOaLx2_AgJ9gWVtlK7bIvS1f?usp=sharing) (anthropic) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Prone to hallucinating tool inputs. | +| [claude-instant-1.2](https://colab.research.google.com/drive/1ahq-2kXwCVCA_3xyC5UMWHyfAcjoG8Gp?usp=sharing) (anthropic) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Prone to hallucinating tool inputs. | ### Open Source LLMs @@ -103,14 +103,14 @@ Since open source LLMs require large amounts of resources, the quantization is r | Model Name | Basic Query Engines | Router Query Engine | SubQuestion Query Engine | Text2SQL | Pydantic Programs | Data Agents |
Notes
| | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ------------------- | ------------------------ | -------- | ----------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [llama2-chat-7b 4bit](https://colab.research.google.com/drive/14N-hmJ87wZsFqHktrw40OU6sVcsiSzlQ?usp=sharing) (huggingface) | ✅ | 🛑 | 🛑 | 🛑 | 🛑 | ⚠️ | Llama2 seems to be quite chatty, which makes parsing structured outputs difficult. Fine-tuning and prompt engineering likely required for better performance on structured outputs. | -| [llama2-13b-chat](https://colab.research.google.com/drive/1S3eCZ8goKjFktF9hIakzcHqDE72g0Ggb?usp=sharing) (replicate) | ✅ | ✅ | 🛑 | ✅ | 🛑 | 🛑 | Our ReAct prompt expects structured outputs, which llama-13b struggles at | -| [llama2-70b-chat](https://colab.research.google.com/drive/1BeOuVI8StygKFTLSpZ0vGCouxar2V5UW?usp=sharing) (replicate) | ✅ | ✅ | ✅ | ✅ | 🛑 | ⚠️ | There are still some issues with parsing structured outputs, especially with pydantic programs. | -| [Mistral-7B-instruct-v0.1 4bit](https://colab.research.google.com/drive/1ZAdrabTJmZ_etDp10rjij_zME2Q3umAQ?usp=sharing) (huggingface) | ✅ | 🛑 | 🛑 | ⚠️ | ⚠️ | ⚠️ | Mistral seems slightly more reliable for structured outputs compared to Llama2. Likely with some prompt engineering, it may do better. | -| [zephyr-7b-alpha](https://colab.research.google.com/drive/16Ygf2IyGNkb725ZqtRmFQjwWBuzFX_kl?usp=sharing) (huggingface) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Overall, `zyphyr-7b-alpha` is appears to be more reliable than other open-source models of this size. Although it still hallucinates a bit, especially as an agent. | -| [zephyr-7b-beta](https://colab.research.google.com/drive/1UoPcoiA5EOBghxWKWduQhChliMHxla7U?usp=sharing) (huggingface) | ✅ | ✅ | ✅ | ✅ | 🛑 | ✅ | Compared to `zyphyr-7b-alpha`, `zyphyr-7b-beta` appears to perform well as an agent however it fails for Pydantic Programs | -| [stablelm-zephyr-3b](https://colab.research.google.com/drive/1USBIOs4yUkjOcxTKBr7onjlzATE-974T?usp=sharing) (huggingface) | ✅ | ⚠️ | ✅ | 🛑 | ✅ | 🛑 | stablelm-zephyr-3b does surprisingly well, especially for structured outputs (surpassing much larger models). It struggles a bit with text-to-SQL and tool use. | -| [starling-lm-7b-alpha](https://colab.research.google.com/drive/1Juk073EWt2utxHZY84q_NfVT9xFwppf8?usp=sharing) (huggingface) | ✅ | 🛑 | ✅ | ⚠️ | ✅ | ✅ | starling-lm-7b-alpha does surprisingly well on agent tasks. It struggles a bit with routing, and is inconsistent with text-to-SQL. | +| [llama2-chat-7b 4bit](https://colab.research.google.com/drive/1ByiIaBqCwbH9QXJOQWqOfUdsq4LEFq-g?usp=sharing) (huggingface) | ✅ | 🛑 | 🛑 | 🛑 | 🛑 | ⚠️ | Llama2 seems to be quite chatty, which makes parsing structured outputs difficult. Fine-tuning and prompt engineering likely required for better performance on structured outputs. | +| [llama2-13b-chat](https://colab.research.google.com/drive/1dpIv3iYQCV4OBB8z2ZRS7y4wUfsfNlO3?usp=sharing) (replicate) | ✅ | ✅ | 🛑 | ✅ | 🛑 | 🛑 | Our ReAct prompt expects structured outputs, which llama-13b struggles at | +| [llama2-70b-chat](https://colab.research.google.com/drive/11h_Av5RG3tGjuOrZ-VKifd9UzcRPeN1J?usp=sharing) (replicate) | ✅ | ✅ | ✅ | ✅ | 🛑 | ⚠️ | There are still some issues with parsing structured outputs, especially with pydantic programs. | +| [Mistral-7B-instruct-v0.1 4bit](https://colab.research.google.com/drive/1-f5v48TnX5rGdaMdWTr8XsjTGrWZ6Q7Y?usp=sharing) (huggingface) | ✅ | 🛑 | 🛑 | ⚠️ | ⚠️ | ⚠️ | Mistral seems slightly more reliable for structured outputs compared to Llama2. Likely with some prompt engineering, it may do better. | +| [zephyr-7b-alpha](https://colab.research.google.com/drive/1asitB49g9LMGrlODgY2J-g_xRExRM_ud?usp=sharing) (huggingface) | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | Overall, `zyphyr-7b-alpha` is appears to be more reliable than other open-source models of this size. Although it still hallucinates a bit, especially as an agent. | +| [zephyr-7b-beta](https://colab.research.google.com/drive/1C55IGyJNDe14DsHkAIIpIjn76NvK5pc1?usp=sharing) (huggingface) | ✅ | ✅ | ✅ | ✅ | 🛑 | ✅ | Compared to `zyphyr-7b-alpha`, `zyphyr-7b-beta` appears to perform well as an agent however it fails for Pydantic Programs | +| [stablelm-zephyr-3b](https://colab.research.google.com/drive/1X_hEUkV62wHmMty3tNLIfJtp4IC6QNYN?usp=sharing) (huggingface) | ✅ | ⚠️ | ✅ | 🛑 | ✅ | 🛑 | stablelm-zephyr-3b does surprisingly well, especially for structured outputs (surpassing much larger models). It struggles a bit with text-to-SQL and tool use. | +| [starling-lm-7b-alpha](https://colab.research.google.com/drive/1z2tZMr4M9wBFU6YX8fvAZ7WLTa3tWKEm?usp=sharing) (huggingface) | ✅ | 🛑 | ✅ | ⚠️ | ✅ | ✅ | starling-lm-7b-alpha does surprisingly well on agent tasks. It struggles a bit with routing, and is inconsistent with text-to-SQL. | ## Modules diff --git a/docs/module_guides/models/multi_modal.md b/docs/module_guides/models/multi_modal.md index 16b09e3bb4df8..5e92d596423f3 100644 --- a/docs/module_guides/models/multi_modal.md +++ b/docs/module_guides/models/multi_modal.md @@ -148,7 +148,7 @@ Below table lists some vector stores supporting Multi-Modal use cases. Our Llama ## Multi-Modal LLM Modules -We support integrations with GPT4-V, CLIP (OpenAI), BLIP (Salesforce), and Replicate (LLaVA, Fuyu-8B, MiniGPT-4, CogVLM), and more. +We support integrations with GPT4-V, Anthropic (Opus, Sonnet), Gemini (Google), CLIP (OpenAI), BLIP (Salesforce), and Replicate (LLaVA, Fuyu-8B, MiniGPT-4, CogVLM), and more. ```{toctree} --- @@ -160,6 +160,7 @@ maxdepth: 1 /examples/multi_modal/multi_modal_pydantic.ipynb /examples/multi_modal/gpt4v_experiments_cot.ipynb /examples/multi_modal/llava_multi_modal_tesla_10q.ipynb +/examples/multi_modal/anthropic_multi_modal.ipynb ``` ## Multi-Modal Retrieval Augmented Generation diff --git a/docs/module_guides/models/prompts.md b/docs/module_guides/models/prompts.md index 1f9d4468e8081..b5810704c1175 100644 --- a/docs/module_guides/models/prompts.md +++ b/docs/module_guides/models/prompts.md @@ -5,9 +5,9 @@ Prompting is the fundamental input that gives LLMs their expressive power. LlamaIndex uses prompts to build the index, do insertion, perform traversal during querying, and to synthesize the final answer. -LlamaIndex uses a set of [default prompt templates](https://github.com/jerryjliu/llama_index/blob/main/llama_index/prompts/default_prompts.py) that work well out of the box. +LlamaIndex uses a set of [default prompt templates](https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/prompts/default_prompts.py) that work well out of the box. -In addition, there are some prompts written and used specifically for chat models like `gpt-3.5-turbo` [here](https://github.com/jerryjliu/llama_index/blob/main/llama_index/prompts/chat_prompts.py). +In addition, there are some prompts written and used specifically for chat models like `gpt-3.5-turbo` [here](https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/prompts/chat_prompts.py). Users may also provide their own prompt templates to further customize the behavior of the framework. The best method for customizing is copying the default prompt from the link above, and using that as the base for any modifications. diff --git a/docs/module_guides/observability/callbacks/root.md b/docs/module_guides/observability/callbacks/root.md index b71a037ae14a4..11bd0093c93e4 100644 --- a/docs/module_guides/observability/callbacks/root.md +++ b/docs/module_guides/observability/callbacks/root.md @@ -31,6 +31,7 @@ You can implement your own callback to track and trace these events, or use an e Currently supported callbacks are as follows: +- [LangfuseCallbackHandler](/examples/callbacks/LangfuseCallbackHandler.ipynb) -> Tracking of events and traces using the open-source platform Langfuse. More details are in the linked notebook or in the [Langfuse docs](https://langfuse.com/docs) - [TokenCountingHandler](/examples/callbacks/TokenCountingHandler.ipynb) -> Flexible token counting for prompt, completion, and embedding token usage. See [the migration details](/module_guides/observability/callbacks/token_counting_migration.md) - [LlamaDebugHanlder](/examples/callbacks/LlamaDebugHandler.ipynb) -> Basic tracking and tracing for events. Example usage can be found in the notebook below. - [WandbCallbackHandler](/examples/callbacks/WandbCallbackHandler.ipynb) -> Tracking of events and traces using the Wandb Prompts frontend. More details are in the notebook below or at [Wandb](https://docs.wandb.ai/guides/prompts/quickstart) @@ -43,6 +44,7 @@ Currently supported callbacks are as follows: maxdepth: 1 hidden: --- +/examples/callbacks/LangfuseCallbackHandler.ipynb /examples/callbacks/TokenCountingHandler.ipynb /examples/callbacks/LlamaDebugHandler.ipynb /examples/callbacks/WandbCallbackHandler.ipynb diff --git a/docs/module_guides/observability/observability.md b/docs/module_guides/observability/observability.md index 48ac5f98bd897..6a419dee79e18 100644 --- a/docs/module_guides/observability/observability.md +++ b/docs/module_guides/observability/observability.md @@ -50,6 +50,34 @@ llama_index.core.set_global_handler("simple") We offer a rich set of integrations with our partners. A short description + usage pattern, and guide is provided for each partner. +### Langfuse + +[Langfuse](https://langfuse.com/docs) is an open source LLM engineering platform to help teams collaboratively debug, analyze and iterate on their LLM Applications. With the Langfuse integration, you can seamlessly track and monitor performance, traces, and metrics of your LlamaIndex application. Detailed traces of the LlamaIndex context augmentation and the LLM querying processes are captured and can be inspected directly in the Langfuse UI. + +#### Usage Pattern + +```python +from llama_index.core import set_global_handler + +# Make sure you've installed the 'llama-index-callbacks-langfuse' integration package. + +# NOTE: Set your environment variables 'LANGFUSE_SECRET_KEY', 'LANGFUSE_PUBLIC_KEY' and 'LANGFUSE_HOST' +# as shown in your langfuse.com project settings. + +set_global_handler("langfuse") +``` + +#### Guides + +```{toctree} +--- +maxdepth: 1 +--- +/examples/callbacks/LangfuseCallbackHandler.ipynb +``` + +![langfuse-tracing](https://static.langfuse.com/llamaindex-langfuse-docs.gif) + ### DeepEval [DeepEval (by Confident AI)](https://github.com/confident-ai/deepeval) is an open-source evaluation framework for LLM applications. As you "unit test" your LLM app using DeepEval's 14+ default metrics it currently offers (summarization, hallucination, answer relevancy, faithfulness, RAGAS, etc.), you can debug failing test cases through this tracing integration with LlamaIndex, or debug unsatisfactory evaluations in **production** through DeepEval's hosted evaluation platform, [Confident AI](https://confident-ai.com), that runs referenceless evaluations in production. diff --git a/docs/module_guides/querying/node_postprocessors/node_postprocessors.md b/docs/module_guides/querying/node_postprocessors/node_postprocessors.md index 86d78d23799f4..255513a03e77b 100644 --- a/docs/module_guides/querying/node_postprocessors/node_postprocessors.md +++ b/docs/module_guides/querying/node_postprocessors/node_postprocessors.md @@ -127,6 +127,22 @@ postprocessor.postprocess_nodes(nodes) Full notebook guide is available [her for Gatsby](/examples/node_postprocessor/LLMReranker-Gatsby.ipynb) and [here for Lyft 10K documents](/examples/node_postprocessor/LLMReranker-Lyft-10k.ipynb). +## JinaRerank + +Uses the "Jina ReRank" functionality to re-order nodes, and returns the top N nodes. + +```python +from llama_index.postprocessor.jinaai_rerank import JinaRerank + +postprocessor = JinaRerank( + top_n=2, model="jina-reranker-v1-base-en", api_key="YOUR JINA API KEY" +) + +postprocessor.postprocess_nodes(nodes) +``` + +Full notebook guide is available [here](/examples/node_postprocessor/JinaRerank.ipynb). + ## FixedRecencyPostprocessor This postproccesor returns the top K nodes sorted by date. This assumes there is a `date` field to parse in the metadata of each node. @@ -254,7 +270,48 @@ postprocessor = RankGPTRerank(top_n=3, llm=OpenAI(model="gpt-3.5-turbo-16k")) postprocessor.postprocess_nodes(nodes) ``` -Full notebook guide is available [her for Van Gogh](/examples/node_postprocessor/rankGPT.ipynb). +Full notebook guide is available [here](/examples/node_postprocessor/rankGPT.ipynb). + +## Colbert Reranker + +Uses Colbert V2 model as a reranker to rerank documents according to the fine-grained similarity between query tokens and passage tokens. Returns the top N ranked nodes. + +```python +from llama_index.postprocessor.colbert_rerank import ColbertRerank + +colbert_reranker = ColbertRerank( + top_n=5, + model="colbert-ir/colbertv2.0", + tokenizer="colbert-ir/colbertv2.0", + keep_retrieval_score=True, +) + +query_engine = index.as_query_engine( + similarity_top_k=10, + node_postprocessors=[colbert_reranker], +) +response = query_engine.query( + query_str, +) +``` + +Full notebook guide is available [here](/examples/node_postprocessor/ColbertRerank.ipynb). + +## Jina Reranker + +Uses models from [jina](https://jina.ai/) to rerank documents. Returns the top N ranked nodes. + +```python +from llama_index.postprocessor.jinaai_rerank import JinaRerank + +jina_rerank = JinaRerank(api_key=api_key, top_n=2) + +query_engine = index.as_query_engine( + similarity_top_k=10, node_postprocessors=[jina_rerank] +) +``` + +Full notebook guide is available [here](/examples/node_postprocessor/JinaRerank.ipynb). ## All Notebooks @@ -273,4 +330,7 @@ maxdepth: 1 /examples/node_postprocessor/MetadataReplacementDemo.ipynb /examples/node_postprocessor/LongContextReorder.ipynb /examples/node_postprocessor/rankGPT.ipynb +/examples/node_postprocessor/ColbertRerank.ipynb +/examples/node_postprocessor/JinaRerank.ipynb +/cookbooks/mixedbread_reranker.ipynb ``` diff --git a/docs/module_guides/querying/retriever/retrievers.md b/docs/module_guides/querying/retriever/retrievers.md index ef152dfb9c014..2c8216582123a 100644 --- a/docs/module_guides/querying/retriever/retrievers.md +++ b/docs/module_guides/querying/retriever/retrievers.md @@ -84,6 +84,7 @@ maxdepth: 1 /examples/managed/GoogleDemo.ipynb /examples/managed/vectaraDemo.ipynb /examples/managed/zcpDemo.ipynb +VideoDB Retriever ``` ### Other Retrievers diff --git a/docs/module_guides/storing/vector_stores.md b/docs/module_guides/storing/vector_stores.md index c00873909697f..70e8f8e24b969 100644 --- a/docs/module_guides/storing/vector_stores.md +++ b/docs/module_guides/storing/vector_stores.md @@ -13,44 +13,45 @@ They can be persisted to (and loaded from) disk by calling `vector_store.persist LlamaIndex supports over 20 different vector store options. We are actively adding more integrations and improving feature coverage for each. -| Vector Store | Type | Metadata Filtering | Hybrid Search | Delete | Store Documents | Async | -| ------------------------ | ------------------- | ------------------ | ------------- | ------ | --------------- | ----- | -| Apache Cassandra® | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| Astra DB | cloud | ✓ | | ✓ | ✓ | | -| Azure Cognitive Search | cloud | | ✓ | ✓ | ✓ | | -| Azure CosmosDB MongoDB | cloud | | | ✓ | ✓ | | -| ChatGPT Retrieval Plugin | aggregator | | | ✓ | ✓ | | -| Chroma | self-hosted | ✓ | | ✓ | ✓ | | -| DashVector | cloud | ✓ | ✓ | ✓ | ✓ | | -| Deeplake | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| DocArray | aggregator | ✓ | | ✓ | ✓ | | -| DynamoDB | cloud | | | ✓ | | | -| Elasticsearch | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | -| FAISS | in-memory | | | | | | -| txtai | in-memory | | | | | | -| Jaguar | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | | -| LanceDB | cloud | ✓ | | ✓ | ✓ | | -| Lantern | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | -| Metal | cloud | ✓ | | ✓ | ✓ | | -| MongoDB Atlas | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| MyScale | cloud | ✓ | ✓ | ✓ | ✓ | | -| Milvus / Zilliz | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| Neo4jVector | self-hosted / cloud | | | ✓ | ✓ | | -| OpenSearch | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | -| Pinecone | cloud | ✓ | ✓ | ✓ | ✓ | | -| Postgres | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | -| pgvecto.rs | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | | -| Qdrant | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | -| Redis | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| Simple | in-memory | ✓ | | ✓ | | | -| SingleStore | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| Supabase | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| Tair | cloud | ✓ | | ✓ | ✓ | | -| TencentVectorDB | cloud | ✓ | ✓ | ✓ | ✓ | | -| Timescale | | ✓ | | ✓ | ✓ | ✓ | -| Typesense | self-hosted / cloud | ✓ | | ✓ | ✓ | | -| Upstash | cloud | | | | ✓ | | -| Weaviate | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | | +| Vector Store | Type | Metadata Filtering | Hybrid Search | Delete | Store Documents | Async | +| ------------------------ | ----------------------- | ------------------ | ------------- | ------ | --------------- | ----- | +| Apache Cassandra® | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| Astra DB | cloud | ✓ | | ✓ | ✓ | | +| Azure Cognitive Search | cloud | | ✓ | ✓ | ✓ | | +| Azure CosmosDB MongoDB | cloud | | | ✓ | ✓ | | +| ChatGPT Retrieval Plugin | aggregator | | | ✓ | ✓ | | +| Chroma | self-hosted | ✓ | | ✓ | ✓ | | +| DashVector | cloud | ✓ | ✓ | ✓ | ✓ | | +| Deeplake | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| DocArray | aggregator | ✓ | | ✓ | ✓ | | +| DuckDB | in-memory / self-hosted | ✓ | | ✓ | ✓ | | +| DynamoDB | cloud | | | ✓ | | | +| Elasticsearch | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | +| FAISS | in-memory | | | | | | +| txtai | in-memory | | | | | | +| Jaguar | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | | +| LanceDB | cloud | ✓ | | ✓ | ✓ | | +| Lantern | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | +| Metal | cloud | ✓ | | ✓ | ✓ | | +| MongoDB Atlas | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| MyScale | cloud | ✓ | ✓ | ✓ | ✓ | | +| Milvus / Zilliz | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| Neo4jVector | self-hosted / cloud | | | ✓ | ✓ | | +| OpenSearch | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | +| Pinecone | cloud | ✓ | ✓ | ✓ | ✓ | | +| Postgres | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | +| pgvecto.rs | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | | +| Qdrant | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | ✓ | +| Redis | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| Simple | in-memory | ✓ | | ✓ | | | +| SingleStore | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| Supabase | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| Tair | cloud | ✓ | | ✓ | ✓ | | +| TencentVectorDB | cloud | ✓ | ✓ | ✓ | ✓ | | +| Timescale | | ✓ | | ✓ | ✓ | ✓ | +| Typesense | self-hosted / cloud | ✓ | | ✓ | ✓ | | +| Upstash | cloud | | | | ✓ | | +| Weaviate | self-hosted / cloud | ✓ | ✓ | ✓ | ✓ | | For more details, see [Vector Store Integrations](/community/integrations/vector_stores.md). @@ -70,6 +71,7 @@ maxdepth: 1 /examples/vector_stores/DeepLakeIndexDemo.ipynb /examples/vector_stores/DocArrayHnswIndexDemo.ipynb /examples/vector_stores/DocArrayInMemoryIndexDemo.ipynb +/examples/vector_stores/DuckDBDemo.ipynb /examples/vector_stores/EpsillaIndexDemo.ipynb /examples/vector_stores/JaguarIndexDemo.ipynb /examples/vector_stores/LanceDBIndexDemo.ipynb diff --git a/docs/presentations/materials/2024-02-28-rag-bootcamp-vector-institute.ipynb b/docs/presentations/materials/2024-02-28-rag-bootcamp-vector-institute.ipynb index 4524113a39298..073e5a818b24e 100644 --- a/docs/presentations/materials/2024-02-28-rag-bootcamp-vector-institute.ipynb +++ b/docs/presentations/materials/2024-02-28-rag-bootcamp-vector-institute.ipynb @@ -121,7 +121,7 @@ "\n", "3. Declaration of Research Assessment: In academia, this could refer to a statement or policy regarding how research is evaluated.\n", "\n", - "4. Digital on-Ramp's Assessment: In the field of digital technology, this could refer to an assessment tool used by the Digital On-Ramps program.\n", + "4. Digital On-Ramp's Assessment: In the field of digital technology, this could refer to an assessment tool used by the Digital On-Ramps program.\n", "\n", "Please provide more context for a more accurate definition.\n" ] @@ -371,7 +371,7 @@ "source": [ "## In Summary\n", "\n", - "- LLMs as powerful as they are, don't perform too well with knowledge-intensive tasks (domain specific, updated data, long-tail)\n", + "- LLMs as powerful as they are, don't perform too well with knowledge-intensive tasks (domain-specific, updated data, long-tail)\n", "- Context augmentation has been shown (in a few studies) to outperform LLMs without augmentation\n", "- In this notebook, we showed one such example that follows that pattern." ] diff --git a/docs/use_cases/chatbots.md b/docs/use_cases/chatbots.md index f2b37b6320b31..727884c2b1bb4 100644 --- a/docs/use_cases/chatbots.md +++ b/docs/use_cases/chatbots.md @@ -10,7 +10,7 @@ Here are some relevant resources: - [create-llama](https://blog.llamaindex.ai/create-llama-a-command-line-tool-to-generate-llamaindex-apps-8f7683021191), a command line tool that generates a full-stack chatbot application for you - [SECinsights.ai](https://www.secinsights.ai/), an open-source application that uses LlamaIndex to build a chatbot that answers questions about SEC filings - [RAGs](https://blog.llamaindex.ai/introducing-rags-your-personalized-chatgpt-experience-over-your-data-2b9d140769b1), a project inspired by OpenAI's GPTs that lets you build a low-code chatbot over your data using Streamlit -- Our [OpenAI agents](/module_guides/deploying/agents/modules.md) are all chat bots in nature +- Our [OpenAI agents](/module_guides/deploying/agents/modules.md) are all chatbots in nature ## External sources diff --git a/docs/use_cases/multimodal.md b/docs/use_cases/multimodal.md index 5aa7fba00a400..42c5e837439b6 100644 --- a/docs/use_cases/multimodal.md +++ b/docs/use_cases/multimodal.md @@ -1,10 +1,10 @@ # Multi-modal -LlamaIndex offers capabilities to not only build language-based applications, but also **multi-modal** applications - combining language and images. +LlamaIndex offers capabilities to not only build language-based applications but also **multi-modal** applications - combining language and images. ## Types of Multi-modal Use Cases -This space is actively being explored right now, but there are some fascinating use cases popping up. +This space is actively being explored right now, but some fascinating use cases are popping up. ### RAG (Retrieval Augmented Generation) @@ -73,7 +73,7 @@ maxdepth: 1 These sections show comparisons between different multi-modal models for different use cases. -### LLaVa-13, Fuyu-8B and MiniGPT-4 Multi-Modal LLM Models Comparison for Image Reasoning +### LLaVa-13, Fuyu-8B, and MiniGPT-4 Multi-Modal LLM Models Comparison for Image Reasoning These notebooks show how to use different Multi-Modal LLM models for image understanding/reasoning. The various model inferences are supported by Replicate or OpenAI GPT4-V API. We compared several popular Multi-Modal LLMs: @@ -97,7 +97,7 @@ GPT4-V: ### Simple Evaluation of Multi-Modal RAG -In this notebook guide, we'll demonstrate how to evaluate a Multi-Modal RAG system. As in the text-only case, we will consider the evaluation of Retrievers and Generators separately. As we alluded in our blog on the topic of Evaluating Multi-Modal RAGs, our approach here involves the application of adapted versions of the usual techniques for evaluating both Retriever and Generator (used for the text-only case). These adapted versions are part of the llama-index library (i.e., evaluation module), and this notebook will walk you through how you can apply them to your evaluation use-cases. +In this notebook guide, we'll demonstrate how to evaluate a Multi-Modal RAG system. As in the text-only case, we will consider the evaluation of Retrievers and Generators separately. As we alluded to in our blog on the topic of Evaluating Multi-Modal RAGs, our approach here involves the application of adapted versions of the usual techniques for evaluating both Retriever and Generator (used for the text-only case). These adapted versions are part of the llama-index library (i.e., evaluation module), and this notebook will walk you through how you can apply them to your evaluation use cases. ```{toctree} --- diff --git a/docs/use_cases/q_and_a/rag_cli.md b/docs/use_cases/q_and_a/rag_cli.md index 040eb93166ef6..06da5db3aaa3f 100644 --- a/docs/use_cases/q_and_a/rag_cli.md +++ b/docs/use_cases/q_and_a/rag_cli.md @@ -112,6 +112,8 @@ import os from llama_index.core.ingestion import IngestionPipeline, IngestionCache from llama_index.core.query_pipeline import QueryPipeline from llama_index.core.storage.docstore import SimpleDocumentStore +from llama_index.cli.rag import RagCLI + # optional, set any API keys your script may need (perhaps using python-dotenv library instead) os.environ["OPENAI_API_KEY"] = "sk-xxx" diff --git a/llama-datasets/10k/uber_2021/BUILD b/llama-datasets/10k/uber_2021/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/10k/uber_2021/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/10k/uber_2021/README.md b/llama-datasets/10k/uber_2021/README.md new file mode 100644 index 0000000000000..8edf323890a03 --- /dev/null +++ b/llama-datasets/10k/uber_2021/README.md @@ -0,0 +1,61 @@ +# Uber 10K Dataset 2021 + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset Uber10KDataset2021 --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset("Uber10KDataset2021", "./data") + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, + query_engine=query_engine, + show_progress=True, +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/10k/uber_2021/card.json b/llama-datasets/10k/uber_2021/card.json new file mode 100644 index 0000000000000..a6fb854c1eafc --- /dev/null +++ b/llama-datasets/10k/uber_2021/card.json @@ -0,0 +1,27 @@ +{ + "name": "Uber 10K Dataset 2021", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset based on the Uber 2021 10K document, consisting of queries, reference answers, and reference contexts.", + "numberObservations": 822, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": [], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.943, + "correctness": 3.874, + "faithfulness": 0.667, + "relevancy": 0.844 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/10k/uber_2021/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/10k/uber_2021/llamaindex_baseline.py b/llama-datasets/10k/uber_2021/llamaindex_baseline.py new file mode 100644 index 0000000000000..8af8eb51b536a --- /dev/null +++ b/llama-datasets/10k/uber_2021/llamaindex_baseline.py @@ -0,0 +1,41 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex +from llama_index.llms import OpenAI + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "Uber10KDataset2021", "./uber10k_2021_dataset" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + judge_llm = OpenAI(model="gpt-3.5-turbo") + rag_evaluator = RagEvaluatorPack( + query_engine=query_engine, rag_dataset=rag_dataset, judge_llm=judge_llm + ) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/__init__.py b/llama-datasets/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-datasets/blockchain_solana/BUILD b/llama-datasets/blockchain_solana/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/blockchain_solana/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/blockchain_solana/README.md b/llama-datasets/blockchain_solana/README.md new file mode 100644 index 0000000000000..cebaed8787417 --- /dev/null +++ b/llama-datasets/blockchain_solana/README.md @@ -0,0 +1,61 @@ +# Blockchain Solana Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset BlockchainSolanaDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "BlockchainSolanaDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/blockchain_solana/card.json b/llama-datasets/blockchain_solana/card.json new file mode 100644 index 0000000000000..d2dcba74d5de5 --- /dev/null +++ b/llama-datasets/blockchain_solana/card.json @@ -0,0 +1,27 @@ +{ + "name": "Blockchain Solana", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset based off an article, From Bitcoin to Solana – Innovating Blockchain towards Enterprise Applications),by Xiangyu Li, Xinyu Wang, Tingli Kong, Junhao Zheng and Min Luo, consisting of queries, reference answers, and reference contexts.", + "numberObservations": 58, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": ["https://arxiv.org/abs/2207.05240"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.945, + "correctness": 4.457, + "faithfulness": 1.0, + "relevancy": 1.0 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/blockchain_solana/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/blockchain_solana/llamaindex_baseline.py b/llama-datasets/blockchain_solana/llamaindex_baseline.py new file mode 100644 index 0000000000000..0d9979c490dde --- /dev/null +++ b/llama-datasets/blockchain_solana/llamaindex_baseline.py @@ -0,0 +1,37 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "BlockchainSolanaDataset", "./blockchain_solana" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/braintrust_coda/BUILD b/llama-datasets/braintrust_coda/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/braintrust_coda/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/braintrust_coda/README.md b/llama-datasets/braintrust_coda/README.md new file mode 100644 index 0000000000000..96bedb61f87f8 --- /dev/null +++ b/llama-datasets/braintrust_coda/README.md @@ -0,0 +1,65 @@ +# Braintrust Coda Help Desk Dataset + +[![Braintrust (346 x 40 px)](https://github.com/nerdai/llama-hub/assets/92402603/a99bddf3-0eab-42e8-8c53-8432da8299d3)](https://www.braintrustdata.com/) + +_This dataset was kindly provided by Kenny Wong and Ankur Goyal._ + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset BraintrustCodaHelpDeskDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "BraintrustCodaHelpDeskDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/braintrust_coda/__init__.py b/llama-datasets/braintrust_coda/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-datasets/braintrust_coda/card.json b/llama-datasets/braintrust_coda/card.json new file mode 100644 index 0000000000000..6da7d2f45d7ce --- /dev/null +++ b/llama-datasets/braintrust_coda/card.json @@ -0,0 +1,29 @@ +{ + "name": "Braintrust Coda Help Desk", + "className": "LabelledRagDataset", + "description": "A list of automatically generated question/answer pairs from the Coda (https://coda.io/) help docs. This dataset is interesting because most models include Coda’s documentation as part of their training set, so you can baseline performance without RAG.", + "numberObservations": 100, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": [ + "https://gist.githubusercontent.com/wong-codaio/b8ea0e087f800971ca5ec9eef617273e/raw/39f8bd2ebdecee485021e20f2c1d40fd649a4c77/articles.json" + ], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.955, + "correctness": 4.32, + "faithfulness": 0.9, + "relevancy": 0.93 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/braintrust_coda/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/braintrust_coda/llamaindex_baseline.py b/llama-datasets/braintrust_coda/llamaindex_baseline.py new file mode 100644 index 0000000000000..13fd55153f03c --- /dev/null +++ b/llama-datasets/braintrust_coda/llamaindex_baseline.py @@ -0,0 +1,37 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "BraintrustCodaHelpDeskDataset", "./braintrust_codahdd" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/covidqa/BUILD b/llama-datasets/covidqa/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/covidqa/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/covidqa/README.md b/llama-datasets/covidqa/README.md new file mode 100644 index 0000000000000..1a725277534dc --- /dev/null +++ b/llama-datasets/covidqa/README.md @@ -0,0 +1,59 @@ +# Covid Qa Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset CovidQaDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset("CovidQaDataset", "./data") + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine, show_progress=True +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=40, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/covidqa/card.json b/llama-datasets/covidqa/card.json new file mode 100644 index 0000000000000..6e362e4a70f6f --- /dev/null +++ b/llama-datasets/covidqa/card.json @@ -0,0 +1,29 @@ +{ + "name": "Covid QA Dataset", + "className": "LabelledRagDataset", + "description": "A human-annotated RAG dataset consisting of over 300 question-answer pairs. This dataset represents a subset of the Covid-QA dataset available on Kaggle and authored by Xhlulu. It is a collection of frequently asked questions on COVID from various websites. This subset only considers the top 10 webpages containing the most question-answer pairs.", + "numberObservations": 316, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": [ + "https://www.kaggle.com/datasets/xhlulu/covidqa/?select=news.csv" + ], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": null, + "correctness": 3.96, + "faithfulness": 0.889, + "relevancy": 0.848 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/covidqa/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/covidqa/llamaindex_baseline.py b/llama-datasets/covidqa/llamaindex_baseline.py new file mode 100644 index 0000000000000..152490e025f41 --- /dev/null +++ b/llama-datasets/covidqa/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("CovidQaDataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=40, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/docugami_kg_rag/sec_10_q/BUILD b/llama-datasets/docugami_kg_rag/sec_10_q/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/docugami_kg_rag/sec_10_q/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/docugami_kg_rag/sec_10_q/README.md b/llama-datasets/docugami_kg_rag/sec_10_q/README.md new file mode 100644 index 0000000000000..fcd97d2c03d84 --- /dev/null +++ b/llama-datasets/docugami_kg_rag/sec_10_q/README.md @@ -0,0 +1,63 @@ +# Docugami KG-RAG - Sec 10-Q + +A labelled RAG dataset with SEC 10-Q documents for major tech companies including queries across multiple docs and chunks, with reference answers. See [https://github.com/docugami/KG-RAG-datasets](https://github.com/docugami/KG-RAG-datasets) for details. + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset DocugamiKgRagSec10Q --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "DocugamiKgRagSec10Q", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/docugami_kg_rag/sec_10_q/card.json b/llama-datasets/docugami_kg_rag/sec_10_q/card.json new file mode 100644 index 0000000000000..cfc73290dfc5d --- /dev/null +++ b/llama-datasets/docugami_kg_rag/sec_10_q/card.json @@ -0,0 +1,27 @@ +{ + "name": "Docugami KG-RAG - SEC 10-Q", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset with SEC 10-Q documents for major tech companies including queries across multiple docs and chunks, with reference answers. See https://github.com/docugami/KG-RAG-datasets for details.", + "numberObservations": 195, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": [], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": null, + "correctness": 2.703, + "faithfulness": 0.897, + "relevancy": 0.826 + }, + "codeUrl": "" + } + ] +} diff --git a/llama-datasets/docugami_kg_rag/sec_10_q/llamaindex_baseline.py b/llama-datasets/docugami_kg_rag/sec_10_q/llamaindex_baseline.py new file mode 100644 index 0000000000000..0945521bd17ab --- /dev/null +++ b/llama-datasets/docugami_kg_rag/sec_10_q/llamaindex_baseline.py @@ -0,0 +1,41 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex +from llama_index.llms import OpenAI + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "DocugamiKgRagSec10Q", "./docugami_kg_rag_sec_10_q" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + judge_llm = OpenAI(model="gpt-3.5-turbo") + rag_evaluator = RagEvaluatorPack( + query_engine=query_engine, rag_dataset=rag_dataset, judge_llm=judge_llm + ) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/eval_llm_survey_paper/BUILD b/llama-datasets/eval_llm_survey_paper/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/eval_llm_survey_paper/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/eval_llm_survey_paper/README.md b/llama-datasets/eval_llm_survey_paper/README.md new file mode 100644 index 0000000000000..5252e714a4a4d --- /dev/null +++ b/llama-datasets/eval_llm_survey_paper/README.md @@ -0,0 +1,61 @@ +# Evaluating Llm Survey Paper Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset EvaluatingLlmSurveyPaperDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "EvaluatingLlmSurveyPaperDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/eval_llm_survey_paper/card.json b/llama-datasets/eval_llm_survey_paper/card.json new file mode 100644 index 0000000000000..54c5e4f25556b --- /dev/null +++ b/llama-datasets/eval_llm_survey_paper/card.json @@ -0,0 +1,27 @@ +{ + "name": "Evaluating LLM Survey Paper Dataset", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset over the comprehensive, spanning 111 pages in total, survey on evaluating LLMs.", + "numberObservations": 276, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": ["https://arxiv.org/pdf/2310.19736.pdf"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.923, + "correctness": 3.81, + "faithfulness": 0.888, + "relevancy": 0.808 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_squadv2/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/eval_llm_survey_paper/llamaindex_baseline.py b/llama-datasets/eval_llm_survey_paper/llamaindex_baseline.py new file mode 100644 index 0000000000000..001accf42813c --- /dev/null +++ b/llama-datasets/eval_llm_survey_paper/llamaindex_baseline.py @@ -0,0 +1,34 @@ +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "EvaluatingLlmSurveyPaperDataset", "./data" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + main() diff --git a/llama-datasets/history_of_alexnet/BUILD b/llama-datasets/history_of_alexnet/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/history_of_alexnet/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/history_of_alexnet/README.md b/llama-datasets/history_of_alexnet/README.md new file mode 100644 index 0000000000000..1cf0f1e64caa0 --- /dev/null +++ b/llama-datasets/history_of_alexnet/README.md @@ -0,0 +1,61 @@ +# History Of Alexnet Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset HistoryOfAlexnetDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "HistoryOfAlexnetDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/history_of_alexnet/card.json b/llama-datasets/history_of_alexnet/card.json new file mode 100644 index 0000000000000..632136c4aec75 --- /dev/null +++ b/llama-datasets/history_of_alexnet/card.json @@ -0,0 +1,27 @@ +{ + "name": "History of Alexnet Dataset", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset based off an article, The History Began from AlexNet: A Comprehensive Survey on Deep Learning Approaches, by Md Zahangir Alom, Tarek M. Taha, Christopher Yakopcic, Stefan Westberg, Paheding Sidike, Mst Shamima Nasrin, Brian C Van Esesn, Abdul A S. Awwal, Vijayan K. Asari, consisting of queries, reference answers, and reference contexts.", + "numberObservations": 160, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": ["https://arxiv.org/abs/1803.01164"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.931, + "correctness": 4.434, + "faithfulness": 0.963, + "relevancy": 0.931 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/history_of_alexnet/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/history_of_alexnet/llamaindex_baseline.py b/llama-datasets/history_of_alexnet/llamaindex_baseline.py new file mode 100644 index 0000000000000..dcf09d21a2db2 --- /dev/null +++ b/llama-datasets/history_of_alexnet/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("HistoryOfAlexnetDataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/library.json b/llama-datasets/library.json new file mode 100644 index 0000000000000..c30a293ccd74a --- /dev/null +++ b/llama-datasets/library.json @@ -0,0 +1,87 @@ +{ + "PaulGrahamEssayDataset": { + "id": "paul_graham_essay", + "author": "nerdai", + "keywords": ["rag"] + }, + "BraintrustCodaHelpDeskDataset": { + "id": "braintrust_coda", + "author": "dashk", + "keywords": ["rag", "help desk"] + }, + "PatronusAIFinanceBenchDataset": { + "id": "patronus_financebench", + "author": "anandnk24", + "keywords": ["rag", "finance"] + }, + "BlockchainSolanaDataset": { + "id": "blockchain_solana", + "author": "CalculusC", + "keywords": ["rag", "cryptocurrency"] + }, + "MiniTruthfulQADataset": { + "id": "mini_truthfulqa", + "author": "nerdai", + "keywords": ["rag", "truthfulqa"] + }, + "Llama2PaperDataset": { + "id": "llama2_paper", + "author": "jerryjliu", + "keywords": ["rag", "llama2"] + }, + "Uber10KDataset2021": { + "id": "10k/uber_2021", + "author": "jerryjliu", + "keywords": ["sec", "uber", "10k"] + }, + "MiniSquadV2Dataset": { + "id": "mini_squadv2", + "author": "axiomofjoy", + "keywords": ["rag", "squadv2"] + }, + "OriginOfCovid19Dataset": { + "id": "origin_of_covid19", + "author": "CalculusC", + "keywords": ["rag", "covid-19"] + }, + "EvaluatingLlmSurveyPaperDataset": { + "id": "eval_llm_survey_paper", + "author": "nerdai", + "keywords": ["rag", "evaluation", "paper"] + }, + "CovidQaDataset": { + "id": "covidqa", + "author": "nerdai", + "keywords": ["rag", "covid"] + }, + "MiniCovidQaDataset": { + "id": "mini_covidqa", + "author": "nerdai", + "keywords": ["rag", "covid", "mini"] + }, + "HistoryOfAlexnetDataset": { + "id": "history_of_alexnet", + "author": "CalculusC", + "keywords": ["rag", "alexnet"] + }, + "DocugamiKgRagSec10Q": { + "id": "docugami_kg_rag/sec_10_q", + "author": "Docugami", + "keywords": ["rag", "kg-rag", "10q", "docugami"] + }, + "MtBenchHumanJudgementDataset": { + "id": "mt_bench_humanjudgement", + "author": "nerdai", + "keywords": ["evaluator", "llm as judge", "human agreement"] + }, + "MiniMtBenchSingleGradingDataset": { + "id": "mini_mt_bench_singlegrading", + "author": "nerdai", + "keywords": ["evaluator", "llm as judge"] + }, + "MiniEsgBenchDataset": { + "id": "mini_esg_bench", + "author": "nerdai", + "keywords": ["rag", "pdf", "esg"] + } +} diff --git a/llama-datasets/llama2_paper/BUILD b/llama-datasets/llama2_paper/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/llama2_paper/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/llama2_paper/README.md b/llama-datasets/llama2_paper/README.md new file mode 100644 index 0000000000000..457cd8fc2f188 --- /dev/null +++ b/llama-datasets/llama2_paper/README.md @@ -0,0 +1,59 @@ +# Llama 2 Paper Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset Llama2PaperDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset("Llama2PaperDataset", "./data") + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/llama2_paper/__init__.py b/llama-datasets/llama2_paper/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-datasets/llama2_paper/card.json b/llama-datasets/llama2_paper/card.json new file mode 100644 index 0000000000000..ce772a8f76205 --- /dev/null +++ b/llama-datasets/llama2_paper/card.json @@ -0,0 +1,27 @@ +{ + "name": "Llama 2 Paper Dataset", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset based off the Llama 2 ArXiv PDF.", + "numberObservations": 100, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": ["https://arxiv.org/abs/2307.09288"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.939, + "correctness": 4.08, + "faithfulness": 0.97, + "relevancy": 0.95 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/llama2_paper/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/llama2_paper/llamaindex_baseline.py b/llama-datasets/llama2_paper/llamaindex_baseline.py new file mode 100644 index 0000000000000..110208f68248c --- /dev/null +++ b/llama-datasets/llama2_paper/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("Llama2PaperDataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mini_covidqa/BUILD b/llama-datasets/mini_covidqa/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/mini_covidqa/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/mini_covidqa/README.md b/llama-datasets/mini_covidqa/README.md new file mode 100644 index 0000000000000..350c82c739e33 --- /dev/null +++ b/llama-datasets/mini_covidqa/README.md @@ -0,0 +1,59 @@ +# Mini Covid Qa Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset MiniCovidQaDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset("MiniCovidQaDataset", "./data") + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/mini_covidqa/card.json b/llama-datasets/mini_covidqa/card.json new file mode 100644 index 0000000000000..99c5b97746ad6 --- /dev/null +++ b/llama-datasets/mini_covidqa/card.json @@ -0,0 +1,29 @@ +{ + "name": "Mini Covid QA Dataset", + "className": "LabelledRagDataset", + "description": "This dataset is a mini version of CovidQaDataset.\n A human-annotated RAG dataset consisting of over 300 question-answer pairs. This dataset represents a subset of the Covid-QA dataset available on Kaggle and authored by Xhlulu. It is a collection of frequently asked questions on COVID from various websites. This subset only considers the top 10 webpages containing the most question-answer pairs.", + "numberObservations": 42, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": [ + "https://www.kaggle.com/datasets/xhlulu/mini_covidqa/?select=news.csv" + ], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": null, + "correctness": 4.214, + "faithfulness": 0.857, + "relevancy": 0.833 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_covidqa/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/mini_covidqa/llamaindex_baseline.py b/llama-datasets/mini_covidqa/llamaindex_baseline.py new file mode 100644 index 0000000000000..bcc9e5b963f81 --- /dev/null +++ b/llama-datasets/mini_covidqa/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("MiniCovidQaDataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=40, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mini_esg_bench/BUILD b/llama-datasets/mini_esg_bench/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/mini_esg_bench/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/mini_esg_bench/README.md b/llama-datasets/mini_esg_bench/README.md new file mode 100644 index 0000000000000..2f3c89c00115b --- /dev/null +++ b/llama-datasets/mini_esg_bench/README.md @@ -0,0 +1,75 @@ +# Mini Esg Bench Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset MiniEsgBenchDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "MiniEsgBenchDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` + +## Citing the data + +If you choose to use this dataset for research, it would be appreciated if you +could cite it with given details below. + +```text +@misc{llamaindex_mini_esg_bench_2023, + title={Mini ESG Bench}, + author={Val Andrei Fajardo}, + year={2023}, + organization={llamaindex} +} +``` diff --git a/llama-datasets/mini_esg_bench/card.json b/llama-datasets/mini_esg_bench/card.json new file mode 100644 index 0000000000000..726fd0909bb4a --- /dev/null +++ b/llama-datasets/mini_esg_bench/card.json @@ -0,0 +1,27 @@ +{ + "name": "Mini ESG Bench Dataset", + "className": "LabelledRagDataset", + "description": "This dataset is meant to be a difficult benchmark for pdf parsers. In particular, adopting the terminology used in the PDFTriage paper (https://arxiv.org/abs/2309.08872), we curate difficult questions involving structural knowledge of the PDF documents. The examples in this dataset come from the Environment, Social and (corporate) Governance (ESG) reports of FAANG (companies) and Microsoft in 2021-2022.", + "numberObservations": 50, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": [], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.836, + "correctness": 1.88, + "faithfulness": 0.84, + "relevancy": 0.6 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_esg_bench/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/mini_esg_bench/llamaindex_baseline.py b/llama-datasets/mini_esg_bench/llamaindex_baseline.py new file mode 100644 index 0000000000000..7aa4607b0f271 --- /dev/null +++ b/llama-datasets/mini_esg_bench/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("MiniEsgBenchDataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mini_mt_bench_singlegrading/BUILD b/llama-datasets/mini_mt_bench_singlegrading/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/mini_mt_bench_singlegrading/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/mini_mt_bench_singlegrading/README.md b/llama-datasets/mini_mt_bench_singlegrading/README.md new file mode 100644 index 0000000000000..ed7170a4f9321 --- /dev/null +++ b/llama-datasets/mini_mt_bench_singlegrading/README.md @@ -0,0 +1,82 @@ +# Mini Mt Bench Single Grading Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset MiniMtBenchSingleGradingDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledEvaluatorDataset + +evaluator_dataset = LabelledEvaluatorDataset.from_json( + "./data/pairwise_evaluation_dataset.json" +) +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `EvaluatorBenchmarkerPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core.evaluation import CorrectnessEvaluator +from llama_index.llms import OpenAI +from llama_index.core import ServiceContext + +# download benchmark dataset +evaluator_dataset, _ = download_llama_dataset( + "MiniMtBenchSingleGradingDataset", "./data" +) + +# define your evaluator +gpt_4_context = ServiceContext.from_defaults( + llm=OpenAI(temperature=0, model="gpt-4"), +) + +evaluator = CorrectnessEvaluator(service_context=gpt_4_context) + +# evaluate using the EvaluatorBenchmarkerPack +EvaluatorBenchmarkerPack = download_llama_pack( + "EvaluatorBenchmarkerPack", "./pack" +) +evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluator, + eval_dataset=evaluator_dataset, + show_progress=True, +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await evaluator_benchmarker.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` + +## Original data citation + +```text +@misc{zheng2023judging, + title={Judging LLM-as-a-judge with MT-Bench and Chatbot Arena}, + author={Lianmin Zheng and Wei-Lin Chiang and Ying Sheng and Siyuan Zhuang and Zhanghao Wu and Yonghao Zhuang and Zi Lin and Zhuohan Li and Dacheng Li and Eric. P Xing and Hao Zhang and Joseph E. Gonzalez and Ion Stoica}, + year={2023}, + eprint={2306.05685}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +``` diff --git a/llama-datasets/mini_mt_bench_singlegrading/baselines.py b/llama-datasets/mini_mt_bench_singlegrading/baselines.py new file mode 100644 index 0000000000000..f8096e882ba40 --- /dev/null +++ b/llama-datasets/mini_mt_bench_singlegrading/baselines.py @@ -0,0 +1,84 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core.evaluation import CorrectnessEvaluator +from llama_index.llms import OpenAI, Gemini +from llama_index.core import ServiceContext +import pandas as pd + + +async def main(): + # DOWNLOAD LLAMADATASET + evaluator_dataset, _ = download_llama_dataset( + "MiniMtBenchSingleGradingDataset", "./mini_mt_bench_data" + ) + + # DEFINE EVALUATORS + gpt_4_context = ServiceContext.from_defaults( + llm=OpenAI(temperature=0, model="gpt-4"), + ) + + gpt_3p5_context = ServiceContext.from_defaults( + llm=OpenAI(temperature=0, model="gpt-3.5-turbo"), + ) + + gemini_pro_context = ServiceContext.from_defaults( + llm=Gemini(model="models/gemini-pro", temperature=0) + ) + + evaluators = { + "gpt-4": CorrectnessEvaluator(service_context=gpt_4_context), + "gpt-3.5": CorrectnessEvaluator(service_context=gpt_3p5_context), + "gemini-pro": CorrectnessEvaluator(service_context=gemini_pro_context), + } + + # EVALUATE WITH PACK + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + EvaluatorBenchmarkerPack = download_llama_pack("EvaluatorBenchmarkerPack", "./pack") + evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluators["gpt-3.5"], + eval_dataset=evaluator_dataset, + show_progress=True, + ) + gpt_3p5_benchmark_df = await evaluator_benchmarker.arun( + batch_size=100, sleep_time_in_seconds=0 + ) + + evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluators["gpt-4"], + eval_dataset=evaluator_dataset, + show_progress=True, + ) + gpt_4_benchmark_df = await evaluator_benchmarker.arun( + batch_size=100, sleep_time_in_seconds=0 + ) + + evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluators["gemini-pro"], + eval_dataset=evaluator_dataset, + show_progress=True, + ) + gemini_pro_benchmark_df = await evaluator_benchmarker.arun( + batch_size=5, sleep_time_in_seconds=0.5 + ) + + benchmark_df = pd.concat( + [ + gpt_3p5_benchmark_df, + gpt_4_benchmark_df, + gemini_pro_benchmark_df, + ], + axis=0, + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mini_mt_bench_singlegrading/card.json b/llama-datasets/mini_mt_bench_singlegrading/card.json new file mode 100644 index 0000000000000..06bfe889a7dd2 --- /dev/null +++ b/llama-datasets/mini_mt_bench_singlegrading/card.json @@ -0,0 +1,55 @@ +{ + "name": "Mini MT Bench Dataset", + "className": "LabelledEvaluatorDataset", + "description": "This is a miniature version to the original MT Bench (Single-Grading) Dataset. In particular, this dataset only consists of answers produced by Llama2-70b LLM to the 160 questions i.e., 80 x 2 since there are two turns. The reference evaluations are done using the `CorrectnessEvaluator` class and with GPT-4 as the judge LLM.", + "numberObservations": 160, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": [ + "https://huggingface.co/spaces/lmsys/mt-bench/tree/main/data/mt_bench" + ], + "baselines": [ + { + "name": "gpt-3.5", + "config": { + "promptUrl": "https://github.com/run-llama/llama_index.core/blob/e471e5f8a93ddae6d366cdbba8a497cd6728c7f8/llama_index.core/evaluation/correctness.py#L17", + "llm": "gpt-3.5" + }, + "metrics": { + "invalidPredictions": 0, + "correlation": 0.317, + "meanAbsoluteError": 1.119, + "hamming": 27 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_mt_bench_singlegrading/baselines.py" + }, + { + "name": "gpt-4", + "config": { + "promptUrl": "https://github.com/run-llama/llama_index.core/blob/e471e5f8a93ddae6d366cdbba8a497cd6728c7f8/llama_index.core/evaluation/correctness.py#L17", + "llm": "gpt-4" + }, + "metrics": { + "invalidPredictions": 0, + "correlation": 0.966, + "meanAbsoluteError": 0.094, + "hamming": 143 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_mt_bench_singlegrading/baselines.py" + }, + { + "name": "gemini-pro", + "config": { + "promptUrl": "https://github.com/run-llama/llama_index.core/blob/e471e5f8a93ddae6d366cdbba8a497cd6728c7f8/llama_index.core/evaluation/correctness.py#L17", + "llm": "gemini-pro" + }, + "metrics": { + "invalidPredictions": 1, + "correlation": 0.295, + "meanAbsoluteError": 1.22, + "hamming": 12 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_mt_bench_singlegrading/baselines.py" + } + ] +} diff --git a/llama-datasets/mini_squadv2/BUILD b/llama-datasets/mini_squadv2/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/mini_squadv2/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/mini_squadv2/README.md b/llama-datasets/mini_squadv2/README.md new file mode 100644 index 0000000000000..ea5cdfd259e9f --- /dev/null +++ b/llama-datasets/mini_squadv2/README.md @@ -0,0 +1,79 @@ +# Mini Squad V2 Dataset + +[![arize (100 x 40 px)](https://github.com/nerdai/llama-hub/assets/92402603/eb4cb77a-1a1a-48a0-9f9d-277798832200)](https://arize.com/) + +This dataset was prepared in collaboration with Xander Song of Arize AI. + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset MiniSquadV2Dataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset("MiniSquadV2Dataset", "./data") + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` + +## Original data citation + +```tex +@article{2016arXiv160605250R, + author = {{Rajpurkar}, Pranav and {Zhang}, Jian and {Lopyrev}, + Konstantin and {Liang}, Percy}, + title = "{SQuAD: 100,000+ Questions for Machine Comprehension of Text}", + journal = {arXiv e-prints}, + year = 2016, + eid = {arXiv:1606.05250}, + pages = {arXiv:1606.05250}, +archivePrefix = {arXiv}, + eprint = {1606.05250}, +} +``` diff --git a/llama-datasets/mini_squadv2/card.json b/llama-datasets/mini_squadv2/card.json new file mode 100644 index 0000000000000..6c5adffa7cd01 --- /dev/null +++ b/llama-datasets/mini_squadv2/card.json @@ -0,0 +1,27 @@ +{ + "name": "Mini Squad V2 Dataset", + "className": "LabelledRagDataset", + "description": "This is a subset of the original SquadV2 dataset. In particular, it considers only the top 10 Wikipedia pages in terms of having questions about them.", + "numberObservations": 195, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": ["https://huggingface.co/datasets/squad_v2"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.878, + "correctness": 3.464, + "faithfulness": 0.815, + "relevancy": 0.697 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_squadv2/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/mini_squadv2/llamaindex_baseline.py b/llama-datasets/mini_squadv2/llamaindex_baseline.py new file mode 100644 index 0000000000000..5bfe2a58b6451 --- /dev/null +++ b/llama-datasets/mini_squadv2/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("MiniSquadV2Dataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mini_truthfulqa/BUILD b/llama-datasets/mini_truthfulqa/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/mini_truthfulqa/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/mini_truthfulqa/README.md b/llama-datasets/mini_truthfulqa/README.md new file mode 100644 index 0000000000000..3ae8b99b1177c --- /dev/null +++ b/llama-datasets/mini_truthfulqa/README.md @@ -0,0 +1,74 @@ +# Mini TruthfulQA Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset MiniTruthfulQADataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "MiniTruthfulQADataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` + +## Original data citation + +```tex +@misc{lin2021truthfulqa, + title={TruthfulQA: Measuring How Models Mimic Human Falsehoods}, + author={Stephanie Lin and Jacob Hilton and Owain Evans}, + year={2021}, + eprint={2109.07958}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +``` diff --git a/llama-datasets/mini_truthfulqa/card.json b/llama-datasets/mini_truthfulqa/card.json new file mode 100644 index 0000000000000..dae559b961270 --- /dev/null +++ b/llama-datasets/mini_truthfulqa/card.json @@ -0,0 +1,27 @@ +{ + "name": "Mini TruthfulQA Dataset", + "className": "LabelledRagDataset", + "description": "This is a subset of the TruthfulQA benchmark. Only examples that are based off of Wikipedia pages are considered; and furthermore, Wikipedia pages that contain only one question are also dropped. The result is 152 examples for evaluating a RAG system.", + "numberObservations": 152, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": ["https://huggingface.co/datasets/truthful_qa"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": null, + "correctness": 3.845, + "faithfulness": 0.605, + "relevancy": 0.599 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mini_truthfulqa/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/mini_truthfulqa/llamaindex_baseline.py b/llama-datasets/mini_truthfulqa/llamaindex_baseline.py new file mode 100644 index 0000000000000..457fafcc58636 --- /dev/null +++ b/llama-datasets/mini_truthfulqa/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("MiniTruthfulQADataset", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mt_bench_humanjudgement/BUILD b/llama-datasets/mt_bench_humanjudgement/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/mt_bench_humanjudgement/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/mt_bench_humanjudgement/README.md b/llama-datasets/mt_bench_humanjudgement/README.md new file mode 100644 index 0000000000000..9f9e9d762e513 --- /dev/null +++ b/llama-datasets/mt_bench_humanjudgement/README.md @@ -0,0 +1,82 @@ +# Mt Bench Human Judgement Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset MtBenchHumanJudgementDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledPairwiseEvaluatorDataset + +pairwise_evaluator_dataset = LabelledPairwiseEvaluatorDataset.from_json( + "./data/pairwise_evaluator_dataset.json" +) +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `EvaluatorBenchmarkerPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core.evaluator import PairwiseComparisonEvaluator +from llama_index.llms import OpenAI +from llama_index.core import ServiceContext + +# download benchmark dataset +pairwise_evaluator_dataset, _ = download_llama_dataset( + "MtBenchHumanJudgementDataset", "./data" +) + +# define your evaluator +gpt_4_context = ServiceContext.from_defaults( + llm=OpenAI(temperature=0, model="gpt-4"), +) + +evaluator = PairwiseComparisonEvaluator(service_context=gpt_4_context) + +# evaluate using the EvaluatorBenchmarkerPack +EvaluatorBenchmarkerPack = download_llama_pack( + "EvaluatorBenchmarkerPack", "./pack" +) +evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluator, + eval_dataset=pairwise_evaluator_dataset, + show_progress=True, +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await evaluator_benchmarker.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` + +## Original data citation + +```text +@misc{zheng2023judging, + title={Judging LLM-as-a-judge with MT-Bench and Chatbot Arena}, + author={Lianmin Zheng and Wei-Lin Chiang and Ying Sheng and Siyuan Zhuang and Zhanghao Wu and Yonghao Zhuang and Zi Lin and Zhuohan Li and Dacheng Li and Eric. P Xing and Hao Zhang and Joseph E. Gonzalez and Ion Stoica}, + year={2023}, + eprint={2306.05685}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +``` diff --git a/llama-datasets/mt_bench_humanjudgement/baselines.py b/llama-datasets/mt_bench_humanjudgement/baselines.py new file mode 100644 index 0000000000000..3e8094ef91540 --- /dev/null +++ b/llama-datasets/mt_bench_humanjudgement/baselines.py @@ -0,0 +1,84 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core.evaluation import PairwiseComparisonEvaluator +from llama_index.llms import OpenAI, Gemini +from llama_index.core import ServiceContext +import pandas as pd + + +async def main(): + # DOWNLOAD LLAMADATASET + pairwise_evaluator_dataset, _ = download_llama_dataset( + "MtBenchHumanJudgementDataset", "./mt_bench_data" + ) + + # DEFINE EVALUATORS + gpt_4_context = ServiceContext.from_defaults( + llm=OpenAI(temperature=0, model="gpt-4"), + ) + + gpt_3p5_context = ServiceContext.from_defaults( + llm=OpenAI(temperature=0, model="gpt-3.5-turbo"), + ) + + gemini_pro_context = ServiceContext.from_defaults( + llm=Gemini(model="models/gemini-pro", temperature=0) + ) + + evaluators = { + "gpt-4": PairwiseComparisonEvaluator(service_context=gpt_4_context), + "gpt-3.5": PairwiseComparisonEvaluator(service_context=gpt_3p5_context), + "gemini-pro": PairwiseComparisonEvaluator(service_context=gemini_pro_context), + } + + # EVALUATE WITH PACK + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + EvaluatorBenchmarkerPack = download_llama_pack("EvaluatorBenchmarkerPack", "./pack") + evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluators["gpt-3.5"], + eval_dataset=pairwise_evaluator_dataset, + show_progress=True, + ) + gpt_3p5_benchmark_df = await evaluator_benchmarker.arun( + batch_size=100, sleep_time_in_seconds=0 + ) + + evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluators["gpt-4"], + eval_dataset=pairwise_evaluator_dataset, + show_progress=True, + ) + gpt_4_benchmark_df = await evaluator_benchmarker.arun( + batch_size=100, sleep_time_in_seconds=0 + ) + + evaluator_benchmarker = EvaluatorBenchmarkerPack( + evaluator=evaluators["gemini-pro"], + eval_dataset=pairwise_evaluator_dataset, + show_progress=True, + ) + gemini_pro_benchmark_df = await evaluator_benchmarker.arun( + batch_size=5, sleep_time_in_seconds=0.5 + ) + + benchmark_df = pd.concat( + [ + gpt_3p5_benchmark_df, + gpt_4_benchmark_df, + gemini_pro_benchmark_df, + ], + axis=0, + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/mt_bench_humanjudgement/card.json b/llama-datasets/mt_bench_humanjudgement/card.json new file mode 100644 index 0000000000000..286ab624ce64d --- /dev/null +++ b/llama-datasets/mt_bench_humanjudgement/card.json @@ -0,0 +1,58 @@ +{ + "name": "MT Bench Human Judgement Dataset", + "className": "LabelledPairwiseEvaluatorDataset", + "description": "This is an adaptation of the original MT Bench Human Judgement dataset, where human evaluators compare two llm model responses and rank them according to their own preference. In the original version, there can be more than one human evaluator for a given example (query, two model responses). In this adapted version however, we aggregate these 'repeated' entries and convert the 'winner' column of the original schema to instead represent the proportion of times 'model_a' wins across all of the human evaluators. To adapt this to a llama-dataset, and to better consider ties (albeit with small samples) we set an uncertainty threshold for this proportion in that if it is between [0.4, 0.6] then we consider there to be no winner between the two models.", + "numberObservations": 1204, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": [ + "https://huggingface.co/datasets/lmsys/mt_bench_human_judgments" + ], + "baselines": [ + { + "name": "gpt-3.5", + "config": { + "promptUrl": "https://github.com/run-llama/llama_index.core/blob/e471e5f8a93ddae6d366cdbba8a497cd6728c7f8/llama_index.core/evaluation/pairwise.py#L21", + "llm": "gpt-3.5" + }, + "metrics": { + "invalidPredictions": 89, + "inconclusives": 407, + "ties": 51, + "agreementRateWithTies": 0.743, + "agreementRateWithoutTies": 0.798 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mt_bench_humanjudgement/baselines.py" + }, + { + "name": "gpt-4", + "config": { + "promptUrl": "https://github.com/run-llama/llama_index.core/blob/e471e5f8a93ddae6d366cdbba8a497cd6728c7f8/llama_index.core/evaluation/pairwise.py#L21", + "llm": "gpt-4" + }, + "metrics": { + "invalidPredictions": 1, + "inconclusives": 107, + "ties": 102, + "agreementRateWithTies": 0.709, + "agreementRateWithoutTies": 0.779 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mt_bench_humanjudgement/baselines.py" + }, + { + "name": "gemini-pro", + "config": { + "promptUrl": "https://github.com/run-llama/llama_index.core/blob/e471e5f8a93ddae6d366cdbba8a497cd6728c7f8/llama_index.core/evaluation/pairwise.py#L21", + "llm": "gemini-pro" + }, + "metrics": { + "invalidPredictions": 2, + "inconclusives": 295, + "ties": 60, + "agreementRateWithTies": 0.742, + "agreementRateWithoutTies": 0.793 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/mt_bench_humanjudgement/baselines.py" + } + ] +} diff --git a/llama-datasets/origin_of_covid19/BUILD b/llama-datasets/origin_of_covid19/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/origin_of_covid19/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/origin_of_covid19/README.md b/llama-datasets/origin_of_covid19/README.md new file mode 100644 index 0000000000000..a7d8b4b9bd8b6 --- /dev/null +++ b/llama-datasets/origin_of_covid19/README.md @@ -0,0 +1,62 @@ +# Origin Of COVID-19 Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset OriginOfCovid19Dataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "OriginOfCovid19Dataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) +benchmark_df = rag_evaluator_pack.run() # async arun() supported as well + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/origin_of_covid19/card.json b/llama-datasets/origin_of_covid19/card.json new file mode 100644 index 0000000000000..6686d3a68b978 --- /dev/null +++ b/llama-datasets/origin_of_covid19/card.json @@ -0,0 +1,27 @@ +{ + "name": "Origin Of Covid19 Dataset", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset based off an article, The Origin Of COVID-19 and Why It Matters, by Morens DM, Breman JG, Calisher CH, Doherty PC, Hahn BH, Keusch GT, Kramer LD, LeDuc JW, Monath TP, Taubenberger JK, consisting of queries, reference answers, and reference contexts.", + "numberObservations": 24, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": ["https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7470595/"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.952, + "correctness": 4.562, + "faithfulness": 1.0, + "relevancy": 0.958 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/origin_of_covid19/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/origin_of_covid19/llamaindex_baseline.py b/llama-datasets/origin_of_covid19/llamaindex_baseline.py new file mode 100644 index 0000000000000..83a5f7415054c --- /dev/null +++ b/llama-datasets/origin_of_covid19/llamaindex_baseline.py @@ -0,0 +1,35 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset("OriginOfCovid19", "./data") + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/patronus_financebench/BUILD b/llama-datasets/patronus_financebench/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/patronus_financebench/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/patronus_financebench/README.md b/llama-datasets/patronus_financebench/README.md new file mode 100644 index 0000000000000..3fb0fdce8129d --- /dev/null +++ b/llama-datasets/patronus_financebench/README.md @@ -0,0 +1,68 @@ +# Patronus AI FinanceBench Dataset + +[![patronus-ai-logo (200 x 40 px)](https://github.com/nerdai/llama-hub/assets/92402603/62a6df3f-57a3-4d68-917b-b0947392efcd)](https://www.patronus.ai/) + +This dataset is a subset of the original FinanceBench dataset. In particular, to +make this benchmark more computationally efficient, we only keep the documents for +which there are 2 or more questions. Such filtering, reduced the total unique pdf +documents from 98 to 32. + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset PatronusAIFinanceBenchDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "PatronusAIFinanceBenchDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/patronus_financebench/__init__.py b/llama-datasets/patronus_financebench/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-datasets/patronus_financebench/card.json b/llama-datasets/patronus_financebench/card.json new file mode 100644 index 0000000000000..38bcfffb6d09d --- /dev/null +++ b/llama-datasets/patronus_financebench/card.json @@ -0,0 +1,27 @@ +{ + "name": "Patronus AI FinanceBench", + "className": "LabelledRagDataset", + "description": "This is a subset of the original FinanceBench dataset. FinanceBench is a first-of-its-kind test suite for evaluating the performance of LLMs on open book financial question answering (QA). This is an open source sample of 150 annotated examples used in the evaluation and analysis of models assessed in the FinanceBench paper. The dataset comprises of questions about publicly traded companies, with corresponding answers and evidence strings. The questions in FinanceBench are ecologically valid and cover a diverse set of scenarios. They are intended to be clear-cut and straightforward to answer to serve as a minimum performance standard.", + "numberObservations": 98, + "containsExamplesByHumans": true, + "containsExamplesByAi": false, + "sourceUrls": ["https://huggingface.co/datasets/PatronusAI/financebench"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 1, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.87, + "correctness": 2.622, + "faithfulness": 0.755, + "relevancy": 0.684 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/patronus_financebench/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/patronus_financebench/llamaindex_baseline.py b/llama-datasets/patronus_financebench/llamaindex_baseline.py new file mode 100644 index 0000000000000..7b9b31b20ec9f --- /dev/null +++ b/llama-datasets/patronus_financebench/llamaindex_baseline.py @@ -0,0 +1,37 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "PatronusAIFinanceBenchDataset", "./patronus_financebench" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/paul_graham_essay/BUILD b/llama-datasets/paul_graham_essay/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-datasets/paul_graham_essay/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-datasets/paul_graham_essay/README.md b/llama-datasets/paul_graham_essay/README.md new file mode 100644 index 0000000000000..ac92763359758 --- /dev/null +++ b/llama-datasets/paul_graham_essay/README.md @@ -0,0 +1,61 @@ +# Paul Graham Essay Dataset + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset PaulGrahamEssayDataset --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset( + "PaulGrahamEssayDataset", "./data" +) + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-datasets/paul_graham_essay/__init__.py b/llama-datasets/paul_graham_essay/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-datasets/paul_graham_essay/card.json b/llama-datasets/paul_graham_essay/card.json new file mode 100644 index 0000000000000..8d4eb8d67dc98 --- /dev/null +++ b/llama-datasets/paul_graham_essay/card.json @@ -0,0 +1,27 @@ +{ + "name": "Paul Graham Essay", + "className": "LabelledRagDataset", + "description": "A labelled RAG dataset based off an essay by Paul Graham, consisting of queries, reference answers, and reference contexts.", + "numberObservations": 44, + "containsExamplesByHumans": false, + "containsExamplesByAi": true, + "sourceUrls": ["http://www.paulgraham.com/articles.html"], + "baselines": [ + { + "name": "llamaindex", + "config": { + "chunkSize": 1024, + "llm": "gpt-3.5-turbo", + "similarityTopK": 2, + "embedModel": "text-embedding-ada-002" + }, + "metrics": { + "contextSimilarity": 0.934, + "correctness": 4.239, + "faithfulness": 0.977, + "relevancy": 0.977 + }, + "codeUrl": "https://github.com/run-llama/llama-hub/blob/main/llama_hub/llama_datasets/paul_graham_essay/llamaindex_baseline.py" + } + ] +} diff --git a/llama-datasets/paul_graham_essay/llamaindex_baseline.py b/llama-datasets/paul_graham_essay/llamaindex_baseline.py new file mode 100644 index 0000000000000..f5f3a76eed926 --- /dev/null +++ b/llama-datasets/paul_graham_essay/llamaindex_baseline.py @@ -0,0 +1,37 @@ +import asyncio + +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + + +async def main(): + # DOWNLOAD LLAMADATASET + rag_dataset, documents = download_llama_dataset( + "PaulGrahamEssayDataset", "./paul_graham" + ) + + # BUILD BASIC RAG PIPELINE + index = VectorStoreIndex.from_documents(documents=documents) + query_engine = index.as_query_engine() + + # EVALUATE WITH PACK + RagEvaluatorPack = download_llama_pack("RagEvaluatorPack", "./pack_stuff") + rag_evaluator = RagEvaluatorPack(query_engine=query_engine, rag_dataset=rag_dataset) + + ############################################################################ + # NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # + # then you'll need to use different batch_size and sleep_time_in_seconds. # + # For Usage Tier 1, settings that seemed to work well were batch_size=5, # + # and sleep_time_in_seconds=15 (as of December 2023.) # + ############################################################################ + benchmark_df = await rag_evaluator.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # number of seconds sleep before making an api call + ) + print(benchmark_df) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main) diff --git a/llama-datasets/template_README.md b/llama-datasets/template_README.md new file mode 100644 index 0000000000000..10852a6ffc3cd --- /dev/null +++ b/llama-datasets/template_README.md @@ -0,0 +1,59 @@ +# {NAME} + +## CLI Usage + +You can download `llamadatasets` directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamadataset {NAME_CAMELCASE} --download-dir ./data +``` + +You can then inspect the files at `./data`. When you're ready to load the data into +python, you can use the below snippet of code: + +```python +from llama_index.core import SimpleDirectoryReader +from llama_index.core.llama_dataset import LabelledRagDataset + +rag_dataset = LabelledRagDataset.from_json("./data/rag_dataset.json") +documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data() +``` + +## Code Usage + +You can download the dataset to a directory, say `./data` directly in Python +as well. From there, you can use the convenient `RagEvaluatorPack` llamapack to +run your own LlamaIndex RAG pipeline with the `llamadataset`. + +```python +from llama_index.core.llama_dataset import download_llama_dataset +from llama_index.core.llama_pack import download_llama_pack +from llama_index.core import VectorStoreIndex + +# download and install dependencies for benchmark dataset +rag_dataset, documents = download_llama_dataset("{NAME_CAMELCASE}", "./data") + +# build basic RAG system +index = VectorStoreIndex.from_documents(documents=documents) +query_engine = index.as_query_engine() + +# evaluate using the RagEvaluatorPack +RagEvaluatorPack = download_llama_pack( + "RagEvaluatorPack", "./rag_evaluator_pack" +) +rag_evaluator_pack = RagEvaluatorPack( + rag_dataset=rag_dataset, query_engine=query_engine +) + +############################################################################ +# NOTE: If have a lower tier subscription for OpenAI API like Usage Tier 1 # +# then you'll need to use different batch_size and sleep_time_in_seconds. # +# For Usage Tier 1, settings that seemed to work well were batch_size=5, # +# and sleep_time_in_seconds=15 (as of December 2023.) # +############################################################################ + +benchmark_df = await rag_evaluator_pack.arun( + batch_size=20, # batches the number of openai api calls to make + sleep_time_in_seconds=1, # seconds to sleep before making an api call +) +``` diff --git a/llama-index-cli/llama_index/cli/command_line.py b/llama-index-cli/llama_index/cli/command_line.py index a04a20bd29776..73eda0c50f1a0 100644 --- a/llama-index-cli/llama_index/cli/command_line.py +++ b/llama-index-cli/llama_index/cli/command_line.py @@ -4,10 +4,10 @@ from llama_index.cli.rag import RagCLI, default_ragcli_persist_dir from llama_index.cli.upgrade import upgrade_dir, upgrade_file from llama_index.core.ingestion import IngestionCache, IngestionPipeline +from llama_index.core.download.module import LLAMA_HUB_URL from llama_index.core.llama_dataset.download import ( LLAMA_DATASETS_LFS_URL, LLAMA_DATASETS_SOURCE_FILES_GITHUB_TREE_URL, - LLAMA_HUB_URL, download_llama_dataset, ) from llama_index.core.llama_pack.download import ( diff --git a/llama-index-cli/llama_index/cli/upgrade/mappings.json b/llama-index-cli/llama_index/cli/upgrade/mappings.json index 86b7b3ae5417f..9ad04974d42bb 100644 --- a/llama-index-cli/llama_index/cli/upgrade/mappings.json +++ b/llama-index-cli/llama_index/cli/upgrade/mappings.json @@ -519,6 +519,7 @@ "TextEmbeddingsInference": "llama_index.embeddings.text_embeddings_inference", "UpTrainCallbackHandler": "llama_index.callbacks.uptrain", "deepeval_callback_handler": "llama_index.callbacks.deepeval", + "langfuse_callback_handler": "llama_index.callbacks.langfuse", "OpenInferenceCallbackHandler": "llama_index.callbacks.openinference", "WandbCallbackHandler": "llama_index.callbacks.wandb", "argilla_callback_handler": "llama_index.callbacks.argilla", diff --git a/llama-index-cli/pyproject.toml b/llama-index-cli/pyproject.toml index 0c68a47316e16..392231d2a0ce1 100644 --- a/llama-index-cli/pyproject.toml +++ b/llama-index-cli/pyproject.toml @@ -32,7 +32,7 @@ maintainers = [ name = "llama-index-cli" packages = [{include = "llama_index/"}] readme = "README.md" -version = "0.1.6" +version = "0.1.7" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-core/llama_index/core/__init__.py b/llama-index-core/llama_index/core/__init__.py index 7a5e3bc828e92..53f85eb7b4cb1 100644 --- a/llama-index-core/llama_index/core/__init__.py +++ b/llama-index-core/llama_index/core/__init__.py @@ -1,6 +1,6 @@ """Init file of LlamaIndex.""" -__version__ = "0.10.14" +__version__ = "0.10.16" import logging from logging import NullHandler diff --git a/llama-index-core/llama_index/core/base/embeddings/base.py b/llama-index-core/llama_index/core/base/embeddings/base.py index 5fb65b2fdea5b..65d9760ad68e7 100644 --- a/llama-index-core/llama_index/core/base/embeddings/base.py +++ b/llama-index-core/llama_index/core/base/embeddings/base.py @@ -288,16 +288,13 @@ async def aget_text_embedding_batch( nested_embeddings = [] if show_progress: try: - from tqdm.auto import tqdm - - nested_embeddings = [ - await f - for f in tqdm( - asyncio.as_completed(embeddings_coroutines), - total=len(embeddings_coroutines), - desc="Generating embeddings", - ) - ] + from tqdm.asyncio import tqdm_asyncio + + nested_embeddings = await tqdm_asyncio.gather( + *embeddings_coroutines, + total=len(embeddings_coroutines), + desc="Generating embeddings", + ) except ImportError: nested_embeddings = await asyncio.gather(*embeddings_coroutines) else: diff --git a/llama-index-core/llama_index/core/base/llms/types.py b/llama-index-core/llama_index/core/base/llms/types.py index 66ad45b08de36..d771d75467493 100644 --- a/llama-index-core/llama_index/core/base/llms/types.py +++ b/llama-index-core/llama_index/core/base/llms/types.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Any, AsyncGenerator, Generator, Optional +from typing import Any, AsyncGenerator, Generator, Optional, Union from llama_index.core.bridge.pydantic import BaseModel, Field from llama_index.core.constants import DEFAULT_CONTEXT_WINDOW, DEFAULT_NUM_OUTPUTS @@ -14,6 +14,7 @@ class MessageRole(str, Enum): FUNCTION = "function" TOOL = "tool" CHATBOT = "chatbot" + MODEL = "model" # ===== Generic Model Input - Chat ===== @@ -27,6 +28,17 @@ class ChatMessage(BaseModel): def __str__(self) -> str: return f"{self.role.value}: {self.content}" + @classmethod + def from_str( + cls, + content: str, + role: Union[MessageRole, str] = MessageRole.USER, + **kwargs: Any, + ) -> "ChatMessage": + if isinstance(role, str): + role = MessageRole(role) + return cls(role=role, content=content, **kwargs) + # ===== Generic Model Output - Chat ===== class ChatResponse(BaseModel): diff --git a/llama-index-core/llama_index/core/callbacks/global_handlers.py b/llama-index-core/llama_index/core/callbacks/global_handlers.py index d915eb16451d7..5a49d193cd7b0 100644 --- a/llama-index-core/llama_index/core/callbacks/global_handlers.py +++ b/llama-index-core/llama_index/core/callbacks/global_handlers.py @@ -95,6 +95,17 @@ def create_global_handler(eval_mode: str, **eval_params: Any) -> BaseCallbackHan "Please install it using `pip install llama-index-callbacks-argilla`" ) handler = argilla_callback_handler(**eval_params) + elif eval_mode == "langfuse": + try: + from llama_index.callbacks.langfuse import ( + langfuse_callback_handler, + ) # pants: no-infer-dep + except ImportError: + raise ImportError( + "LangfuseCallbackHandler is not installed. " + "Please install it using `pip install llama-index-callbacks-langfuse`" + ) + handler = langfuse_callback_handler(**eval_params) else: raise ValueError(f"Eval mode {eval_mode} not supported.") diff --git a/llama-index-core/llama_index/core/chat_engine/types.py b/llama-index-core/llama_index/core/chat_engine/types.py index 3174622edbb59..124e1f5d6bca4 100644 --- a/llama-index-core/llama_index/core/chat_engine/types.py +++ b/llama-index-core/llama_index/core/chat_engine/types.py @@ -178,14 +178,19 @@ def response_gen(self) -> Generator[str, None, None]: self.response = self._unformatted_response.strip() async def async_response_gen(self) -> AsyncGenerator[str, None]: - while not self._is_done or not self._aqueue.empty(): - if not self._aqueue.empty(): - delta = self._aqueue.get_nowait() - self._unformatted_response += delta - yield delta + while True: + if not self._aqueue.empty() or not self._is_done: + try: + delta = await asyncio.wait_for(self._aqueue.get(), timeout=0.1) + except asyncio.TimeoutError: + if self._is_done: + break + continue + if delta is not None: + self._unformatted_response += delta + yield delta else: - await self._new_item_event.wait() # Wait until a new item is added - self._new_item_event.clear() # Clear the event for the next wait + break self.response = self._unformatted_response.strip() def print_response_stream(self) -> None: diff --git a/llama-index-core/llama_index/core/command_line/mappings.json b/llama-index-core/llama_index/core/command_line/mappings.json index 86b7b3ae5417f..9ad04974d42bb 100644 --- a/llama-index-core/llama_index/core/command_line/mappings.json +++ b/llama-index-core/llama_index/core/command_line/mappings.json @@ -519,6 +519,7 @@ "TextEmbeddingsInference": "llama_index.embeddings.text_embeddings_inference", "UpTrainCallbackHandler": "llama_index.callbacks.uptrain", "deepeval_callback_handler": "llama_index.callbacks.deepeval", + "langfuse_callback_handler": "llama_index.callbacks.langfuse", "OpenInferenceCallbackHandler": "llama_index.callbacks.openinference", "WandbCallbackHandler": "llama_index.callbacks.wandb", "argilla_callback_handler": "llama_index.callbacks.argilla", diff --git a/llama-index-core/llama_index/core/download/dataset.py b/llama-index-core/llama_index/core/download/dataset.py index 8900107626cdb..5739897598285 100644 --- a/llama-index-core/llama_index/core/download/dataset.py +++ b/llama-index-core/llama_index/core/download/dataset.py @@ -6,7 +6,6 @@ from typing import Any, Dict, List, Optional, Union import tqdm -from llama_index.core.download.module import LLAMA_HUB_URL from llama_index.core.download.utils import ( get_file_content, get_file_content_bytes, @@ -14,6 +13,12 @@ initialize_directory, ) +LLAMA_INDEX_CONTENTS_URL = ( + f"https://raw.githubusercontent.com/run-llama/llama_index/main" +) +LLAMA_DATASETS_PATH = "/llama-datasets" +LLAMA_DATASETS_URL = LLAMA_INDEX_CONTENTS_URL + LLAMA_DATASETS_PATH + LLAMA_DATASETS_LFS_URL = ( f"https://media.githubusercontent.com/media/run-llama/llama-datasets/main" ) @@ -91,7 +96,8 @@ def get_dataset_info( source_files = [] if dataset_class_name == "LabelledRagDataset": source_files = get_source_files_list( - str(remote_source_dir_path), f"/{dataset_id}/{source_files_path}" + str(remote_source_dir_path), + f"/llama_datasets/{dataset_id}/{source_files_path}", ) # create cache dir if needed @@ -141,7 +147,7 @@ def download_dataset_and_source_files( base_file_name = _resolve_dataset_file_name(dataset_class_name) dataset_raw_content, _ = get_file_content( - str(remote_lfs_dir_path), f"/{dataset_id}/{base_file_name}" + str(remote_lfs_dir_path), f"/llama_datasets/{dataset_id}/{base_file_name}" ) with open(f"{module_path}/{base_file_name}", "w") as f: @@ -158,7 +164,7 @@ def download_dataset_and_source_files( if ".pdf" in source_file: source_file_raw_content_bytes, _ = get_file_content_bytes( str(remote_lfs_dir_path), - f"/{dataset_id}/{source_files_dir_path}/{source_file}", + f"/llama_datasets/{dataset_id}/{source_files_dir_path}/{source_file}", ) with open( f"{module_path}/{source_files_dir_path}/{source_file}", "wb" @@ -167,7 +173,7 @@ def download_dataset_and_source_files( else: source_file_raw_content, _ = get_file_content( str(remote_lfs_dir_path), - f"/{dataset_id}/{source_files_dir_path}/{source_file}", + f"/llama_datasets/{dataset_id}/{source_files_dir_path}/{source_file}", ) with open( f"{module_path}/{source_files_dir_path}/{source_file}", "w" @@ -177,7 +183,7 @@ def download_dataset_and_source_files( def download_llama_dataset( dataset_class: str, - llama_hub_url: str = LLAMA_HUB_URL, + llama_datasets_url: str = LLAMA_DATASETS_URL, llama_datasets_lfs_url: str = LLAMA_DATASETS_LFS_URL, llama_datasets_source_files_tree_url: str = LLAMA_DATASETS_SOURCE_FILES_GITHUB_TREE_URL, refresh_cache: bool = False, @@ -218,7 +224,7 @@ def download_llama_dataset( # fetch info from library.json file dataset_info = get_dataset_info( local_dir_path=dirpath, - remote_dir_path=llama_hub_url, + remote_dir_path=llama_datasets_url, remote_source_dir_path=llama_datasets_source_files_tree_url, dataset_class=dataset_class, refresh_cache=refresh_cache, diff --git a/llama-index-core/llama_index/core/embeddings/multi_modal_base.py b/llama-index-core/llama_index/core/embeddings/multi_modal_base.py index 1c41993e3762a..2b8863d2c5b1f 100644 --- a/llama-index-core/llama_index/core/embeddings/multi_modal_base.py +++ b/llama-index-core/llama_index/core/embeddings/multi_modal_base.py @@ -155,16 +155,13 @@ async def aget_image_embedding_batch( nested_embeddings = [] if show_progress: try: - from tqdm.auto import tqdm - - nested_embeddings = [ - await f - for f in tqdm( - asyncio.as_completed(embeddings_coroutines), - total=len(embeddings_coroutines), - desc="Generating image embeddings", - ) - ] + from tqdm.asyncio import tqdm_asyncio + + nested_embeddings = await tqdm_asyncio.gather( + *embeddings_coroutines, + total=len(embeddings_coroutines), + desc="Generating embeddings", + ) except ImportError: nested_embeddings = await asyncio.gather(*embeddings_coroutines) else: diff --git a/llama-index-core/llama_index/core/indices/base.py b/llama-index-core/llama_index/core/indices/base.py index a657a988526ed..a35797a4a1da6 100644 --- a/llama-index-core/llama_index/core/indices/base.py +++ b/llama-index-core/llama_index/core/indices/base.py @@ -85,6 +85,9 @@ def __init__( objects = objects or [] self._object_map = {obj.index_id: obj.obj for obj in objects} + for obj in objects: + obj.obj = None # clear the object to avoid serialization issues + with self._callback_manager.as_trace("index_construction"): if index_struct is None: nodes = nodes or [] diff --git a/llama-index-core/llama_index/core/indices/knowledge_graph/base.py b/llama-index-core/llama_index/core/indices/knowledge_graph/base.py index 86f29a5de8303..b3f9f0013dc3a 100644 --- a/llama-index-core/llama_index/core/indices/knowledge_graph/base.py +++ b/llama-index-core/llama_index/core/indices/knowledge_graph/base.py @@ -259,10 +259,9 @@ def upsert_triplet( self._graph_store.upsert_triplet(*triplet) triplet_str = str(triplet) if include_embeddings: - set_embedding = self._service_context.embed_model.get_text_embedding( - triplet_str - ) + set_embedding = self._embed_model.get_text_embedding(triplet_str) self._index_struct.add_to_embedding_dict(str(triplet), set_embedding) + self._storage_context.index_store.add_index_struct(self._index_struct) def add_node(self, keywords: List[str], node: BaseNode) -> None: """Add node. @@ -300,10 +299,9 @@ def upsert_triplet_and_node( self.add_node([subj, obj], node) triplet_str = str(triplet) if include_embeddings: - set_embedding = self._service_context.embed_model.get_text_embedding( - triplet_str - ) + set_embedding = self._embed_model.get_text_embedding(triplet_str) self._index_struct.add_to_embedding_dict(str(triplet), set_embedding) + self._storage_context.index_store.add_index_struct(self._index_struct) def _delete_node(self, node_id: str, **delete_kwargs: Any) -> None: """Delete a node.""" diff --git a/llama-index-core/llama_index/core/llama_dataset/download.py b/llama-index-core/llama_index/core/llama_dataset/download.py index e17c5657be675..29622e7e4dadd 100644 --- a/llama-index-core/llama_index/core/llama_dataset/download.py +++ b/llama-index-core/llama_index/core/llama_dataset/download.py @@ -4,10 +4,10 @@ from llama_index.core.download.dataset import ( LLAMA_DATASETS_LFS_URL, LLAMA_DATASETS_SOURCE_FILES_GITHUB_TREE_URL, + LLAMA_DATASETS_URL, ) from llama_index.core.download.dataset import download_llama_dataset as download from llama_index.core.download.module import ( - LLAMA_HUB_URL, MODULE_TYPE, track_download, ) @@ -35,7 +35,7 @@ def _resolve_dataset_class(filename: str) -> Type[BaseLlamaDataset]: def download_llama_dataset( llama_dataset_class: str, download_dir: str, - llama_hub_url: str = LLAMA_HUB_URL, + llama_datasets_url: str = LLAMA_DATASETS_URL, llama_datasets_lfs_url: str = LLAMA_DATASETS_LFS_URL, llama_datasets_source_files_tree_url: str = LLAMA_DATASETS_SOURCE_FILES_GITHUB_TREE_URL, show_progress: bool = False, @@ -67,12 +67,12 @@ def download_llama_dataset( """ filenames: Tuple[str, str] = download( llama_dataset_class, - llama_hub_url=llama_hub_url, + llama_datasets_url=llama_datasets_url, llama_datasets_lfs_url=llama_datasets_lfs_url, llama_datasets_source_files_tree_url=llama_datasets_source_files_tree_url, refresh_cache=True, custom_path=download_dir, - library_path="llama_datasets/library.json", + library_path="library.json", disable_library_cache=True, override_path=True, show_progress=show_progress, diff --git a/llama-index-core/llama_index/core/program/multi_modal_llm_program.py b/llama-index-core/llama_index/core/program/multi_modal_llm_program.py index 79621a3d952e4..b42604af24bed 100644 --- a/llama-index-core/llama_index/core/program/multi_modal_llm_program.py +++ b/llama-index-core/llama_index/core/program/multi_modal_llm_program.py @@ -36,7 +36,8 @@ def __init__( @classmethod def from_defaults( cls, - output_parser: PydanticOutputParser, + output_parser: Optional[PydanticOutputParser] = None, + output_cls: Optional[Type[BaseModel]] = None, prompt_template_str: Optional[str] = None, prompt: Optional[PromptTemplate] = None, multi_modal_llm: Optional[MultiModalLLM] = None, @@ -64,6 +65,12 @@ def from_defaults( raise ValueError("Must provide either prompt or prompt_template_str.") if prompt_template_str is not None: prompt = PromptTemplate(prompt_template_str) + + if output_parser is None: + if output_cls is None: + raise ValueError("Must provide either output_cls or output_parser.") + output_parser = PydanticOutputParser(output_cls=output_cls) + return cls( output_parser, prompt=cast(PromptTemplate, prompt), diff --git a/llama-index-core/llama_index/core/prompts/base.py b/llama-index-core/llama_index/core/prompts/base.py index c791076351976..8d3a7a6181d3e 100644 --- a/llama-index-core/llama_index/core/prompts/base.py +++ b/llama-index-core/llama_index/core/prompts/base.py @@ -246,6 +246,20 @@ def __init__( function_mappings=function_mappings, ) + @classmethod + def from_messages( + cls, + message_templates: Union[List[Tuple[str, str]], List[ChatMessage]], + **kwargs: Any, + ) -> "ChatPromptTemplate": + """From messages.""" + if isinstance(message_templates[0], tuple): + message_templates = [ + ChatMessage.from_str(role=role, content=content) + for role, content in message_templates + ] + return cls(message_templates=message_templates, **kwargs) + def partial_format(self, **kwargs: Any) -> "ChatPromptTemplate": prompt = deepcopy(self) prompt.kwargs.update(kwargs) diff --git a/llama-index-core/llama_index/core/prompts/chat_prompts.py b/llama-index-core/llama_index/core/prompts/chat_prompts.py index 3aa6e581b1360..ac5b0cd2d9843 100644 --- a/llama-index-core/llama_index/core/prompts/chat_prompts.py +++ b/llama-index-core/llama_index/core/prompts/chat_prompts.py @@ -69,7 +69,7 @@ "1. **Rewrite** an original answer using the new context.\n" "2. **Repeat** the original answer if the new context isn't useful.\n" "Never reference the original answer or context directly in your answer.\n" - "When in doubt, just repeat the original answer." + "When in doubt, just repeat the original answer.\n" "New Context: {context_msg}\n" "Query: {query_str}\n" "Original Answer: {existing_answer}\n" diff --git a/llama-index-core/llama_index/core/query_engine/jsonalyze_query_engine.py b/llama-index-core/llama_index/core/query_engine/jsonalyze_query_engine.py index e6edc6f8670bc..c2b81afbf9e1e 100644 --- a/llama-index-core/llama_index/core/query_engine/jsonalyze_query_engine.py +++ b/llama-index-core/llama_index/core/query_engine/jsonalyze_query_engine.py @@ -80,7 +80,7 @@ def default_jsonalyzer( try: # Load list of dictionaries into SQLite database db[table_name].insert_all(list_of_dict) - except sqlite_utils.db_exceptions.IntegrityError as exc: + except sqlite_utils.utils.sqlite3.IntegrityError as exc: print_text(f"Error inserting into table {table_name}, expected format:") print_text("[{col1: val1, col2: val2, ...}, ...]") raise ValueError("Invalid list_of_dict") from exc @@ -105,7 +105,7 @@ def default_jsonalyzer( try: # Execute the SQL query results = list(db.query(sql_query)) - except sqlite_utils.db_exceptions.OperationalError as exc: + except sqlite_utils.utils.sqlite3.OperationalError as exc: print_text(f"Error executing query: {sql_query}") raise ValueError("Invalid query") from exc @@ -148,7 +148,7 @@ async def async_default_jsonalyzer( try: # Load list of dictionaries into SQLite database db[table_name].insert_all(list_of_dict) - except sqlite_utils.db_exceptions.IntegrityError as exc: + except sqlite_utils.utils.sqlite3.IntegrityError as exc: print_text(f"Error inserting into table {table_name}, expected format:") print_text("[{col1: val1, col2: val2, ...}, ...]") raise ValueError("Invalid list_of_dict") from exc @@ -173,7 +173,7 @@ async def async_default_jsonalyzer( try: # Execute the SQL query results = list(db.query(sql_query)) - except sqlite_utils.db_exceptions.OperationalError as exc: + except sqlite_utils.utils.sqlite3.OperationalError as exc: print_text(f"Error executing query: {sql_query}") raise ValueError("Invalid query") from exc diff --git a/llama-index-core/llama_index/core/readers/file/base.py b/llama-index-core/llama_index/core/readers/file/base.py index 585b13f723211..a20e7d2b7e5db 100644 --- a/llama-index-core/llama_index/core/readers/file/base.py +++ b/llama-index-core/llama_index/core/readers/file/base.py @@ -56,6 +56,21 @@ def _try_loading_included_file_formats() -> Dict[str, Type[BaseReader]]: return default_file_reader_cls +def _format_file_timestamp(timestamp: float) -> Optional[str]: + """Format file timestamp to a %Y-%m-%d string. + + Args: + timestamp (float): timestamp in float + + Returns: + str: formatted timestamp + """ + try: + return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d") + except Exception: + return None + + def default_file_metadata_func( file_path: str, fs: Optional[fsspec.AbstractFileSystem] = None ) -> Dict: @@ -66,20 +81,10 @@ def default_file_metadata_func( """ fs = fs or get_default_fs() stat_result = fs.stat(file_path) - creation_date = stat_result.get("created") - last_modified_date = stat_result.get("mtime") - last_accessed_date = stat_result.get("atime") - try: - creation_date = datetime.fromtimestamp(creation_date).strftime("%Y-%m-%d") - last_modified_date = datetime.fromtimestamp(last_modified_date).strftime( - "%Y-%m-%d" - ) - last_accessed_date = datetime.fromtimestamp(last_accessed_date).strftime( - "%Y-%m-%d" - ) - except Exception: - pass - return { + creation_date = _format_file_timestamp(stat_result.get("created")) + last_modified_date = _format_file_timestamp(stat_result.get("mtime")) + last_accessed_date = _format_file_timestamp(stat_result.get("atime")) + default_meta = { "file_path": file_path, "file_name": stat_result["name"], "file_type": mimetypes.guess_type(file_path)[0], @@ -89,6 +94,13 @@ def default_file_metadata_func( "last_accessed_date": last_accessed_date, } + # Return not null value + return { + meta_key: meta_value + for meta_key, meta_value in default_meta.items() + if meta_value is not None + } + class _DefaultFileMetadataFunc: """ @@ -237,7 +249,9 @@ def _add_files(self, input_dir: Path) -> List[Path]: # in glob for backwards compatibility. ref = Path(ref) is_dir = self.fs.isdir(ref) - skip_because_hidden = self.exclude_hidden and self.is_hidden(ref) + skip_because_hidden = self.exclude_hidden and self.is_hidden( + ref.relative_to(input_dir.absolute()) + ) skip_because_bad_ext = ( self.required_exts is not None and ref.suffix not in self.required_exts ) diff --git a/llama-index-core/llama_index/core/retrievers/fusion_retriever.py b/llama-index-core/llama_index/core/retrievers/fusion_retriever.py index d5de8373b1252..d1425fcc9a4e6 100644 --- a/llama-index-core/llama_index/core/retrievers/fusion_retriever.py +++ b/llama-index-core/llama_index/core/retrievers/fusion_retriever.py @@ -130,7 +130,11 @@ def _simple_fusion( for nodes_with_scores in results.values(): for node_with_score in nodes_with_scores: text = node_with_score.node.get_content() - all_nodes[text] = node_with_score + if text in all_nodes: + max_score = max(node_with_score.score, all_nodes[text].score) + all_nodes[text].score = max_score + else: + all_nodes[text] = node_with_score return sorted(all_nodes.values(), key=lambda x: x.score or 0.0, reverse=True) diff --git a/llama-index-core/llama_index/core/tools/tool_spec/load_and_search/README.md b/llama-index-core/llama_index/core/tools/tool_spec/load_and_search/README.md index fad3c0db7b175..53f2ce8d9ec42 100644 --- a/llama-index-core/llama_index/core/tools/tool_spec/load_and_search/README.md +++ b/llama-index-core/llama_index/core/tools/tool_spec/load_and_search/README.md @@ -1,5 +1,9 @@ # LoadAndSearch Tool +```bash +pip install llama-index-tools-wikipedia +``` + This Tool Spec is intended to wrap other tools, allowing the Agent to perform separate loading and reading of data. This is very useful for when tools return information larger than or closer to the size of the context window. ## Usage @@ -11,7 +15,7 @@ from llama_index.core.tools.tool_spec.load_and_search import ( LoadAndSearchToolSpec, ) from llama_index.core.agent import OpenAIAgent -from llama_hub.tools.wikipedia.base import WikipediaToolSpec +from llama_index.tools.wikipedia.base import WikipediaToolSpec wiki_spec = WikipediaToolSpec() diff --git a/llama-index-core/llama_index/core/utilities/gemini_utils.py b/llama-index-core/llama_index/core/utilities/gemini_utils.py new file mode 100644 index 0000000000000..3ed4df6c848d7 --- /dev/null +++ b/llama-index-core/llama_index/core/utilities/gemini_utils.py @@ -0,0 +1,52 @@ +"""Global Gemini Utilities (shared between Gemini LLM and Vertex).""" + +from collections.abc import Sequence +from typing import Dict + +from llama_index.core.base.llms.types import ChatMessage, MessageRole + +ROLES_TO_GEMINI: Dict[MessageRole, MessageRole] = { + MessageRole.USER: MessageRole.USER, + MessageRole.ASSISTANT: MessageRole.MODEL, + ## Gemini only has user and model roles. Put the rest in user role. + MessageRole.SYSTEM: MessageRole.USER, +} +ROLES_FROM_GEMINI: Dict[MessageRole, MessageRole] = { + ## Gemini only has user and model roles. + MessageRole.USER: MessageRole.USER, + MessageRole.MODEL: MessageRole.ASSISTANT, +} + + +def merge_neighboring_same_role_messages( + messages: Sequence[ChatMessage], +) -> Sequence[ChatMessage]: + # Gemini does not support multiple messages of the same role in a row, so we merge them + merged_messages = [] + i = 0 + + while i < len(messages): + current_message = messages[i] + # Initialize merged content with current message content + merged_content = [current_message.content] + + # Check if the next message exists and has the same role + while ( + i + 1 < len(messages) + and ROLES_TO_GEMINI[messages[i + 1].role] + == ROLES_TO_GEMINI[current_message.role] + ): + i += 1 + next_message = messages[i] + merged_content.extend([next_message.content]) + + # Create a new ChatMessage or similar object with merged content + merged_message = ChatMessage( + role=ROLES_TO_GEMINI[current_message.role], + content="\n".join([str(msg_content) for msg_content in merged_content]), + additional_kwargs=current_message.additional_kwargs, + ) + merged_messages.append(merged_message) + i += 1 + + return merged_messages diff --git a/llama-index-core/llama_index/core/vector_stores/__init__.py b/llama-index-core/llama_index/core/vector_stores/__init__.py index c6ce1525becd9..713c7f859a1a8 100644 --- a/llama-index-core/llama_index/core/vector_stores/__init__.py +++ b/llama-index-core/llama_index/core/vector_stores/__init__.py @@ -1,6 +1,5 @@ """Vector stores.""" - from llama_index.core.vector_stores.simple import SimpleVectorStore from llama_index.core.vector_stores.types import ( ExactMatchFilter, @@ -8,8 +7,10 @@ FilterOperator, MetadataFilter, MetadataFilters, + MetadataInfo, VectorStoreQuery, VectorStoreQueryResult, + VectorStoreInfo, ) __all__ = [ @@ -17,8 +18,10 @@ "VectorStoreQueryResult", "MetadataFilters", "MetadataFilter", + "MetadataInfo", "ExactMatchFilter", "FilterCondition", "FilterOperator", "SimpleVectorStore", + "VectorStoreInfo", ] diff --git a/llama-index-core/pyproject.toml b/llama-index-core/pyproject.toml index ffa6a94e30e90..766f8c69c9560 100644 --- a/llama-index-core/pyproject.toml +++ b/llama-index-core/pyproject.toml @@ -43,7 +43,7 @@ name = "llama-index-core" packages = [{include = "llama_index"}] readme = "README.md" repository = "https://github.com/run-llama/llama_index" -version = "0.10.14" +version = "0.10.16" [tool.poetry.dependencies] SQLAlchemy = {extras = ["asyncio"], version = ">=1.4.49"} diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/BUILD b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/BUILD new file mode 100644 index 0000000000000..0896ca890d8bf --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/BUILD @@ -0,0 +1,3 @@ +poetry_requirements( + name="poetry", +) diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/Makefile b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/Makefile new file mode 100644 index 0000000000000..b9eab05aa3706 --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/Makefile @@ -0,0 +1,17 @@ +GIT_ROOT ?= $(shell git rev-parse --show-toplevel) + +help: ## Show all Makefile targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' + +format: ## Run code autoformatters (black). + pre-commit install + git ls-files | xargs pre-commit run black --files + +lint: ## Run linters: pre-commit (black, ruff, codespell) and mypy + pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files + +test: ## Run tests via pytest. + pytest tests + +watch-docs: ## Build and watch documentation. + sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/ diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/README.md b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/README.md new file mode 100644 index 0000000000000..4048b24540065 --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/README.md @@ -0,0 +1,18 @@ +# LlamaIndex Callbacks Integration: Langfuse + +[Langfuse](https://langfuse.com/docs) is an open source LLM engineering platform to help teams collaboratively debug, analyze and iterate on their LLM Applications. With the Langfuse integration, you can seamlessly track and monitor performance, traces, and metrics of your LlamaIndex application. Detailed traces of the LlamaIndex context augmentation and the LLM querying processes are captured and can be inspected directly in the Langfuse UI. + +#### Usage Pattern + +```python +from llama_index.core import set_global_handler + +# Make sure you've installed the 'llama-index-callbacks-langfuse' integration package. + +# NOTE: Set your environment variables 'LANGFUSE_SECRET_KEY', 'LANGFUSE_PUBLIC_KEY' and 'LANGFUSE_HOST' +# as shown in your langfuse.com project settings. + +set_global_handler("langfuse") +``` + +![langfuse-tracing](https://static.langfuse.com/llamaindex-langfuse-docs.gif) diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/BUILD b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/__init__.py b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/__init__.py new file mode 100644 index 0000000000000..b060aaf4c8446 --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/__init__.py @@ -0,0 +1,3 @@ +from llama_index.callbacks.langfuse.base import langfuse_callback_handler + +__all__ = ["langfuse_callback_handler"] diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/base.py b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/base.py new file mode 100644 index 0000000000000..381b77ccc69a9 --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/llama_index/callbacks/langfuse/base.py @@ -0,0 +1,11 @@ +from typing import Any + +from llama_index.core.callbacks.base_handler import BaseCallbackHandler + +from langfuse.llama_index import LlamaIndexCallbackHandler + + +def langfuse_callback_handler(**eval_params: Any) -> BaseCallbackHandler: + return LlamaIndexCallbackHandler( + **eval_params, sdk_integration="llama-index_set-global-handler" + ) diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/pyproject.toml b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/pyproject.toml new file mode 100644 index 0000000000000..132031548ecf0 --- /dev/null +++ b/llama-index-integrations/callbacks/llama-index-callbacks-langfuse/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.codespell] +check-filenames = true +check-hidden = true +ignore-words-list = "Gere" +skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb" + +[tool.llamahub] +contains_example = false +import_path = "llama_index.callbacks.langfuse" + +[tool.llamahub.class_authors] +LangfuseCallbackHandler = "llama-index" + +[tool.mypy] +disallow_untyped_defs = true +exclude = ["_static", "build", "examples", "notebooks", "venv"] +ignore_missing_imports = true +python_version = "3.8" + +[tool.poetry] +authors = ["Your Name "] +description = "llama-index callbacks langfuse integration" +exclude = ["**/BUILD"] +license = "MIT" +name = "llama-index-callbacks-langfuse" +readme = "README.md" +version = "0.1.2" + +[tool.poetry.dependencies] +python = ">=3.8.1,<4.0" +llama-index-core = "^0.10.8" +langfuse = "^2.18.0" + +[tool.poetry.group.dev.dependencies] +ipython = "8.10.0" +jupyter = "^1.0.0" +mypy = "0.991" +pre-commit = "3.2.0" +pylint = "2.15.10" +pytest = "7.2.1" +pytest-mock = "3.11.1" +ruff = "0.0.292" +tree-sitter-languages = "^1.8.0" +types-Deprecated = ">=0.1.0" +types-PyYAML = "^6.0.12.12" +types-protobuf = "^4.24.0.4" +types-redis = "4.5.5.0" +types-requests = "2.28.11.8" +types-setuptools = "67.1.0.0" + +[tool.poetry.group.dev.dependencies.black] +extras = ["jupyter"] +version = "<=23.9.1,>=23.7.0" + +[tool.poetry.group.dev.dependencies.codespell] +extras = ["toml"] +version = ">=v2.2.6" + +[[tool.poetry.packages]] +include = "llama_index/" diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/README.md b/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/README.md index f9c12773deaa9..e2f48e4c2338e 100644 --- a/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/README.md +++ b/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/README.md @@ -1,8 +1,8 @@ # LlamaIndex Callbacks Integration: UpTrain -UpTrain is an open-source tool to evaluate and monitor the performance of language models. It provides a set of pre-built evaluations to assess the quality of responses generated by the model. Once you add UpTrainCallbackHandler to your existing LlamaIndex pipeline, it will take care of sending the generated responses to the UpTrain Managed Service for evaluations and display the results in the output. +UpTrain ([github](https://github.com/uptrain-ai/uptrain) || [website](https://uptrain.ai/) || [docs](https://docs.uptrain.ai/getting-started/introduction)) is an open-source platform to evaluate and improve Generative AI applications. It provides grades for 20+ preconfigured checks (covering language, code, embedding use cases), performs root cause analysis on failure cases and gives insights on how to resolve them. Once you add UpTrainCallbackHandler to your existing LlamaIndex pipeline, it will automatically capture the right data, run evaluations and display the results in the output. -Three additional evaluations for Llamaindex have been introduced, complementing existing ones. These evaluations run automatically, with results displayed in the output. More details on UpTrain's evaluations can be found [here](https://github.com/uptrain-ai/uptrain?tab=readme-ov-file#pre-built-evaluations-we-offer-). +More details on UpTrain's evaluations can be found [here](https://github.com/uptrain-ai/uptrain?tab=readme-ov-file#pre-built-evaluations-we-offer-). Selected operators from the LlamaIndex pipeline are highlighted for demonstration: @@ -10,26 +10,26 @@ Selected operators from the LlamaIndex pipeline are highlighted for demonstratio The RAG query engine plays a crucial role in retrieving context and generating responses. To ensure its performance and response quality, we conduct the following evaluations: -- **Context Relevance**: Determines if the context extracted from the query is relevant to the response. -- **Factual Accuracy**: Assesses if the LLM is hallcuinating or providing incorrect information. -- **Response Completeness**: Checks if the response contains all the information requested by the query. +- **[Context Relevance](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-relevance)**: Determines if the context extracted from the query is relevant to the response. +- **[Factual Accuracy](https://docs.uptrain.ai/predefined-evaluations/context-awareness/factual-accuracy)**: Assesses if the LLM is hallucinating or providing incorrect information. +- **[Response Completeness](https://docs.uptrain.ai/predefined-evaluations/response-quality/response-completeness)**: Checks if the response contains all the information requested by the query. ## 2. **Sub-Question Query Generation Evaluation**: -The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using a RAG query engine. Given the complexity, we include the previous evaluations and add: +The SubQuestionQueryGeneration operator decomposes a question into sub-questions, generating responses for each using a RAG query engine. To evaluate the performance of SubQuery module, we add another check as well as run the above three for all the sub-queries: -- **Sub Query Completeness**: Assures that the sub-questions accurately and comprehensively cover the original query. +- **[Sub Query Completeness](https://docs.uptrain.ai/predefined-evaluations/query-quality/sub-query-completeness)**: Assures that the sub-questions accurately and comprehensively cover the original query. ## 3. **Re-Ranking Evaluations**: -Re-ranking involves reordering nodes based on relevance to the query and choosing top n nodes. Different evaluations are performed based on the number of nodes returned after re-ranking. +Re-ranking involves reordering nodes based on relevance to the query and choosing the top n nodes. Different evaluations are performed based on the number of nodes returned after re-ranking. a. Same Number of Nodes -- **Context Reranking**: Checks if the order of re-ranked nodes is more relevant to the query than the original order. +- **[Context Reranking](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-reranking)**: Checks if the order of re-ranked nodes is more relevant to the query than the original order. b. Different Number of Nodes: -- **Context Conciseness**: Examines whether the reduced number of nodes still provides all the required information. +- **[Context Conciseness](https://docs.uptrain.ai/predefined-evaluations/context-awareness/context-conciseness)**: Examines whether the reduced number of nodes still provides all the required information. These evaluations collectively ensure the robustness and effectiveness of the RAG query engine, SubQuestionQueryGeneration operator, and the re-ranking process in the LlamaIndex pipeline. diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/llama_index/callbacks/uptrain/base.py b/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/llama_index/callbacks/uptrain/base.py index 69ab6de8e509b..b842c6de18ea5 100644 --- a/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/llama_index/callbacks/uptrain/base.py +++ b/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/llama_index/callbacks/uptrain/base.py @@ -118,7 +118,7 @@ def uptrain_evaluate( if column == "question": print(f"\nQuestion: {row[column]}") elif column == "response": - print(f"Response: {row[column]}") + print(f"Response: {row[column]}\n") elif column.startswith("score"): if column in score_name_map: print(f"{score_name_map[column]}: {row[column]}") diff --git a/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/pyproject.toml b/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/pyproject.toml index 903e9f86fa358..d8b310ef645c0 100644 --- a/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/pyproject.toml +++ b/llama-index-integrations/callbacks/llama-index-callbacks-uptrain/pyproject.toml @@ -21,18 +21,18 @@ ignore_missing_imports = true python_version = "3.8" [tool.poetry] -authors = ["Your Name "] +authors = ["Dhruv Chawla "] description = "llama-index callbacks uptrain integration" exclude = ["**/BUILD"] license = "MIT" name = "llama-index-callbacks-uptrain" readme = "README.md" -version = "0.1.1" +version = "0.1.2" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" -llama-index-core = "0.10.0" -uptrain = ">=0.5.0" +llama-index-core = ">=0.10.0" +uptrain = ">=0.6.6" [tool.poetry.group.dev.dependencies] ipython = "8.10.0" diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/llama_index/embeddings/bedrock/base.py b/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/llama_index/embeddings/bedrock/base.py index ac5faa6d1c1b2..97262078c3d1b 100644 --- a/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/llama_index/embeddings/bedrock/base.py +++ b/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/llama_index/embeddings/bedrock/base.py @@ -3,7 +3,7 @@ import warnings from enum import Enum from deprecated import deprecated -from typing import Any, Callable, Dict, List, Literal, Optional, Sequence +from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Union from llama_index.core.base.embeddings.base import BaseEmbedding, Embedding from llama_index.core.base.llms.types import ChatMessage @@ -27,10 +27,12 @@ class Models(str, Enum): PROVIDER_SPECIFIC_IDENTIFIERS = { PROVIDERS.AMAZON.value: { - "get_embeddings_func": lambda r: r.get("embedding"), + "get_embeddings_func": lambda r, isbatch: r.get("embedding"), }, PROVIDERS.COHERE.value: { - "get_embeddings_func": lambda r: r.get("embeddings")[0], + "get_embeddings_func": lambda r, isbatch: ( + r.get("embeddings") if isbatch else r.get("embeddings")[0] + ), }, } @@ -317,7 +319,18 @@ def from_credentials( verbose=verbose, ) - def _get_embedding(self, payload: str, type: Literal["text", "query"]) -> Embedding: + def _get_embedding( + self, payload: Union[str, List[str]], type: Literal["text", "query"] + ) -> Union[Embedding, List[Embedding]]: + """Get the embedding for the given payload. + + Args: + payload (Union[str, List[str]]): The text or list of texts for which the embeddings are to be obtained. + type (Literal["text", "query"]): The type of the payload. It can be either "text" or "query". + + Returns: + Union[Embedding, List[Embedding]]: The embedding or list of embeddings for the given payload. If the payload is a list of strings, then the response will be a list of embeddings. + """ if self._client is None: self.set_credentials() @@ -338,7 +351,7 @@ def _get_embedding(self, payload: str, type: Literal["text", "query"]) -> Embedd identifiers = PROVIDER_SPECIFIC_IDENTIFIERS.get(provider, None) if identifiers is None: raise ValueError("Provider not supported") - return identifiers["get_embeddings_func"](resp) + return identifiers["get_embeddings_func"](resp, isinstance(payload, list)) def _get_query_embedding(self, query: str) -> Embedding: return self._get_embedding(query, "query") @@ -346,8 +359,17 @@ def _get_query_embedding(self, query: str) -> Embedding: def _get_text_embedding(self, text: str) -> Embedding: return self._get_embedding(text, "text") + def _get_text_embeddings(self, texts: List[str]) -> List[Embedding]: + provider = self.model.split(".")[0] + if provider == PROVIDERS.COHERE: + return self._get_embedding(texts, "text") + return super()._get_text_embeddings(texts) + def _get_request_body( - self, provider: str, payload: str, type: Literal["text", "query"] + self, + provider: str, + payload: Union[str, List[str]], + input_type: Literal["text", "query"], ) -> Any: """Build the request body as per the provider. Currently supported providers are amazon, cohere. @@ -366,6 +388,8 @@ def _get_request_body( """ if provider == PROVIDERS.AMAZON: + if isinstance(payload, list): + raise ValueError("Amazon provider does not support list of texts") request_body = json.dumps({"inputText": payload}) elif provider == PROVIDERS.COHERE: input_types = { @@ -374,8 +398,8 @@ def _get_request_body( } request_body = json.dumps( { - "texts": [payload], - "input_type": input_types[type], + "texts": [payload] if isinstance(payload, str) else payload, + "input_type": input_types[input_type], "truncate": "NONE", } ) diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/pyproject.toml b/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/pyproject.toml index 3cf9cc1ffe24b..36cb104a8c710 100644 --- a/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/pyproject.toml +++ b/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-embeddings-bedrock" readme = "README.md" -version = "0.1.3" +version = "0.1.4" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/tests/test_bedrock.py b/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/tests/test_bedrock.py index d14df0be8cd87..3876d0d5c9835 100644 --- a/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/tests/test_bedrock.py +++ b/llama-index-integrations/embeddings/llama-index-embeddings-bedrock/tests/test_bedrock.py @@ -73,3 +73,37 @@ def test_get_text_embedding_cohere(self) -> None: self.bedrock_stubber.assert_no_pending_responses() self.assertEqual(embedding, mock_response["embeddings"][0]) + + def test_get_text_embedding_batch_cohere(self) -> None: + mock_response = { + "embeddings": [ + [0.017410278, 0.040924072, -0.007507324, 0.09429932, 0.015304565], + [0.017410278, 0.040924072, -0.007507324, 0.09429932, 0.015304565], + ] + } + mock_request = ["foo bar baz", "foo baz bar"] + + mock_stream = BytesIO(json.dumps(mock_response).encode()) + + self.bedrock_stubber.add_response( + "invoke_model", + { + "contentType": "application/json", + "body": StreamingBody(mock_stream, len(json.dumps(mock_response))), + }, + ) + + bedrock_embedding = BedrockEmbedding( + model=Models.COHERE_EMBED_ENGLISH_V3, + client=self.bedrock_client, + ) + + self.bedrock_stubber.activate() + embedding = bedrock_embedding.get_text_embedding_batch(texts=mock_request) + + self.bedrock_stubber.deactivate() + + self.assertEqual(len(embedding), 2) + + for i in range(2): + self.assertEqual(embedding[i], mock_response["embeddings"][i]) diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/.gitignore b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/.gitignore new file mode 100644 index 0000000000000..990c18de22908 --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/.gitignore @@ -0,0 +1,153 @@ +llama_index/_static +.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +bin/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +etc/ +include/ +lib/ +lib64/ +parts/ +sdist/ +share/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.ruff_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints +notebooks/ + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pyvenv.cfg + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Jetbrains +.idea +modules/ +*.swp + +# VsCode +.vscode + +# pipenv +Pipfile +Pipfile.lock + +# pyright +pyrightconfig.json diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/BUILD b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/BUILD new file mode 100644 index 0000000000000..84f2657a9f879 --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/BUILD @@ -0,0 +1,3 @@ +poetry_requirements( + name="poetry", module_mapping={"google-cloud-aiplatform": ["vertexai"]} +) diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/Makefile b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/Makefile new file mode 100644 index 0000000000000..b9eab05aa3706 --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/Makefile @@ -0,0 +1,17 @@ +GIT_ROOT ?= $(shell git rev-parse --show-toplevel) + +help: ## Show all Makefile targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' + +format: ## Run code autoformatters (black). + pre-commit install + git ls-files | xargs pre-commit run black --files + +lint: ## Run linters: pre-commit (black, ruff, codespell) and mypy + pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files + +test: ## Run tests via pytest. + pytest tests + +watch-docs: ## Build and watch documentation. + sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/ diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/README.md b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/README.md new file mode 100644 index 0000000000000..d625c2907cffb --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/README.md @@ -0,0 +1,14 @@ +# LlamaIndex Embeddings Integration: Vertex + +Implements Vertex AI Embeddings Models: + +| Model | Release Date | +| ------------------------------------ | ----------------- | +| textembedding-gecko@003 | December 12, 2023 | +| textembedding-gecko@002 | November 2, 2023 | +| textembedding-gecko-multilingual@001 | November 2, 2023 | +| textembedding-gecko@001 | June 7, 2023 | +| multimodalembedding | | + +**Note**: Currently Vertex AI does not support async on `multimodalembedding`. +Otherwise, `VertexTextEmbedding` supports async interface. diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/examples/multimodal_embedding.ipynb b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/examples/multimodal_embedding.ipynb new file mode 100644 index 0000000000000..7be7dff32873a --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/examples/multimodal_embedding.ipynb @@ -0,0 +1,202 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3af200b75c4bd924", + "metadata": {}, + "source": [ + "# Vertex AI Multimodal Embedding\n", + "Uses APPLICATION_DEFAULT_CREDENTIALS if no credentials is specified. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b43f20b2f09ff70", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.embeddings.vertex import VertexMultiModalEmbedding\n", + "\n", + "embed_model = VertexMultiModalEmbedding(\n", + " project=\"speedy-atom-413006\", location=\"us-central1\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a10d4efa47801541", + "metadata": {}, + "outputs": [], + "source": [ + "image_url = \"https://upload.wikimedia.org/wikipedia/commons/4/43/Cute_dog.jpg\"" + ] + }, + { + "cell_type": "markdown", + "id": "6e29951621ec9acc", + "metadata": {}, + "source": [ + "Download this image to `data/test-image.jpg`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45aca848dd1d17e3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.core.display import Image\n", + "\n", + "display(Image(url=image_url, width=500))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d3394b6ce654ec4", + "metadata": {}, + "outputs": [], + "source": [ + "result = embed_model.get_image_embedding(\"data/test-image.jpg\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75022fc91552014c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[-0.00822397694,\n", + " 0.0167199261,\n", + " 0.0195552949,\n", + " 0.00935372803,\n", + " 0.00746282,\n", + " 0.011754944,\n", + " -0.0363474153,\n", + " 0.00836938061,\n", + " -0.0170917399,\n", + " 0.0218462963]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[:10]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "103e4c523d039fdd", + "metadata": {}, + "outputs": [], + "source": [ + "text_result = embed_model.get_text_embedding(\n", + " \"a brown and white puppy laying in the grass with purple daisies in the background\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c14b8971981d61e5", + "metadata": {}, + "outputs": [], + "source": [ + "text_result_2 = embed_model.get_text_embedding(\"airplanes in the sky\")" + ] + }, + { + "cell_type": "markdown", + "id": "588f1585ba25bc57", + "metadata": {}, + "source": [ + "We expect that a similar description to the image will yield a higher similarity result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23a129176e8d1007", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.20342717022759096" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "embed_model.similarity(result, text_result)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be0c503c3dd57412", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.009063958097860215" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "embed_model.similarity(result, text_result_2)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/examples/text_embedding.ipynb b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/examples/text_embedding.ipynb new file mode 100644 index 0000000000000..d992449a5258d --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/examples/text_embedding.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8085d744ceb233ff", + "metadata": {}, + "source": [ + "# Vertex AI Text Embedding\n", + "\n", + "Imports the VertexTextEmbedding class and initializes an instance named embed_model with a specified project and location. Uses APPLICATION_DEFAULT_CREDENTIALS if no credentials is specified. The default model is `textembedding-gecko@003` in document retrival mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c52b0b97984c1ceb", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.embeddings.vertex import VertexTextEmbedding\n", + "\n", + "embed_model = VertexTextEmbedding(project=\"speedy-atom-413006\", location=\"us-central1\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61d58ea0808d0941", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'model_name': 'textembedding-gecko@003',\n", + " 'embed_batch_size': 10,\n", + " 'embed_mode': ,\n", + " 'additional_kwargs': {},\n", + " 'class_name': 'VertexTextEmbedding'}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "embed_model.dict()" + ] + }, + { + "cell_type": "markdown", + "id": "c98da813ca018111", + "metadata": {}, + "source": [ + "## Document and Query Retrival" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f6e67d1951da538", + "metadata": {}, + "outputs": [], + "source": [ + "embed_text_result = embed_model.get_text_embedding(\"Hello World!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f61a801502c3de8f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.05736415088176727,\n", + " 0.0049842665903270245,\n", + " -0.07065856456756592,\n", + " -0.021812528371810913,\n", + " 0.060468606650829315]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "embed_text_result[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "416ed8894817e213", + "metadata": {}, + "outputs": [], + "source": [ + "embed_query_result = embed_model.get_query_embedding(\"Hello World!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62510b52e204a271", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.05158292129635811,\n", + " -0.033334773033857346,\n", + " -0.03221268951892853,\n", + " -0.029282240197062492,\n", + " 0.020004423335194588]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "embed_query_result[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d10c0164acddc5d7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.7375430761259468" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from llama_index.core.base.embeddings.base import SimilarityMode\n", + "\n", + "embed_model.similarity(\n", + " embed_text_result, embed_query_result, SimilarityMode.DOT_PRODUCT\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "68292f47908eabad", + "metadata": {}, + "source": [ + "## Using the async interface" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10aa2c79d07d6f77", + "metadata": {}, + "outputs": [], + "source": [ + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()\n", + "\n", + "result = await embed_model.aget_text_embedding(\"Hello World!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "596498385119ecab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.05733369290828705,\n", + " 0.005178301595151424,\n", + " -0.07033716142177582,\n", + " -0.021963153034448624,\n", + " 0.06050697714090347]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[:5]" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/BUILD b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/__init__.py b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/__init__.py new file mode 100644 index 0000000000000..2889c5b5e5d5a --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/__init__.py @@ -0,0 +1,7 @@ +from llama_index.embeddings.vertex.base import ( + VertexTextEmbedding, + VertexMultiModalEmbedding, + VertexEmbeddingMode, +) + +__all__ = ["VertexTextEmbedding", "VertexMultiModalEmbedding", "VertexEmbeddingMode"] diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/base.py b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/base.py new file mode 100644 index 0000000000000..1095758019a3b --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/llama_index/embeddings/vertex/base.py @@ -0,0 +1,228 @@ +from enum import Enum +from typing import Optional, List, Any, Dict, Union + +import vertexai +from llama_index.core.base.embeddings.base import Embedding, BaseEmbedding +from llama_index.core.bridge.pydantic import PrivateAttr, Field +from llama_index.core.callbacks import CallbackManager +from llama_index.core.embeddings import MultiModalEmbedding +from llama_index.core.schema import ImageType +from llama_index.core.base.embeddings.base import DEFAULT_EMBED_BATCH_SIZE +from vertexai.language_models import TextEmbeddingModel, TextEmbeddingInput +from vertexai.vision_models import MultiModalEmbeddingModel, Image + +from google.auth import credentials as auth_credentials + + +class VertexEmbeddingMode(str, Enum): + """VertexAI embedding mode. + + Attributes: + DEFAULT_MODE (str): The default embedding mode, for older models before August 2023, + that does not support task_type + CLASSIFICATION_MODE (str): Optimizes embeddings for classification tasks. + CLUSTERING_MODE (str): Optimizes embeddings for clustering tasks. + SEMANTIC_SIMILARITY_MODE (str): Optimizes embeddings for tasks that require assessments of semantic similarity. + RETRIEVAL_MODE (str): Optimizes embeddings for retrieval tasks, including search and document retrieval. + """ + + DEFAULT_MODE = "default" + CLASSIFICATION_MODE = "classification" + CLUSTERING_MODE = "clustering" + SEMANTIC_SIMILARITY_MODE = "similarity" + RETRIEVAL_MODE = "retrieval" + + +_TEXT_EMBED_TASK_TYPE_MAPPING: Dict[VertexEmbeddingMode, str] = { + VertexEmbeddingMode.CLASSIFICATION_MODE: "CLASSIFICATION", + VertexEmbeddingMode.CLUSTERING_MODE: "CLUSTERING", + VertexEmbeddingMode.SEMANTIC_SIMILARITY_MODE: "SEMANTIC_SIMILARITY", + VertexEmbeddingMode.RETRIEVAL_MODE: "RETRIEVAL_DOCUMENT", +} + +_QUERY_EMBED_TASK_TYPE_MAPPING: Dict[VertexEmbeddingMode, str] = { + VertexEmbeddingMode.CLASSIFICATION_MODE: "CLASSIFICATION", + VertexEmbeddingMode.CLUSTERING_MODE: "CLUSTERING", + VertexEmbeddingMode.SEMANTIC_SIMILARITY_MODE: "SEMANTIC_SIMILARITY", + VertexEmbeddingMode.RETRIEVAL_MODE: "RETRIEVAL_QUERY", +} + + +def init_vertexai( + project: Optional[str] = None, + location: Optional[str] = None, + credentials: Optional[auth_credentials.Credentials] = None, +) -> None: + """Init vertexai. + + Args: + project: The default GCP project to use when making Vertex API calls. + location: The default location to use when making API calls. + credentials: The default custom + credentials to use when making API calls. If not provided credentials + will be ascertained from the environment. + """ + vertexai.init( + project=project, + location=location, + credentials=credentials, + ) + + +def _get_embedding_request( + texts: List[str], embed_mode: VertexEmbeddingMode, is_query: bool +) -> List[Union[str, TextEmbeddingInput]]: + if embed_mode != VertexEmbeddingMode.DEFAULT_MODE: + mapping = ( + _QUERY_EMBED_TASK_TYPE_MAPPING + if is_query + else _TEXT_EMBED_TASK_TYPE_MAPPING + ) + texts = [ + TextEmbeddingInput(text=text, task_type=mapping[embed_mode]) + for text in texts + ] + return texts + + +class VertexTextEmbedding(BaseEmbedding): + embed_mode: VertexEmbeddingMode = Field(description="The embedding mode to use.") + additional_kwargs: Dict[str, Any] = Field( + default_factory=dict, description="Additional kwargs for the Vertex." + ) + + _model: TextEmbeddingModel = PrivateAttr() + + def __init__( + self, + model_name: str = "textembedding-gecko@003", + project: Optional[str] = None, + location: Optional[str] = None, + credentials: Optional[auth_credentials.Credentials] = None, + embed_mode: VertexEmbeddingMode = VertexEmbeddingMode.RETRIEVAL_MODE, + embed_batch_size: int = DEFAULT_EMBED_BATCH_SIZE, + callback_manager: Optional[CallbackManager] = None, + additional_kwargs: Optional[Dict[str, Any]] = None, + ) -> None: + init_vertexai(project=project, location=location, credentials=credentials) + callback_manager = callback_manager or CallbackManager([]) + additional_kwargs = additional_kwargs or {} + + super().__init__( + embed_mode=embed_mode, + additional_kwargs=additional_kwargs, + model_name=model_name, + embed_batch_size=embed_batch_size, + callback_manager=callback_manager, + ) + self._model = TextEmbeddingModel.from_pretrained(model_name) + + @classmethod + def class_name(cls) -> str: + return "VertexTextEmbedding" + + def _get_text_embeddings(self, texts: List[str]) -> List[Embedding]: + texts = _get_embedding_request( + texts=texts, embed_mode=self.embed_mode, is_query=False + ) + embeddings = self._model.get_embeddings(texts, **self.additional_kwargs) + return [embedding.values for embedding in embeddings] + + def _get_text_embedding(self, text: str) -> Embedding: + return self._get_text_embeddings([text])[0] + + async def _aget_text_embedding(self, text: str) -> Embedding: + return (await self._aget_text_embeddings([text]))[0] + + async def _aget_text_embeddings(self, texts: List[str]) -> List[Embedding]: + texts = _get_embedding_request( + texts=texts, embed_mode=self.embed_mode, is_query=False + ) + embeddings = await self._model.get_embeddings_async( + texts, **self.additional_kwargs + ) + return [embedding.values for embedding in embeddings] + + def _get_query_embedding(self, query: str) -> Embedding: + texts = _get_embedding_request( + texts=[query], embed_mode=self.embed_mode, is_query=True + ) + embeddings = self._model.get_embeddings(texts, **self.additional_kwargs) + return embeddings[0].values + + async def _aget_query_embedding(self, query: str) -> Embedding: + texts = _get_embedding_request( + texts=[query], embed_mode=self.embed_mode, is_query=True + ) + embeddings = await self._model.get_embeddings_async( + texts, **self.additional_kwargs + ) + return embeddings[0].values + + +class VertexMultiModalEmbedding(MultiModalEmbedding): + embed_dimension: int = Field(description="The vertex output embedding dimension.") + additional_kwargs: Dict[str, Any] = Field( + default_factory=dict, description="Additional kwargs for the Vertex." + ) + + _model: MultiModalEmbeddingModel = PrivateAttr() + _embed_dimension: int = PrivateAttr() + + def __init__( + self, + model_name: str = "multimodalembedding", + project: Optional[str] = None, + location: Optional[str] = None, + credentials: Optional[Any] = None, + embed_dimension: int = 1408, + embed_batch_size: int = DEFAULT_EMBED_BATCH_SIZE, + callback_manager: Optional[CallbackManager] = None, + additional_kwargs: Optional[Dict[str, Any]] = None, + ) -> None: + init_vertexai(project=project, location=location, credentials=credentials) + callback_manager = callback_manager or CallbackManager([]) + additional_kwargs = additional_kwargs or {} + + super().__init__( + embed_dimension=embed_dimension, + additional_kwargs=additional_kwargs, + model_name=model_name, + embed_batch_size=embed_batch_size, + callback_manager=callback_manager, + ) + self._model = MultiModalEmbeddingModel.from_pretrained(model_name) + + @classmethod + def class_name(cls) -> str: + return "VertexMultiModalEmbedding" + + def _get_text_embedding(self, text: str) -> Embedding: + return self._model.get_embeddings( + contextual_text=text, + dimension=self.embed_dimension, + **self.additional_kwargs + ).text_embedding + + def _get_image_embedding(self, img_file_path: ImageType) -> Embedding: + if isinstance(img_file_path, str): + image = Image.load_from_file(img_file_path) + else: + image = Image(image_bytes=img_file_path.getvalue()) + embeddings = self._model.get_embeddings( + image=image, dimension=self.embed_dimension, **self.additional_kwargs + ) + return embeddings.image_embedding + + def _get_query_embedding(self, query: str) -> Embedding: + return self._get_text_embedding(query) + + # Vertex AI SDK does not support async variants yet + async def _aget_text_embedding(self, text: str) -> Embedding: + return self._get_text_embedding(text) + + async def _aget_image_embedding(self, img_file_path: ImageType) -> Embedding: + return self._get_image_embedding(img_file_path) + + async def _aget_query_embedding(self, query: str) -> Embedding: + return self._get_query_embedding(query) diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/pyproject.toml b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/pyproject.toml new file mode 100644 index 0000000000000..786c029857e87 --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/pyproject.toml @@ -0,0 +1,58 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.codespell] +check-filenames = true +check-hidden = true +# Feel free to un-skip examples, and experimental, you will just need to +# work through many typos (--write-changes and --interactive will help) +skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb" + +[tool.llamahub] +contains_example = true +import_path = "llama_index.embeddings.vertex" + +[tool.llamahub.class_authors] +VertexMultiModalEmbedding = "mustartt" +VertexTextEmbedding = "mustartt" + +[tool.mypy] +disallow_untyped_defs = true +# Remove venv skip when integrated with pre-commit +exclude = ["_static", "build", "examples", "notebooks", "venv"] +ignore_missing_imports = true +python_version = "3.8" + +[tool.poetry] +authors = ["Henry Jiang "] +description = "llama-index embeddings vertex integration" +license = "MIT" +name = "llama-index-embeddings-vertex" +packages = [{include = "llama_index/"}] +readme = "README.md" +version = "0.1.0" + +[tool.poetry.dependencies] +python = ">=3.9,<4.0" +llama-index-core = "^0.10.0" +google-cloud-aiplatform = ">=1.43.0" + +[tool.poetry.group.dev.dependencies] +black = {extras = ["jupyter"], version = "<=23.9.1,>=23.7.0"} +codespell = {extras = ["toml"], version = ">=v2.2.6"} +ipython = "8.10.0" +jupyter = "^1.0.0" +mypy = "0.991" +pre-commit = "3.2.0" +pylint = "2.15.10" +pytest = "7.2.1" +pytest-mock = "3.11.1" +ruff = "0.0.292" +tree-sitter-languages = "^1.8.0" +types-Deprecated = ">=0.1.0" +types-PyYAML = "^6.0.12.12" +types-protobuf = "^4.24.0.4" +types-redis = "4.5.5.0" +types-requests = "2.28.11.8" # TODO: unpin when mypy>0.991 +types-setuptools = "67.1.0.0" diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/BUILD b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/BUILD new file mode 100644 index 0000000000000..619cac15ff840 --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/BUILD @@ -0,0 +1,3 @@ +python_tests( + interpreter_constraints=["==3.9.*", "==3.10.*"], +) diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/__init__.py b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/test_embeddings_vertex.py b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/test_embeddings_vertex.py new file mode 100644 index 0000000000000..2117305a9ff0e --- /dev/null +++ b/llama-index-integrations/embeddings/llama-index-embeddings-vertex/tests/test_embeddings_vertex.py @@ -0,0 +1,254 @@ +import io +import unittest +from unittest.mock import patch, Mock, MagicMock, AsyncMock + +from llama_index.core.base.embeddings.base import BaseEmbedding +from llama_index.core.embeddings import MultiModalEmbedding +from vertexai.language_models import TextEmbedding +from vertexai.vision_models import MultiModalEmbeddingResponse + +from PIL import Image as PillowImage + +from llama_index.embeddings.vertex import ( + VertexTextEmbedding, + VertexMultiModalEmbedding, + VertexEmbeddingMode, +) + + +class VertexTextEmbeddingTest(unittest.TestCase): + @patch("vertexai.init") + @patch("vertexai.language_models.TextEmbeddingModel.from_pretrained") + def test_init(self, model_mock: Mock, mock_init: Mock): + mock_cred = Mock(return_value="mock_credentials_instance") + embedding = VertexTextEmbedding( + model_name="textembedding-gecko@001", + project="test-project", + location="us-test-location", + credentials=mock_cred, + embed_mode=VertexEmbeddingMode.RETRIEVAL_MODE, + embed_batch_size=100, + ) + + mock_init.assert_called_once_with( + project="test-project", + location="us-test-location", + credentials=mock_cred, + ) + + self.assertIsInstance(embedding, BaseEmbedding) + + self.assertEqual(embedding.model_name, "textembedding-gecko@001") + self.assertEqual(embedding.embed_mode, VertexEmbeddingMode.RETRIEVAL_MODE) + self.assertEqual(embedding.embed_batch_size, 100) + + @patch("vertexai.init") + @patch("vertexai.language_models.TextEmbeddingModel.from_pretrained") + def test_get_embedding_retrieval(self, model_mock: Mock, init_mock: Mock): + model = MagicMock() + model_mock.return_value = model + + embedding = VertexTextEmbedding( + project="test-project", + location="us-test-location", + embed_mode=VertexEmbeddingMode.RETRIEVAL_MODE, + additional_kwargs={"auto_truncate": True}, + ) + + model.get_embeddings.return_value = [TextEmbedding(values=[0.1, 0.2, 0.3])] + result = embedding.get_text_embedding("some text") + + model.get_embeddings.assert_called_once() + positional_args, keyword_args = model.get_embeddings.call_args + model.get_embeddings.reset_mock() + + self.assertEqual(len(positional_args[0]), 1) + self.assertEqual(positional_args[0][0].text, "some text") + self.assertEqual(positional_args[0][0].task_type, "RETRIEVAL_DOCUMENT") + self.assertEqual(result, [0.1, 0.2, 0.3]) + self.assertTrue(keyword_args["auto_truncate"]) + + model.get_embeddings.return_value = [TextEmbedding(values=[0.1, 0.2, 0.3])] + result = embedding.get_query_embedding("some query text") + + model.get_embeddings.assert_called_once() + positional_args, keyword_args = model.get_embeddings.call_args + + self.assertEqual(len(positional_args[0]), 1) + self.assertEqual(positional_args[0][0].text, "some query text") + self.assertEqual(positional_args[0][0].task_type, "RETRIEVAL_QUERY") + self.assertEqual(result, [0.1, 0.2, 0.3]) + self.assertTrue(keyword_args["auto_truncate"]) + + +class VertexTextEmbeddingTestAsync(unittest.IsolatedAsyncioTestCase): + @patch("vertexai.init") + @patch("vertexai.language_models.TextEmbeddingModel.from_pretrained") + async def test_get_embedding_retrieval( + self, model_mock: AsyncMock, init_mock: AsyncMock + ): + model = MagicMock() + model.get_embeddings_async = ( + AsyncMock() + ) # Ensure get_embeddings is an AsyncMock for async calls + model_mock.return_value = model + + embedding = VertexTextEmbedding( + project="test-project", + location="us-test-location", + embed_mode=VertexEmbeddingMode.RETRIEVAL_MODE, + additional_kwargs={"auto_truncate": True}, + ) + + model.get_embeddings_async.return_value = [ + TextEmbedding(values=[0.1, 0.2, 0.3]) + ] + result = await embedding.aget_text_embedding("some text") + + model.get_embeddings_async.assert_called_once() + positional_args, keyword_args = model.get_embeddings_async.call_args + model.get_embeddings_async.reset_mock() + + self.assertEqual(len(positional_args[0]), 1) + self.assertEqual(positional_args[0][0].text, "some text") + self.assertEqual(positional_args[0][0].task_type, "RETRIEVAL_DOCUMENT") + self.assertEqual(result, [0.1, 0.2, 0.3]) + self.assertTrue(keyword_args["auto_truncate"]) + + model.get_embeddings_async.return_value = [ + TextEmbedding(values=[0.1, 0.2, 0.3]) + ] + result = await embedding.aget_query_embedding("some query text") + + model.get_embeddings_async.assert_called_once() + positional_args, keyword_args = model.get_embeddings_async.call_args + + self.assertEqual(len(positional_args[0]), 1) + self.assertEqual(positional_args[0][0].text, "some query text") + self.assertEqual(positional_args[0][0].task_type, "RETRIEVAL_QUERY") + self.assertEqual(result, [0.1, 0.2, 0.3]) + self.assertTrue(keyword_args["auto_truncate"]) + + +class VertexMultiModalEmbeddingTest(unittest.TestCase): + @patch("vertexai.init") + @patch("vertexai.vision_models.MultiModalEmbeddingModel.from_pretrained") + def test_init(self, model_mock: Mock, mock_init: Mock): + mock_cred = Mock(return_value="mock_credentials_instance") + embedding = VertexMultiModalEmbedding( + model_name="multimodalembedding", + project="test-project", + location="us-test-location", + credentials=mock_cred, + embed_dimension=1408, + embed_batch_size=100, + ) + + mock_init.assert_called_once_with( + project="test-project", + location="us-test-location", + credentials=mock_cred, + ) + + self.assertIsInstance(embedding, MultiModalEmbedding) + + self.assertEqual(embedding.model_name, "multimodalembedding") + self.assertEqual(embedding.embed_batch_size, 100) + self.assertEqual(embedding.embed_dimension, 1408) + + @patch("vertexai.init") + @patch("vertexai.vision_models.MultiModalEmbeddingModel.from_pretrained") + def test_text_embedding(self, model_mock: Mock, init_mock: Mock): + model = MagicMock() + model_mock.return_value = model + + embedding = VertexMultiModalEmbedding( + project="test-project", + location="us-test-location", + embed_dimension=1408, + additional_kwargs={"additional_kwarg": True}, + ) + + model.get_embeddings.return_value = MultiModalEmbeddingResponse( + _prediction_response=None, text_embedding=[0.1, 0.2, 0.3] + ) + + result = embedding.get_text_embedding("some text") + self.assertEqual(result, [0.1, 0.2, 0.3]) + + model.get_embeddings.assert_called_once() + positional_args, keyword_args = model.get_embeddings.call_args + + self.assertEqual(keyword_args["contextual_text"], "some text") + self.assertEqual(keyword_args["dimension"], 1408) + self.assertTrue(keyword_args["additional_kwarg"]) + + @patch("vertexai.init") + @patch("vertexai.vision_models.Image.load_from_file") + @patch("vertexai.vision_models.MultiModalEmbeddingModel.from_pretrained") + def test_image_embedding_path( + self, model_mock: Mock, load_file_mock: Mock, init_mock: Mock + ): + model = MagicMock() + model_mock.return_value = model + + embedding = VertexMultiModalEmbedding( + project="test-project", + location="us-test-location", + embed_dimension=1408, + additional_kwargs={"additional_kwarg": True}, + ) + + model.get_embeddings.return_value = MultiModalEmbeddingResponse( + _prediction_response=None, image_embedding=[0.1, 0.2, 0.3] + ) + + result = embedding.get_image_embedding("data/test-image.jpg") + self.assertEqual(result, [0.1, 0.2, 0.3]) + + model.get_embeddings.assert_called_once() + positional_args, keyword_args = model.get_embeddings.call_args + + load_file_mock.assert_called_once_with("data/test-image.jpg") + self.assertTrue("image" in keyword_args) + self.assertEqual(keyword_args["dimension"], 1408) + self.assertTrue(keyword_args["additional_kwarg"]) + + @patch("vertexai.init") + @patch("vertexai.vision_models.Image.load_from_file") + @patch("vertexai.vision_models.MultiModalEmbeddingModel.from_pretrained") + def test_image_embedding_bytes( + self, model_mock: Mock, load_file_mock: Mock, init_mock: Mock + ): + model = MagicMock() + model_mock.return_value = model + + embedding = VertexMultiModalEmbedding( + project="test-project", + location="us-test-location", + embed_dimension=1408, + additional_kwargs={"additional_kwarg": True}, + ) + + model.get_embeddings.return_value = MultiModalEmbeddingResponse( + _prediction_response=None, image_embedding=[0.1, 0.2, 0.3] + ) + + image = PillowImage.new("RGB", (128, 128)) + bytes_io = io.BytesIO() + image.save(bytes_io, "jpeg") + bytes_io.seek(0) + + result = embedding.get_image_embedding(bytes_io) + self.assertEqual(result, [0.1, 0.2, 0.3]) + + model.get_embeddings.assert_called_once() + positional_args, keyword_args = model.get_embeddings.call_args + + load_file_mock.assert_not_called() + self.assertEqual(keyword_args["dimension"], 1408) + self.assertTrue(keyword_args["additional_kwarg"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/base.py b/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/base.py index 7fc634cfa05a0..853ba3831faa2 100644 --- a/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/base.py +++ b/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/base.py @@ -1,5 +1,5 @@ from typing import Any, Callable, Dict, Optional, Sequence - +from anthropic.types import ContentBlockDeltaEvent from llama_index.core.base.llms.types import ( ChatMessage, ChatResponse, @@ -28,12 +28,13 @@ from llama_index.core.types import BaseOutputParser, PydanticProgramMode from llama_index.llms.anthropic.utils import ( anthropic_modelname_to_contextsize, - messages_to_anthropic_prompt, + messages_to_anthropic_messages, ) +from llama_index.core.utils import Tokenizer import anthropic -DEFAULT_ANTHROPIC_MODEL = "claude-2" +DEFAULT_ANTHROPIC_MODEL = "claude-2.1" DEFAULT_ANTHROPIC_MAX_TOKENS = 512 @@ -123,12 +124,16 @@ def metadata(self) -> LLMMetadata: model_name=self.model, ) + @property + def tokenizer(self) -> Tokenizer: + return self._client.get_tokenizer() + @property def _model_kwargs(self) -> Dict[str, Any]: base_kwargs = { "model": self.model, "temperature": self.temperature, - "max_tokens_to_sample": self.max_tokens, + "max_tokens": self.max_tokens, } return { **base_kwargs, @@ -143,15 +148,18 @@ def _get_all_kwargs(self, **kwargs: Any) -> Dict[str, Any]: @llm_chat_callback() def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse: - prompt = messages_to_anthropic_prompt(messages) + anthropic_messages, system_prompt = messages_to_anthropic_messages(messages) all_kwargs = self._get_all_kwargs(**kwargs) - response = self._client.completions.create( - prompt=prompt, stream=False, **all_kwargs + response = self._client.messages.create( + messages=anthropic_messages, + stream=False, + system=system_prompt, + **all_kwargs, ) return ChatResponse( message=ChatMessage( - role=MessageRole.ASSISTANT, content=response.completion + role=MessageRole.ASSISTANT, content=response.content[0].text ), raw=dict(response), ) @@ -167,24 +175,25 @@ def complete( def stream_chat( self, messages: Sequence[ChatMessage], **kwargs: Any ) -> ChatResponseGen: - prompt = messages_to_anthropic_prompt(messages) + anthropic_messages, system_prompt = messages_to_anthropic_messages(messages) all_kwargs = self._get_all_kwargs(**kwargs) - response = self._client.completions.create( - prompt=prompt, stream=True, **all_kwargs + response = self._client.messages.create( + messages=anthropic_messages, system=system_prompt, stream=True, **all_kwargs ) def gen() -> ChatResponseGen: content = "" role = MessageRole.ASSISTANT for r in response: - content_delta = r.completion - content += content_delta - yield ChatResponse( - message=ChatMessage(role=role, content=content), - delta=content_delta, - raw=r, - ) + if isinstance(r, ContentBlockDeltaEvent): + content_delta = r.delta.text + content += content_delta + yield ChatResponse( + message=ChatMessage(role=role, content=content), + delta=content_delta, + raw=r, + ) return gen() @@ -199,15 +208,18 @@ def stream_complete( async def achat( self, messages: Sequence[ChatMessage], **kwargs: Any ) -> ChatResponse: - prompt = messages_to_anthropic_prompt(messages) + anthropic_messages, system_prompt = messages_to_anthropic_messages(messages) all_kwargs = self._get_all_kwargs(**kwargs) - response = await self._aclient.completions.create( - prompt=prompt, stream=False, **all_kwargs + response = await self._aclient.messages.create( + messages=anthropic_messages, + system=system_prompt, + stream=False, + **all_kwargs, ) return ChatResponse( message=ChatMessage( - role=MessageRole.ASSISTANT, content=response.completion + role=MessageRole.ASSISTANT, content=response.content[0].text ), raw=dict(response), ) @@ -223,24 +235,25 @@ async def acomplete( async def astream_chat( self, messages: Sequence[ChatMessage], **kwargs: Any ) -> ChatResponseAsyncGen: - prompt = messages_to_anthropic_prompt(messages) + anthropic_messages, system_prompt = messages_to_anthropic_messages(messages) all_kwargs = self._get_all_kwargs(**kwargs) - response = await self._aclient.completions.create( - prompt=prompt, stream=True, **all_kwargs + response = await self._aclient.messages.create( + messages=anthropic_messages, system=system_prompt, stream=True, **all_kwargs ) async def gen() -> ChatResponseAsyncGen: content = "" role = MessageRole.ASSISTANT async for r in response: - content_delta = r.completion - content += content_delta - yield ChatResponse( - message=ChatMessage(role=role, content=content), - delta=content_delta, - raw=r, - ) + if isinstance(r, ContentBlockDeltaEvent): + content_delta = r.delta.text + content += content_delta + yield ChatResponse( + message=ChatMessage(role=role, content=content), + delta=content_delta, + raw=r, + ) return gen() diff --git a/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/utils.py b/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/utils.py index 3cfbd0177d0df..19c1ae5dc7b5d 100644 --- a/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/utils.py +++ b/llama-index-integrations/llms/llama-index-llms-anthropic/llama_index/llms/anthropic/utils.py @@ -1,17 +1,18 @@ -from typing import Dict, Sequence +from typing import Dict, Sequence, Tuple from llama_index.core.base.llms.types import ChatMessage, MessageRole HUMAN_PREFIX = "\n\nHuman:" ASSISTANT_PREFIX = "\n\nAssistant:" - CLAUDE_MODELS: Dict[str, int] = { "claude-instant-1": 100000, "claude-instant-1.2": 100000, "claude-2": 100000, "claude-2.0": 100000, "claude-2.1": 200000, + "claude-3-opus-20240229": 180000, + "claude-3-sonnet-20240229": 180000, } @@ -25,6 +26,21 @@ def anthropic_modelname_to_contextsize(modelname: str) -> int: return CLAUDE_MODELS[modelname] +def messages_to_anthropic_messages( + messages: Sequence[ChatMessage], +) -> Tuple[Sequence[ChatMessage], str]: + anthropic_messages = [] + system_prompt = "" + for message in messages: + if message.role == MessageRole.SYSTEM: + system_prompt = message.content + else: + message = {"role": message.role.value, "content": message.content} + anthropic_messages.append(message) + return anthropic_messages, system_prompt + + +# Function used in bedrock def _message_to_anthropic_prompt(message: ChatMessage) -> str: if message.role == MessageRole.USER: prompt = f"{HUMAN_PREFIX} {message.content}" diff --git a/llama-index-integrations/llms/llama-index-llms-anthropic/pyproject.toml b/llama-index-integrations/llms/llama-index-llms-anthropic/pyproject.toml index 5e9ce75b791bd..543b8789f719d 100644 --- a/llama-index-integrations/llms/llama-index-llms-anthropic/pyproject.toml +++ b/llama-index-integrations/llms/llama-index-llms-anthropic/pyproject.toml @@ -27,12 +27,12 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-llms-anthropic" readme = "README.md" -version = "0.1.3" +version = "0.1.5" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" llama-index-core = "^0.10.1" -anthropic = "^0.11.0" +anthropic = "^0.17.0" [tool.poetry.group.dev.dependencies] ipython = "8.10.0" diff --git a/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/base.py b/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/base.py index a5e6b3b247d59..5bfaa888fa215 100644 --- a/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/base.py +++ b/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/base.py @@ -1,4 +1,5 @@ """Google's hosted Gemini API.""" + import os import typing from typing import Any, Dict, Optional, Sequence @@ -14,17 +15,16 @@ from llama_index.core.bridge.pydantic import Field, PrivateAttr from llama_index.core.callbacks import CallbackManager from llama_index.core.constants import DEFAULT_NUM_OUTPUTS, DEFAULT_TEMPERATURE -from llama_index.core.llms.callbacks import ( - llm_chat_callback, - llm_completion_callback, -) +from llama_index.core.llms.callbacks import llm_chat_callback, llm_completion_callback from llama_index.core.llms.custom import CustomLLM -from llama_index.llms.gemini.utils import ( +from llama_index.core.utilities.gemini_utils import ( ROLES_FROM_GEMINI, + merge_neighboring_same_role_messages, +) +from llama_index.llms.gemini.utils import ( chat_from_gemini_response, chat_message_to_gemini, completion_from_gemini_response, - merge_neighboring_same_role_messages, ) if typing.TYPE_CHECKING: diff --git a/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/utils.py b/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/utils.py index 8bc01a53c076b..d7c5ae0ca9026 100644 --- a/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/utils.py +++ b/llama-index-integrations/llms/llama-index-llms-gemini/llama_index/llms/gemini/utils.py @@ -1,22 +1,15 @@ -from typing import Sequence, Union +from typing import Union import google.ai.generativelanguage as glm import google.generativeai as genai import PIL -from llama_index.core.base.llms.types import MessageRole + from llama_index.core.base.llms.types import ( ChatMessage, ChatResponse, CompletionResponse, ) - -ROLES_TO_GEMINI = { - MessageRole.USER: "user", - MessageRole.ASSISTANT: "model", - ## Gemini only has user and model roles. Put the rest in user role. - MessageRole.SYSTEM: "user", -} -ROLES_FROM_GEMINI = {v: k for k, v in ROLES_TO_GEMINI.items()} +from llama_index.core.utilities.gemini_utils import ROLES_FROM_GEMINI, ROLES_TO_GEMINI def _error_if_finished_early(candidate: "glm.Candidate") -> None: # type: ignore[name-defined] # only until release @@ -79,37 +72,3 @@ def chat_message_to_gemini(message: ChatMessage) -> "genai.types.ContentDict": "role": ROLES_TO_GEMINI[message.role], "parts": parts, } - - -def merge_neighboring_same_role_messages( - messages: Sequence[ChatMessage], -) -> Sequence[ChatMessage]: - # Gemini does not support multiple messages of the same role in a row, so we merge them - merged_messages = [] - i = 0 - - while i < len(messages): - current_message = messages[i] - # Initialize merged content with current message content - merged_content = [current_message.content] - - # Check if the next message exists and has the same role - while ( - i + 1 < len(messages) - and ROLES_TO_GEMINI[messages[i + 1].role] - == ROLES_TO_GEMINI[current_message.role] - ): - i += 1 - next_message = messages[i] - merged_content.extend([next_message.content]) - - # Create a new ChatMessage or similar object with merged content - merged_message = ChatMessage( - role=current_message.role, - content="\n".join([str(msg_content) for msg_content in merged_content]), - additional_kwargs=current_message.additional_kwargs, - ) - merged_messages.append(merged_message) - i += 1 - - return merged_messages diff --git a/llama-index-integrations/llms/llama-index-llms-gemini/pyproject.toml b/llama-index-integrations/llms/llama-index-llms-gemini/pyproject.toml index 7e365aa08d381..9d03b89c826c4 100644 --- a/llama-index-integrations/llms/llama-index-llms-gemini/pyproject.toml +++ b/llama-index-integrations/llms/llama-index-llms-gemini/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-llms-gemini" readme = "README.md" -version = "0.1.4" +version = "0.1.5" [tool.poetry.dependencies] python = ">=3.9,<4.0" diff --git a/llama-index-integrations/llms/llama-index-llms-ollama/llama_index/llms/ollama/base.py b/llama-index-integrations/llms/llama-index-llms-ollama/llama_index/llms/ollama/base.py index 562b0c73429d8..78281eb5918fd 100644 --- a/llama-index-integrations/llms/llama-index-llms-ollama/llama_index/llms/ollama/base.py +++ b/llama-index-integrations/llms/llama-index-llms-ollama/llama_index/llms/ollama/base.py @@ -20,7 +20,7 @@ DEFAULT_REQUEST_TIMEOUT = 30.0 -def get_addtional_kwargs( +def get_additional_kwargs( response: Dict[str, Any], exclude: Tuple[str, ...] ) -> Dict[str, Any]: return {k: v for k, v in response.items() if k not in exclude} @@ -109,12 +109,12 @@ def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse: message=ChatMessage( content=message.get("content"), role=MessageRole(message.get("role")), - additional_kwargs=get_addtional_kwargs( + additional_kwargs=get_additional_kwargs( message, ("content", "role") ), ), raw=raw, - additional_kwargs=get_addtional_kwargs(raw, ("message",)), + additional_kwargs=get_additional_kwargs(raw, ("message",)), ) @llm_chat_callback() @@ -156,13 +156,15 @@ def stream_chat( message=ChatMessage( content=text, role=MessageRole(message.get("role")), - additional_kwargs=get_addtional_kwargs( + additional_kwargs=get_additional_kwargs( message, ("content", "role") ), ), delta=delta, raw=chunk, - additional_kwargs=get_addtional_kwargs(chunk, ("message",)), + additional_kwargs=get_additional_kwargs( + chunk, ("message",) + ), ) @llm_completion_callback() @@ -188,7 +190,7 @@ def complete( return CompletionResponse( text=text, raw=raw, - additional_kwargs=get_addtional_kwargs(raw, ("response",)), + additional_kwargs=get_additional_kwargs(raw, ("response",)), ) @llm_completion_callback() @@ -220,7 +222,7 @@ def stream_complete( delta=delta, text=text, raw=chunk, - additional_kwargs=get_addtional_kwargs( + additional_kwargs=get_additional_kwargs( chunk, ("response",) ), ) diff --git a/llama-index-integrations/llms/llama-index-llms-ollama/tests/test_utils.py b/llama-index-integrations/llms/llama-index-llms-ollama/tests/test_utils.py new file mode 100644 index 0000000000000..0d300048cc47d --- /dev/null +++ b/llama-index-integrations/llms/llama-index-llms-ollama/tests/test_utils.py @@ -0,0 +1,12 @@ +from llama_index.llms.ollama.base import get_additional_kwargs + + +def test_get_additional_kwargs(): + response = {"key1": "value1", "key2": "value2", "exclude_me": "value3"} + exclude = ("exclude_me", "exclude_me_too") + + expected = {"key1": "value1", "key2": "value2"} + + actual = get_additional_kwargs(response, exclude) + + assert actual == expected diff --git a/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml b/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml index 1319956ecd567..1b0333b9b8d8d 100644 --- a/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml +++ b/llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml @@ -23,13 +23,13 @@ ignore_missing_imports = true python_version = "3.8" [tool.poetry] -authors = ["Your Name "] +authors = ["llama-index"] description = "llama-index llms openai integration" exclude = ["**/BUILD"] license = "MIT" name = "llama-index-llms-openai" readme = "README.md" -version = "0.1.6" +version = "0.1.7" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/llms/llama-index-llms-perplexity/llama_index/llms/perplexity/base.py b/llama-index-integrations/llms/llama-index-llms-perplexity/llama_index/llms/perplexity/base.py index e3a574f4d5332..5bab5031a782f 100644 --- a/llama-index-integrations/llms/llama-index-llms-perplexity/llama_index/llms/perplexity/base.py +++ b/llama-index-integrations/llms/llama-index-llms-perplexity/llama_index/llms/perplexity/base.py @@ -109,14 +109,13 @@ def metadata(self) -> LLMMetadata: def _get_context_window(self) -> int: model_context_windows = { + "sonar-small-chat": 16384, + "sonar-small-online": 12000, + "sonar-medium-chat": 16384, + "sonar-medium-online": 12000, "codellama-34b-instruct": 16384, - "llama-2-70b-chat": 4096, - "mistral-7b-instruct": 4096, - "mixtral-8x7b-instruct": 4096, - "pplx-7b-chat": 8192, - "pplx-70b-chat": 4096, - "pplx-7b-online": 4096, - "pplx-70b-online": 4096, + "mistral-7b-instruct": 16384, + "mixtral-8x7b-instruct": 16384, } return model_context_windows.get( self.model, 4096 @@ -124,14 +123,12 @@ def _get_context_window(self) -> int: def _is_chat_model(self) -> bool: chat_models = { - "codellama-34b-instruct", - "llama-2-70b-chat", + "sonar-small-chat", + "sonar-small-online", + "sonar-medium-chat", + "sonar-medium-online" "codellama-34b-instruct", "mistral-7b-instruct", "mixtral-8x7b-instruct", - "pplx-7b-chat", - "pplx-70b-chat", - "pplx-7b-online", - "pplx-70b-online", } return self.model in chat_models diff --git a/llama-index-integrations/llms/llama-index-llms-perplexity/pyproject.toml b/llama-index-integrations/llms/llama-index-llms-perplexity/pyproject.toml index 0f1b3e22527b8..824d8a13122e1 100644 --- a/llama-index-integrations/llms/llama-index-llms-perplexity/pyproject.toml +++ b/llama-index-integrations/llms/llama-index-llms-perplexity/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-llms-perplexity" readme = "README.md" -version = "0.1.2" +version = "0.1.3" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/base.py b/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/base.py index 02971bd1833e9..0b21e82975e30 100644 --- a/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/base.py +++ b/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/base.py @@ -16,6 +16,7 @@ from llama_index.core.llms.callbacks import llm_chat_callback, llm_completion_callback from llama_index.core.llms.llm import LLM from llama_index.core.types import BaseOutputParser, PydanticProgramMode +from llama_index.core.utilities.gemini_utils import merge_neighboring_same_role_messages from llama_index.llms.vertex.gemini_utils import create_gemini_client, is_gemini_model from llama_index.llms.vertex.utils import ( CHAT_MODELS, @@ -130,7 +131,9 @@ def metadata(self) -> LLMMetadata: return LLMMetadata( is_chat_model=self._is_chat_model, model_name=self.model, - system_role=MessageRole.USER, # Vertex does not support the default: MessageRole.SYSTEM + system_role=( + MessageRole.USER if self._is_gemini else MessageRole.SYSTEM + ), # Gemini does not support the default: MessageRole.SYSTEM ) @property @@ -152,8 +155,13 @@ def _get_all_kwargs(self, **kwargs: Any) -> Dict[str, Any]: @llm_chat_callback() def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse: - question = _parse_message(messages[-1], self._is_gemini) - chat_history = _parse_chat_history(messages[:-1], self._is_gemini) + merged_messages = ( + merge_neighboring_same_role_messages(messages) + if self._is_gemini + else messages + ) + question = _parse_message(merged_messages[-1], self._is_gemini) + chat_history = _parse_chat_history(merged_messages[:-1], self._is_gemini) chat_params = {**chat_history} kwargs = kwargs if kwargs else {} @@ -209,8 +217,13 @@ def complete( def stream_chat( self, messages: Sequence[ChatMessage], **kwargs: Any ) -> ChatResponseGen: - question = _parse_message(messages[-1], self._is_gemini) - chat_history = _parse_chat_history(messages[:-1], self._is_gemini) + merged_messages = ( + merge_neighboring_same_role_messages(messages) + if self._is_gemini + else messages + ) + question = _parse_message(merged_messages[-1], self._is_gemini) + chat_history = _parse_chat_history(merged_messages[:-1], self._is_gemini) chat_params = {**chat_history} kwargs = kwargs if kwargs else {} params = {**self._model_kwargs, **kwargs} @@ -283,8 +296,13 @@ def gen() -> CompletionResponseGen: async def achat( self, messages: Sequence[ChatMessage], **kwargs: Any ) -> ChatResponse: - question = _parse_message(messages[-1], self._is_gemini) - chat_history = _parse_chat_history(messages[:-1], self._is_gemini) + merged_messages = ( + merge_neighboring_same_role_messages(messages) + if self._is_gemini + else messages + ) + question = _parse_message(merged_messages[-1], self._is_gemini) + chat_history = _parse_chat_history(merged_messages[:-1], self._is_gemini) chat_params = {**chat_history} kwargs = kwargs if kwargs else {} params = {**self._model_kwargs, **kwargs} diff --git a/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/utils.py b/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/utils.py index c9f7bb2f9e97d..cafdd2977bc0e 100644 --- a/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/utils.py +++ b/llama-index-integrations/llms/llama-index-llms-vertex/llama_index/llms/vertex/utils.py @@ -6,7 +6,6 @@ import google.api_core import vertexai -from llama_index.core.base.llms.types import ChatMessage, MessageRole from tenacity import ( before_sleep_log, retry, @@ -14,12 +13,10 @@ stop_after_attempt, wait_exponential, ) -from vertexai.language_models import ( - ChatMessage as VertexChatMessage, -) -from vertexai.language_models import ( - InputOutputTextPair, -) +from vertexai.language_models import ChatMessage as VertexChatMessage +from vertexai.language_models import InputOutputTextPair + +from llama_index.core.base.llms.types import ChatMessage, MessageRole CHAT_MODELS = ["chat-bison", "chat-bison-32k", "chat-bison@001"] TEXT_MODELS = ["text-bison", "text-bison-32k", "text-bison@001"] @@ -169,9 +166,13 @@ def _parse_chat_history(history: Any, is_gemini: bool) -> Any: for i, message in enumerate(history): if i == 0 and message.role == MessageRole.SYSTEM: if is_gemini: - raise ValueError("Gemini model don't support system messages") + raise ValueError("Gemini model doesn't support system messages") context = message.content - elif message.role == MessageRole.ASSISTANT or message.role == MessageRole.USER: + elif message.role in ( + MessageRole.MODEL, + MessageRole.ASSISTANT, + MessageRole.USER, + ): if is_gemini: from llama_index.llms.vertex.gemini_utils import ( convert_chat_message_to_gemini_content, @@ -185,12 +186,16 @@ def _parse_chat_history(history: Any, is_gemini: bool) -> Any: else: vertex_message = VertexChatMessage( content=message.content, - author="bot" if message.role == MessageRole.ASSISTANT else "user", + author=( + "bot" + if message.role in (MessageRole.ASSISTANT, MessageRole.MODEL) + else MessageRole.USER + ), ) vertex_messages.append(vertex_message) else: raise ValueError( - f"Unexpected message with type {type(message)} at the position {i}." + f"Unexpected message with role {message.role} at the position {i}." ) if len(vertex_messages) % 2 != 0: raise ValueError("total no of messages should be even") diff --git a/llama-index-integrations/llms/llama-index-llms-vertex/pyproject.toml b/llama-index-integrations/llms/llama-index-llms-vertex/pyproject.toml index d1e15c08261df..be582e194b98c 100644 --- a/llama-index-integrations/llms/llama-index-llms-vertex/pyproject.toml +++ b/llama-index-integrations/llms/llama-index-llms-vertex/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-llms-vertex" readme = "README.md" -version = "0.1.3" +version = "0.1.4" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/.gitignore b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/.gitignore new file mode 100644 index 0000000000000..990c18de22908 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/.gitignore @@ -0,0 +1,153 @@ +llama_index/_static +.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +bin/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +etc/ +include/ +lib/ +lib64/ +parts/ +sdist/ +share/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.ruff_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints +notebooks/ + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pyvenv.cfg + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Jetbrains +.idea +modules/ +*.swp + +# VsCode +.vscode + +# pipenv +Pipfile +Pipfile.lock + +# pyright +pyrightconfig.json diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/BUILD b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/BUILD new file mode 100644 index 0000000000000..0896ca890d8bf --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/BUILD @@ -0,0 +1,3 @@ +poetry_requirements( + name="poetry", +) diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/Makefile b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/Makefile new file mode 100644 index 0000000000000..b9eab05aa3706 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/Makefile @@ -0,0 +1,17 @@ +GIT_ROOT ?= $(shell git rev-parse --show-toplevel) + +help: ## Show all Makefile targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' + +format: ## Run code autoformatters (black). + pre-commit install + git ls-files | xargs pre-commit run black --files + +lint: ## Run linters: pre-commit (black, ruff, codespell) and mypy + pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files + +test: ## Run tests via pytest. + pytest tests + +watch-docs: ## Build and watch documentation. + sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/ diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/README.md b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/README.md new file mode 100644 index 0000000000000..2c0efcc4faa28 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/README.md @@ -0,0 +1 @@ +# LlamaIndex Multi-Modal-Llms Integration: Anthropic diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/BUILD b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/__init__.py b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/__init__.py new file mode 100644 index 0000000000000..f6bbe497845f1 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/__init__.py @@ -0,0 +1,3 @@ +from llama_index.multi_modal_llms.anthropic.base import AnthropicMultiModal + +__all__ = ["AnthropicMultiModal"] diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/base.py b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/base.py new file mode 100644 index 0000000000000..d9a3168c3dc9c --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/base.py @@ -0,0 +1,330 @@ +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple + +import httpx +from anthropic.types import ContentBlockDeltaEvent +from llama_index.core.base.llms.types import ( + CompletionResponse, + CompletionResponseAsyncGen, + CompletionResponseGen, + MessageRole, +) +from llama_index.core.bridge.pydantic import Field, PrivateAttr +from llama_index.core.callbacks import CallbackManager +from llama_index.core.constants import ( + DEFAULT_CONTEXT_WINDOW, + DEFAULT_NUM_OUTPUTS, + DEFAULT_TEMPERATURE, +) +from llama_index.core.base.llms.generic_utils import ( + messages_to_prompt as generic_messages_to_prompt, +) +from llama_index.core.multi_modal_llms import ( + MultiModalLLM, + MultiModalLLMMetadata, +) +from llama_index.core.schema import ImageDocument +from llama_index.multi_modal_llms.anthropic.utils import ( + ANTHROPIC_MULTI_MODAL_MODELS, + generate_anthropic_multi_modal_chat_message, + resolve_anthropic_credentials, +) + +from anthropic import Anthropic, AsyncAnthropic + + +class AnthropicMultiModal(MultiModalLLM): + model: str = Field(description="The Multi-Modal model to use from Anthropic.") + temperature: float = Field(description="The temperature to use for sampling.") + max_tokens: Optional[int] = Field( + description=" The maximum numbers of tokens to generate, ignoring the number of tokens in the prompt", + gt=0, + ) + context_window: Optional[int] = Field( + description="The maximum number of context tokens for the model.", + gt=0, + ) + max_retries: int = Field( + default=3, + description="Maximum number of retries.", + gte=0, + ) + timeout: float = Field( + default=60.0, + description="The timeout, in seconds, for API requests.", + gte=0, + ) + api_key: str = Field( + default=None, description="The Anthropic API key.", exclude=True + ) + system_prompt: str = Field(default="", description="System Prompt.") + api_base: str = Field(default=None, description="The base URL for Anthropic API.") + api_version: str = Field(description="The API version for Anthropic API.") + additional_kwargs: Dict[str, Any] = Field( + default_factory=dict, description="Additional kwargs for the Anthropic API." + ) + default_headers: Dict[str, str] = Field( + default=None, description="The default headers for API requests." + ) + + _messages_to_prompt: Callable = PrivateAttr() + _completion_to_prompt: Callable = PrivateAttr() + _client: Anthropic = PrivateAttr() + _aclient: AsyncAnthropic = PrivateAttr() + _http_client: Optional[httpx.Client] = PrivateAttr() + + def __init__( + self, + model: str = "claude-3-opus-20240229", + temperature: float = DEFAULT_TEMPERATURE, + max_tokens: Optional[int] = 300, + additional_kwargs: Optional[Dict[str, Any]] = None, + context_window: Optional[int] = DEFAULT_CONTEXT_WINDOW, + max_retries: int = 3, + timeout: float = 60.0, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + api_version: Optional[str] = None, + messages_to_prompt: Optional[Callable] = None, + completion_to_prompt: Optional[Callable] = None, + callback_manager: Optional[CallbackManager] = None, + default_headers: Optional[Dict[str, str]] = None, + http_client: Optional[httpx.Client] = None, + system_prompt: Optional[str] = "", + **kwargs: Any, + ) -> None: + self._messages_to_prompt = messages_to_prompt or generic_messages_to_prompt + self._completion_to_prompt = completion_to_prompt or (lambda x: x) + api_key, api_base, api_version = resolve_anthropic_credentials( + api_key=api_key, + api_base=api_base, + api_version=api_version, + ) + + super().__init__( + model=model, + temperature=temperature, + max_tokens=max_tokens, + additional_kwargs=additional_kwargs or {}, + context_window=context_window, + max_retries=max_retries, + timeout=timeout, + api_key=api_key, + api_base=api_base, + api_version=api_version, + callback_manager=callback_manager, + default_headers=default_headers, + system_promt=system_prompt, + **kwargs, + ) + self._http_client = http_client + self._client, self._aclient = self._get_clients(**kwargs) + + def _get_clients(self, **kwargs: Any) -> Tuple[Anthropic, AsyncAnthropic]: + client = Anthropic(**self._get_credential_kwargs()) + aclient = AsyncAnthropic(**self._get_credential_kwargs()) + return client, aclient + + @classmethod + def class_name(cls) -> str: + return "anthropic_multi_modal_llm" + + @property + def metadata(self) -> MultiModalLLMMetadata: + """Multi Modal LLM metadata.""" + return MultiModalLLMMetadata( + num_output=self.max_tokens or DEFAULT_NUM_OUTPUTS, + model_name=self.model, + ) + + def _get_credential_kwargs(self, **kwargs: Any) -> Dict[str, Any]: + return { + "api_key": self.api_key, + "base_url": self.api_base, + "max_retries": self.max_retries, + "timeout": self.timeout, + **kwargs, + } + + def _get_multi_modal_chat_messages( + self, + prompt: str, + role: str, + image_documents: Sequence[ImageDocument], + **kwargs: Any, + ) -> List[Dict]: + return generate_anthropic_multi_modal_chat_message( + prompt=prompt, + role=role, + image_documents=image_documents, + ) + + # Model Params for Anthropic Multi Modal model. + def _get_model_kwargs(self, **kwargs: Any) -> Dict[str, Any]: + if self.model not in ANTHROPIC_MULTI_MODAL_MODELS: + raise ValueError( + f"Invalid model {self.model}. " + f"Available models are: {list(ANTHROPIC_MULTI_MODAL_MODELS.keys())}" + ) + base_kwargs = {"model": self.model, "temperature": self.temperature, **kwargs} + if self.max_tokens is not None: + base_kwargs["max_tokens"] = self.max_tokens + return {**base_kwargs, **self.additional_kwargs} + + def _get_response_token_counts(self, raw_response: Any) -> dict: + """Get the token usage reported by the response.""" + if not isinstance(raw_response, dict): + return {} + + usage = raw_response.get("usage", {}) + # NOTE: other model providers that use the Anthropic client may not report usage + if usage is None: + return {} + + return { + "prompt_tokens": usage.get("prompt_tokens", 0), + "completion_tokens": usage.get("completion_tokens", 0), + "total_tokens": usage.get("total_tokens", 0), + } + + def _complete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponse: + all_kwargs = self._get_model_kwargs(**kwargs) + message_dict = self._get_multi_modal_chat_messages( + prompt=prompt, role=MessageRole.USER, image_documents=image_documents + ) + + response = self._client.messages.create( + messages=message_dict, + system=self.system_prompt, + stream=False, + **all_kwargs, + ) + + return CompletionResponse( + text=response.content[0].text, + raw=response, + additional_kwargs=self._get_response_token_counts(response), + ) + + def _stream_complete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponseGen: + all_kwargs = self._get_model_kwargs(**kwargs) + message_dict = self._get_multi_modal_chat_messages( + prompt=prompt, role=MessageRole.USER, image_documents=image_documents + ) + + def gen() -> CompletionResponseGen: + text = "" + + for response in self._client.messages.create( + messages=message_dict, + stream=True, + system=self.system_prompt, + **all_kwargs, + ): + if isinstance(response, ContentBlockDeltaEvent): + # update using deltas + content_delta = response.delta.text or "" + text += content_delta + + yield CompletionResponse( + delta=content_delta, + text=text, + raw=response, + additional_kwargs=self._get_response_token_counts(response), + ) + + return gen() + + def complete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponse: + return self._complete(prompt, image_documents, **kwargs) + + def stream_complete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponseGen: + return self._stream_complete(prompt, image_documents, **kwargs) + + def chat( + self, + **kwargs: Any, + ) -> Any: + raise NotImplementedError("This function is not yet implemented.") + + def stream_chat( + self, + **kwargs: Any, + ) -> Any: + raise NotImplementedError("This function is not yet implemented.") + + # ===== Async Endpoints ===== + + async def _acomplete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponse: + all_kwargs = self._get_model_kwargs(**kwargs) + message_dict = self._get_multi_modal_chat_messages( + prompt=prompt, role=MessageRole.USER, image_documents=image_documents + ) + response = await self._aclient.messages.create( + messages=message_dict, + stream=False, + system=self.system_prompt, + **all_kwargs, + ) + + return CompletionResponse( + text=response.content[0].text, + raw=response, + additional_kwargs=self._get_response_token_counts(response), + ) + + async def acomplete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponse: + return await self._acomplete(prompt, image_documents, **kwargs) + + async def _astream_complete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponseAsyncGen: + all_kwargs = self._get_model_kwargs(**kwargs) + message_dict = self._get_multi_modal_chat_messages( + prompt=prompt, role=MessageRole.USER, image_documents=image_documents + ) + + async def gen() -> CompletionResponseAsyncGen: + text = "" + + async for response in await self._aclient.messages.create( + messages=message_dict, + stream=True, + system=self.system_prompt, + **all_kwargs, + ): + if isinstance(response, ContentBlockDeltaEvent): + # update using deltas + content_delta = response.delta.text or "" + text += content_delta + + yield CompletionResponse( + delta=content_delta, + text=text, + raw=response, + additional_kwargs=self._get_response_token_counts(response), + ) + + return gen() + + async def astream_complete( + self, prompt: str, image_documents: Sequence[ImageDocument], **kwargs: Any + ) -> CompletionResponseAsyncGen: + return await self._astream_complete(prompt, image_documents, **kwargs) + + async def achat(self, **kwargs: Any) -> Any: + raise NotImplementedError("This function is not yet implemented.") + + async def astream_chat(self, **kwargs: Any) -> Any: + raise NotImplementedError("This function is not yet implemented.") diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/utils.py b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/utils.py new file mode 100644 index 0000000000000..8ee491b0795b7 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/llama_index/multi_modal_llms/anthropic/utils.py @@ -0,0 +1,133 @@ +import logging +from typing import Any, Dict, Optional, Sequence, Tuple, List +import base64 +import httpx + +from llama_index.core.multi_modal_llms.generic_utils import encode_image +from llama_index.core.schema import ImageDocument +from llama_index.core.base.llms.generic_utils import get_from_param_or_env + +DEFAULT_ANTHROPIC_API_TYPE = "anthropic_ai" +DEFAULT_ANTHROPIC_API_BASE = "https://api.anthropic.com" +DEFAULT_ANTHROPIC_API_VERSION = "" + + +ANTHROPIC_MULTI_MODAL_MODELS = { + "claude-3-opus-20240229": 180000, + "claude-3-sonnet-20240229": 180000, +} + + +MISSING_API_KEY_ERROR_MESSAGE = """No API key found for Anthropic. +Please set either the ANTHROPIC_API_KEY environment variable \ +API keys can be found or created at \ +https://console.anthropic.com/settings/keys +""" + +logger = logging.getLogger(__name__) + + +def infer_image_mimetype(image_file_path: str) -> str: + # Get the file extension + file_extension = image_file_path.split(".")[-1].lower() + + # Map file extensions to mimetypes + # Claude 3 support the base64 source type for images, and the image/jpeg, image/png, image/gif, and image/webp media types. + # https://docs.anthropic.com/claude/reference/messages_post + if file_extension == "jpg" or file_extension == "jpeg": + return "image/jpeg" + elif file_extension == "png": + return "image/png" + elif file_extension == "gif": + return "image/gif" + elif file_extension == "webp": + return "image/webp" + # Add more mappings for other image types if needed + + # If the file extension is not recognized + return "image/jpeg" + + +def generate_anthropic_multi_modal_chat_message( + prompt: str, + role: str, + image_documents: Optional[Sequence[ImageDocument]] = None, +) -> List[Dict[str, Any]]: + # if image_documents is empty, return text only chat message + if image_documents is None: + return [{"role": role, "content": prompt}] + + # if image_documents is not empty, return text with images chat message + completion_content = [] + for image_document in image_documents: + image_content: Dict[str, Any] = {} + if image_document.image_path and image_document.image_path != "": + mimetype = infer_image_mimetype(image_document.image_path) + base64_image = encode_image(image_document.image_path) + image_content = { + "type": "image", + "source": { + "type": "base64", + "media_type": mimetype, + "data": base64_image, + }, + } + elif ( + "file_path" in image_document.metadata + and image_document.metadata["file_path"] != "" + ): + mimetype = infer_image_mimetype(image_document.metadata["file_path"]) + base64_image = encode_image(image_document.metadata["file_path"]) + image_content = { + "type": "image", + "source": { + "type": "base64", + "media_type": mimetype, + "data": base64_image, + }, + } + elif image_document.image_url and image_document.image_url != "": + mimetype = infer_image_mimetype(image_document.image_url) + image_content = { + "type": "image", + "source": { + "type": "base64", + "media_type": mimetype, + "data": base64.b64encode( + httpx.get(image_document.image_url).content + ).decode("utf-8"), + }, + } + completion_content.append(image_content) + + completion_content.append({"type": "text", "text": prompt}) + + return [{"role": role, "content": completion_content}] + + +def resolve_anthropic_credentials( + api_key: Optional[str] = None, + api_base: Optional[str] = None, + api_version: Optional[str] = None, +) -> Tuple[Optional[str], str, str]: + """ "Resolve Anthropic credentials. + + The order of precedence is: + 1. param + 2. env + 3. anthropic module + 4. default + """ + # resolve from param or env + api_key = get_from_param_or_env("api_key", api_key, "ANTHROPIC_API_KEY", "") + api_base = get_from_param_or_env("api_base", api_base, "ANTHROPIC_API_BASE", "") + api_version = get_from_param_or_env( + "api_version", api_version, "ANTHROPIC_API_VERSION", "" + ) + + # resolve from Anthropic module or default + final_api_key = api_key or "" + final_api_base = api_base or DEFAULT_ANTHROPIC_API_BASE + final_api_version = api_version or DEFAULT_ANTHROPIC_API_VERSION + + return final_api_key, str(final_api_base), final_api_version diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/pyproject.toml b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/pyproject.toml new file mode 100644 index 0000000000000..50bee985a72cb --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/pyproject.toml @@ -0,0 +1,63 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.codespell] +check-filenames = true +check-hidden = true +skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb" + +[tool.llamahub] +contains_example = false +import_path = "llama_index.multi_modal_llms.anthropic" + +[tool.llamahub.class_authors] +AnthropicMultiModal = "llama-index" + +[tool.mypy] +disallow_untyped_defs = true +exclude = ["_static", "build", "examples", "notebooks", "venv"] +ignore_missing_imports = true +python_version = "3.8" + +[tool.poetry] +authors = ["Your Name "] +description = "llama-index multi-modal-llms anthropic integration" +exclude = ["**/BUILD"] +license = "MIT" +name = "llama-index-multi-modal-llms-anthropic" +readme = "README.md" +version = "0.1.2" + +[tool.poetry.dependencies] +python = ">=3.8.1,<4.0" +llama-index-core = "^0.10.1" +anthropic = "0.17.0" + +[tool.poetry.group.dev.dependencies] +ipython = "8.10.0" +jupyter = "^1.0.0" +mypy = "0.991" +pre-commit = "3.2.0" +pylint = "2.15.10" +pytest = "7.2.1" +pytest-mock = "3.11.1" +ruff = "0.0.292" +tree-sitter-languages = "^1.8.0" +types-Deprecated = ">=0.1.0" +types-PyYAML = "^6.0.12.12" +types-protobuf = "^4.24.0.4" +types-redis = "4.5.5.0" +types-requests = "2.28.11.8" +types-setuptools = "67.1.0.0" + +[tool.poetry.group.dev.dependencies.black] +extras = ["jupyter"] +version = "<=23.9.1,>=23.7.0" + +[tool.poetry.group.dev.dependencies.codespell] +extras = ["toml"] +version = ">=v2.2.6" + +[[tool.poetry.packages]] +include = "llama_index/" diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/BUILD b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/BUILD new file mode 100644 index 0000000000000..dabf212d7e716 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/BUILD @@ -0,0 +1 @@ +python_tests() diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/__init__.py b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/test_multi-modal-llms_anthropic.py b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/test_multi-modal-llms_anthropic.py new file mode 100644 index 0000000000000..0d401ff2ffb83 --- /dev/null +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-anthropic/tests/test_multi-modal-llms_anthropic.py @@ -0,0 +1,7 @@ +from llama_index.core.multi_modal_llms.base import MultiModalLLM +from llama_index.multi_modal_llms.anthropic import AnthropicMultiModal + + +def test_embedding_class(): + names_of_base_classes = [b.__name__ for b in AnthropicMultiModal.__mro__] + assert MultiModalLLM.__name__ in names_of_base_classes diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/llama_index/multi_modal_llms/ollama/base.py b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/llama_index/multi_modal_llms/ollama/base.py index a1cd409edc75a..c4e44dca3e769 100644 --- a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/llama_index/multi_modal_llms/ollama/base.py +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/llama_index/multi_modal_llms/ollama/base.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Sequence, Tuple +from typing import Any, Dict, Optional, Sequence, Tuple + +from ollama import Client from llama_index.core.base.llms.types import ( ChatMessage, @@ -10,7 +12,7 @@ CompletionResponseGen, MessageRole, ) -from llama_index.core.bridge.pydantic import Field +from llama_index.core.bridge.pydantic import Field, PrivateAttr from llama_index.core.constants import DEFAULT_CONTEXT_WINDOW, DEFAULT_NUM_OUTPUTS from llama_index.core.multi_modal_llms import ( MultiModalLLM, @@ -48,6 +50,10 @@ def _messages_to_dicts(messages: Sequence[ChatMessage]) -> Sequence[Dict[str, An class OllamaMultiModal(MultiModalLLM): + base_url: str = Field( + default="http://localhost:11434", + description="Base url the model is hosted under.", + ) model: str = Field(description="The MultiModal Ollama model to use.") temperature: float = Field( default=0.75, @@ -60,21 +66,19 @@ class OllamaMultiModal(MultiModalLLM): description="The maximum number of context tokens for the model.", gt=0, ) + request_timeout: Optional[float] = Field( + description="The timeout for making http request to Ollama API server", + ) additional_kwargs: Dict[str, Any] = Field( default_factory=dict, description="Additional model parameters for the Ollama API.", ) + _client: Client = PrivateAttr() def __init__(self, **kwargs: Any) -> None: - """Init params.""" - # make sure that ollama is installed - try: - import ollama # noqa: F401 - except ImportError: - raise ImportError( - "Ollama is not installed. Please install it using `pip install ollama`." - ) + """Init params and ollama client.""" super().__init__(**kwargs) + self._client = Client(host=self.base_url, timeout=self.request_timeout) @classmethod def class_name(cls) -> str: @@ -103,10 +107,8 @@ def _model_kwargs(self) -> Dict[str, Any]: def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse: """Chat.""" - import ollama - ollama_messages = _messages_to_dicts(messages) - response = ollama.chat( + response = self._client.chat( model=self.model, messages=ollama_messages, stream=False, **kwargs ) return ChatResponse( @@ -123,10 +125,8 @@ def stream_chat( self, messages: Sequence[ChatMessage], **kwargs: Any ) -> ChatResponseGen: """Stream chat.""" - import ollama - ollama_messages = _messages_to_dicts(messages) - response = ollama.chat( + response = self._client.chat( model=self.model, messages=ollama_messages, stream=True, **kwargs ) text = "" @@ -157,9 +157,7 @@ def complete( **kwargs: Any, ) -> CompletionResponse: """Complete.""" - import ollama - - response = ollama.generate( + response = self._client.generate( model=self.model, prompt=prompt, images=image_documents_to_base64(image_documents), @@ -181,9 +179,7 @@ def stream_complete( **kwargs: Any, ) -> CompletionResponseGen: """Stream complete.""" - import ollama - - response = ollama.generate( + response = self._client.generate( model=self.model, prompt=prompt, images=image_documents_to_base64(image_documents), diff --git a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/pyproject.toml b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/pyproject.toml index 8100f24534041..65a021d3cc80b 100644 --- a/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/pyproject.toml +++ b/llama-index-integrations/multi_modal_llms/llama-index-multi-modal-llms-ollama/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-multi-modal-llms-ollama" readme = "README.md" -version = "0.1.2" +version = "0.1.3" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/readers/README.md b/llama-index-integrations/readers/README.md index 28086dbb4d088..cd6aa2c93d911 100644 --- a/llama-index-integrations/readers/README.md +++ b/llama-index-integrations/readers/README.md @@ -1,15 +1,16 @@ # Readers (Loaders) -## Reader Usage (Use `download_loader` from LlamaIndex) +Readers can be installed directly as packages: -You can also use the loaders with `download_loader` from LlamaIndex in a single line of code. +```bash +pip install llama-index-readers-google +``` For example, see the code snippets below using the Google Docs Loader. ```python from llama_index.core import VectorStoreIndex, download_loader - -GoogleDocsReader = download_loader("GoogleDocsReader") +from llama_index.readers.google import GoogleDocsReader gdoc_ids = ["1wf-y2pd9C878Oh-FmLH7Q_BQkljdm6TQal-c1pUfrec"] loader = GoogleDocsReader() diff --git a/llama-index-integrations/readers/llama-index-readers-agent-search/README.md b/llama-index-integrations/readers/llama-index-readers-agent-search/README.md index 3d7f9ad282186..d67611bceac0a 100644 --- a/llama-index-integrations/readers/llama-index-readers-agent-search/README.md +++ b/llama-index-integrations/readers/llama-index-readers-agent-search/README.md @@ -1,5 +1,9 @@ # AgentSearch Loader +```bash +pip install llama-index-readers-agent-search +``` + This framework facilitates seamless integration with the AgentSearch dataset or hosted search APIs (e.g. Search Engines) and with RAG-specialized LLM's (e.g. Search Agents). During query-time, the user passes in the query string, search provider (`bing`, `agent-search`), and RAG provider model (`SciPhi/Sensei-7B-V1`). @@ -15,9 +19,7 @@ Here's an example usage of the AgentSearchReader. # import os # os.environ["SCIPHI_API_KEY"] = "..." -from llama_index import download_loader - -AgentSearch = download_loader("AgentSearchReader") +from llama_index.readers.agent_search import AgentSearchReader reader = AgentSearch() diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-cdk/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-cdk/README.md index 534cdab3b27df..bf621cf2a45d4 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-cdk/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-cdk/README.md @@ -1,10 +1,14 @@ # Airbyte CDK Loader +```bash +pip install llama-index-readers-airbyte-cdk +``` + The Airbyte CDK Loader is a shim for sources created using the [Airbyte Python CDK](https://docs.airbyte.com/connector-development/cdk-python/). It allows you to load data from any Airbyte source into LlamaIndex. ## Installation -- Install llama_hub: `pip install llama_hub` +- Install llama-index reader: `pip install llama-index-readers-airbyte-cdk` - Install airbyte-cdk: `pip install airbyte-cdk` - Install a source via git (or implement your own): `pip install git+https://github.com/airbytehq/airbyte.git@master#egg=source_github&subdirectory=airbyte-integrations/connectors/source-github` @@ -15,8 +19,7 @@ Implement and import your own source. You can find lots of resources for how to Here's an example usage of the AirbyteCdkReader. ```python -from llama_index import download_loader -from llama_hub.airbyte_cdk import AirbyteCDKReader +from llama_index.readers.airbyte_cdk import AirbyteCDKReader from source_github.source import ( SourceGithub, ) # this is just an example, you can use any source here - this one is loaded from the Airbyte Github repo via pip install git+https://github.com/airbytehq/airbyte.git@master#egg=source_github&subdirectory=airbyte-integrations/connectors/source-github` diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-gong/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-gong/README.md index 94a7c3f130e30..20b46ee18e075 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-gong/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-gong/README.md @@ -1,18 +1,17 @@ # Airbyte Gong Loader -The Airbyte Gong Loader allows you to access different Gong objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-gong +``` -- Install llama_hub: `pip install llama_hub` -- Install the gong source: `pip install airbyte-source-gong` +The Airbyte Gong Loader allows you to access different Gong objects. ## Usage Here's an example usage of the AirbyteGongReader. ```python -from llama_hub.airbyte_gong import AirbyteGongReader +from llama_index.readers.airbyte_gong import AirbyteGongReader gong_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-hubspot/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-hubspot/README.md index 34fca9a7318d5..29eef93781030 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-hubspot/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-hubspot/README.md @@ -1,18 +1,17 @@ # Airbyte Hubspot Loader -The Airbyte Hubspot Loader allows you to access different Hubspot objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-hubspot +``` -- Install llama_hub: `pip install llama_hub` -- Install the hubspot source: `pip install airbyte-source-hubspot` +The Airbyte Hubspot Loader allows you to access different Hubspot objects. ## Usage Here's an example usage of the AirbyteHubspotReader. ```python -from llama_hub.airbyte_hubspot import AirbyteHubspotReader +from llama_index.readers.airbyte_hubspot import AirbyteHubspotReader hubspot_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-salesforce/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-salesforce/README.md index 375ea08282eea..7b03d4492a998 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-salesforce/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-salesforce/README.md @@ -1,18 +1,17 @@ # Airbyte Salesforce Loader -The Airbyte Salesforce Loader allows you to access different Salesforce objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-salesforce +``` -- Install llama_hub: `pip install llama_hub` -- Install the salesforce source: `pip install airbyte-source-salesforce` +The Airbyte Salesforce Loader allows you to access different Salesforce objects. ## Usage Here's an example usage of the AirbyteSalesforceReader. ```python -from llama_hub.airbyte_salesforce import AirbyteSalesforceReader +from llama_index.readers.airbyte_salesforce import AirbyteSalesforceReader salesforce_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-shopify/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-shopify/README.md index 8802120283d4f..c2c250b5521e9 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-shopify/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-shopify/README.md @@ -1,18 +1,17 @@ # Airbyte Shopify Loader -The Airbyte Shopify Loader allows you to access different Shopify objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-shopify +``` -- Install llama_hub: `pip install llama_hub` -- Install the shopify source: `pip install airbyte-source-shopify` +The Airbyte Shopify Loader allows you to access different Shopify objects. ## Usage Here's an example usage of the AirbyteShopifyReader. ```python -from llama_hub.airbyte_shopify import AirbyteShopifyReader +from llama_index.readers.airbyte_shopify import AirbyteShopifyReader shopify_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-stripe/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-stripe/README.md index 094255a574969..96b9dfe3de793 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-stripe/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-stripe/README.md @@ -1,18 +1,17 @@ # Airbyte Stripe Loader -The Airbyte Stripe Loader allows you to access different Stripe objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-stripe +``` -- Install llama_hub: `pip install llama_hub` -- Install the stripe source: `pip install airbyte-source-stripe` +The Airbyte Stripe Loader allows you to access different Stripe objects. ## Usage Here's an example usage of the AirbyteStripeReader. ```python -from llama_hub.airbyte_stripe import AirbyteStripeReader +from llama_index.readers.airbyte_stripe import AirbyteStripeReader stripe_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-typeform/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-typeform/README.md index bb9338d260ad3..a4f7ee9760142 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-typeform/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-typeform/README.md @@ -1,18 +1,17 @@ # Airbyte Typeform Loader -The Airbyte Typeform Loader allows you to access different Typeform objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-typeform +``` -- Install llama_hub: `pip install llama_hub` -- Install the typeform source: `pip install airbyte-source-typeform` +The Airbyte Typeform Loader allows you to access different Typeform objects. ## Usage Here's an example usage of the AirbyteTypeformReader. ```python -from llama_hub.airbyte_typeform import AirbyteTypeformReader +from llama_index.readers.airbyte_typeform import AirbyteTypeformReader typeform_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airbyte-zendesk-support/README.md b/llama-index-integrations/readers/llama-index-readers-airbyte-zendesk-support/README.md index 8e9359053e2a9..72aa30eb1cce0 100644 --- a/llama-index-integrations/readers/llama-index-readers-airbyte-zendesk-support/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airbyte-zendesk-support/README.md @@ -1,18 +1,19 @@ # Airbyte ZendeskSupport Loader -The Airbyte ZendeskSupport Loader allows you to access different ZendeskSupport objects. - -## Installation +```bash +pip install llama-index-readers-airbyte-zendesk-support +``` -- Install llama_hub: `pip install llama_hub` -- Install the zendesk_support source: `pip install airbyte-source-zendesk-support` +The Airbyte ZendeskSupport Loader allows you to access different ZendeskSupport objects. ## Usage Here's an example usage of the AirbyteZendeskSupportReader. ```python -from llama_hub.airbyte_zendesk_support import AirbyteZendeskSupportReader +from llama_index.readers.airbyte_zendesk_support import ( + AirbyteZendeskSupportReader, +) zendesk_support_config = { # ... diff --git a/llama-index-integrations/readers/llama-index-readers-airtable/README.md b/llama-index-integrations/readers/llama-index-readers-airtable/README.md index 64197e51dbcb4..ab47b7c07de38 100644 --- a/llama-index-integrations/readers/llama-index-readers-airtable/README.md +++ b/llama-index-integrations/readers/llama-index-readers-airtable/README.md @@ -1,5 +1,9 @@ # Airtable Loader +```bash +pip install llama-index-readers-airtable +``` + This loader loads documents from Airtable. The user specifies an API token to initialize the AirtableReader. They then specify a `table_id` and a `base_id` to load in the corresponding Document objects. ## Usage @@ -7,10 +11,9 @@ This loader loads documents from Airtable. The user specifies an API token to in Here's an example usage of the AirtableReader. ```python -from llama_index import download_loader import os -AirtableReader = download_loader("AirtableReader") +from llama_index.readers.airtable import AirtableReader reader = AirtableReader("") documents = reader.load_data(table_id="", base_id="") diff --git a/llama-index-integrations/readers/llama-index-readers-apify/README.md b/llama-index-integrations/readers/llama-index-readers-apify/README.md index e752e540db716..a52ae9787dc5e 100644 --- a/llama-index-integrations/readers/llama-index-readers-apify/README.md +++ b/llama-index-integrations/readers/llama-index-readers-apify/README.md @@ -1,5 +1,9 @@ # Apify Loaders +```bash +pip install llama-index-readers-apify +``` + ## Apify Actor Loader [Apify](https://apify.com/) is a cloud platform for web scraping and data extraction, @@ -20,8 +24,7 @@ To use this loader, you need to have a (free) Apify account and set your [Apify API token](https://console.apify.com/account/integrations) in the code. ```python -from llama_index import download_loader -from llama_index.readers.schema import Document +from llama_index.core import Document # Converts a single record from the Actor's resulting dataset to the LlamaIndex format @@ -34,7 +37,7 @@ def tranform_dataset_item(item): ) -ApifyActor = download_loader("ApifyActor") +from llama_index.readers.apify import ApifyActor reader = ApifyActor("") documents = reader.load_data( @@ -72,8 +75,7 @@ To use this loader, you need to have a (free) Apify account and set your [Apify API token](https://console.apify.com/account/integrations) in the code. ```python -from llama_index import download_loader -from llama_index.readers.schema import Document +from llama_index.core import Document # Converts a single record from the Apify dataset to the LlamaIndex format @@ -86,7 +88,7 @@ def tranform_dataset_item(item): ) -ApifyDataset = download_loader("ApifyDataset") +from llama_index.readers.apify import ApifyDataset reader = ApifyDataset("") documents = reader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-arango-db/README.md b/llama-index-integrations/readers/llama-index-readers-arango-db/README.md index e31489ee1ec9c..d8fef88eb1f98 100644 --- a/llama-index-integrations/readers/llama-index-readers-arango-db/README.md +++ b/llama-index-integrations/readers/llama-index-readers-arango-db/README.md @@ -1,5 +1,9 @@ # LlamaIndex Readers Integration: Arango Db +```bash +pip install llama-index-readers-arango-db +``` + This loader loads documents from ArangoDB. The user specifies a ArangoDB instance to initialize the reader. They then specify the collection name and query params to fetch the relevant docs. @@ -9,10 +13,9 @@ fetch the relevant docs. Here's an example usage of the SimpleArangoDBReader. ```python -from llama_index.core.readers import download_loader import os -SimpleArangoDBReader = download_loader("SimpleArangoDBReader") +from llama_index.readers.arango_db import SimpleArangoDBReader host = "" db_name = "" @@ -32,4 +35,4 @@ documents = reader.load_data( ) ``` -This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. See [here](https://github.com/run-llama/llama-hub/tree/main/llama_hub) for examples. +This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. diff --git a/llama-index-integrations/readers/llama-index-readers-asana/README.md b/llama-index-integrations/readers/llama-index-readers-asana/README.md index 2bd439c33e365..cbfe43f829009 100644 --- a/llama-index-integrations/readers/llama-index-readers-asana/README.md +++ b/llama-index-integrations/readers/llama-index-readers-asana/README.md @@ -1,5 +1,9 @@ # Asana Loader +```bash +pip install llama-index-readers-asana +``` + This loader loads documents from Asana. The user specifies an API token to initialize the AsanaReader. They then specify a `workspace_id` OR a `project_id` to load in the corresponding Document objects. ## Usage @@ -7,10 +11,9 @@ This loader loads documents from Asana. The user specifies an API token to initi Here's an example usage of the AsanaReader. ```python -from llama_index import download_loader import os -AsanaReader = download_loader("AsanaReader") +from llama_index.readers.asana import AsanaReader reader = AsanaReader("") diff --git a/llama-index-integrations/readers/llama-index-readers-assemblyai/README.md b/llama-index-integrations/readers/llama-index-readers-assemblyai/README.md index e0e7d14cfef64..36fdd1c65800e 100644 --- a/llama-index-integrations/readers/llama-index-readers-assemblyai/README.md +++ b/llama-index-integrations/readers/llama-index-readers-assemblyai/README.md @@ -1,5 +1,9 @@ # AssemblyAI Audio Transcript Loader +```bash +pip install llama-index-readers-assemblyai +``` + The AssemblyAI Audio Transcript Loader allows to transcribe audio files with the [AssemblyAI API](https://www.assemblyai.com/) and loads the transcribed text into documents. To use it, you should have the `assemblyai` python package installed, and the environment variable `ASSEMBLYAI_API_KEY` set with your API key. Alternatively, the API key can also be passed as an argument. @@ -10,40 +14,12 @@ More info about AssemblyAI: - [Get a Free API key](https://www.assemblyai.com/dashboard/signup) - [AssemblyAI API Docs](https://www.assemblyai.com/docs) -## Installation - -First, you need to install the `assemblyai` python package. - -You can find more info about it inside the [assemblyai-python-sdk GitHub repo](https://github.com/AssemblyAI/assemblyai-python-sdk). - -```bash -pip install assemblyai -``` - -Optionally: You can install the AssemblyAI integration yourself with: - -```bash -pip install llama-index-readers-assemblyai -``` - -Then you can import it with: - -```python -from llama_index.readers.assemblyai import AssemblyAIAudioTranscriptReader -``` - -As an alternative, you can also use the `download_loader()` to install and use this integration (see next section). - ## Usage The `AssemblyAIAudioTranscriptReader` needs at least the `file_path` argument. Audio files can be specified as an URL or a local file path. ```python -from llama_index.core import download_loader - -AssemblyAIAudioTranscriptReader = download_loader( - "AssemblyAIAudioTranscriptReader" -) +from llama_index.readers.assemblyai import AssemblyAIAudioTranscriptReader audio_file = "https://storage.googleapis.com/aai-docs-samples/nbc.mp3" # or a local file path: audio_file = "./nbc.mp3" diff --git a/llama-index-integrations/readers/llama-index-readers-astra-db/README.md b/llama-index-integrations/readers/llama-index-readers-astra-db/README.md index bd7b4e7cdeff2..eda6a64ad4ef4 100644 --- a/llama-index-integrations/readers/llama-index-readers-astra-db/README.md +++ b/llama-index-integrations/readers/llama-index-readers-astra-db/README.md @@ -1,5 +1,9 @@ # Astra DB Loader +```bash +pip install llama-index-readers-astra-db +``` + The Astra DB Loader returns a set of documents retrieved from Astra DB. The user initializes the loader with an Astra DB index. They then pass in a vector. @@ -10,8 +14,6 @@ Here's an example usage of the AstraDBReader. ```python from openai import OpenAI -from llama_index import download_loader - # Get the credentials for Astra DB api_endpoint = "https://324<...>f1c.astra.datastax.com" @@ -29,7 +31,7 @@ response = client.embeddings.create( query_vector = response.data[0].embedding # Initialize the Reader object -AstraDBReader = download_loader("AstraDBReader") +from llama_index.readers.astra_db import AstraDBReader # Your Astra DB Account will provide you with the endpoint URL and Token reader = AstraDBReader( diff --git a/llama-index-integrations/readers/llama-index-readers-athena/README.md b/llama-index-integrations/readers/llama-index-readers-athena/README.md index afff8d84f5c64..082e2ca83c151 100644 --- a/llama-index-integrations/readers/llama-index-readers-athena/README.md +++ b/llama-index-integrations/readers/llama-index-readers-athena/README.md @@ -1,5 +1,11 @@ # Athena reader. +```bash +pip install llama-index-readers-athena + +pip install llama-index-llms-openai +``` + Athena reader allow execute SQL with AWS Athena. We using SQLAlchemy and PyAthena under the hood. ## Permissions @@ -13,10 +19,10 @@ Here's an example usage of the AthenaReader. ``` import os import dotenv -from llama_index import SQLDatabase,ServiceContext -from llama_index.indices.struct_store import NLSQLTableQueryEngine -from llama_index.llms import OpenAI -from llama_hub.athena import AthenaReader +from llama_index.core import SQLDatabase,ServiceContext +from llama_index.core.query_engine import NLSQLTableQueryEngine +from llama_index.llms.openai import OpenAI +from llama_index.readers.athena import AthenaReader dotenv.load_dotenv() diff --git a/llama-index-integrations/readers/llama-index-readers-azcognitive-search/README.md b/llama-index-integrations/readers/llama-index-readers-azcognitive-search/README.md index 953bef038c961..9891527c4abde 100644 --- a/llama-index-integrations/readers/llama-index-readers-azcognitive-search/README.md +++ b/llama-index-integrations/readers/llama-index-readers-azcognitive-search/README.md @@ -1,5 +1,9 @@ # Azure Cognitive Search Loader +```bash +pip install llama-index-readers-azcognitive-search +``` + The AzCognitiveSearchReader Loader returns a set of texts corresponding to documents retrieved from specific index of Azure Cognitive Search. The user initializes the loader with credentials (service name and key) and the index name. @@ -8,9 +12,7 @@ The user initializes the loader with credentials (service name and key) and the Here's an example usage of the AzCognitiveSearchReader. ```python -from llama_index import download_loader - -AzCognitiveSearchReader = download_loader("AzCognitiveSearchReader") +from llama_index.readers.azcognitive_search import AzCognitiveSearchReader reader = AzCognitiveSearchReader( "", @@ -30,11 +32,11 @@ documents = reader.load_data( ## Usage in combination with langchain ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.chains.conversation.memory import ConversationBufferMemory from langchain.agents import Tool, AgentExecutor, load_tools, initialize_agent -AzCognitiveSearchReader = download_loader("AzCognitiveSearchReader") +from llama_index.readers.azcognitive_search import AzCognitiveSearchReader az_loader = AzCognitiveSearchReader( COGNITIVE_SEARCH_SERVICE_NAME, COGNITIVE_SEARCH_KEY, INDEX_NAME diff --git a/llama-index-integrations/readers/llama-index-readers-azstorage-blob/README.md b/llama-index-integrations/readers/llama-index-readers-azstorage-blob/README.md index 306aec783686a..e4c1adcbfb2c1 100644 --- a/llama-index-integrations/readers/llama-index-readers-azstorage-blob/README.md +++ b/llama-index-integrations/readers/llama-index-readers-azstorage-blob/README.md @@ -1,5 +1,9 @@ # Azure Storage Blob Loader +```bash +pip install llama-index-readers-azstorage-blob +``` + This loader parses any file stored as an Azure Storage blob or the entire container (with an optional prefix / attribute filter) if no particular file is specified. When initializing `AzStorageBlobReader`, you may pass in your account url with a SAS token or crdentials to authenticate. All files are temporarily downloaded locally and subsequently parsed with `SimpleDirectoryReader`. Hence, you may also specify a custom `file_extractor`, relying on any of the loaders in this library (or your own)! If you need a clue on finding the file extractor object because you'd like to use your own file extractor, follow this sample. @@ -20,9 +24,7 @@ To use this loader, you need to pass in the name of your Azure Storage Container ### Using a Storage Account SAS URL ```python -from llama_index import download_loader - -AzStorageBlobReader = download_loader("AzStorageBlobReader") +from llama_index.readers.azstorage_blob import AzStorageBlobReader loader = AzStorageBlobReader( container="scrabble-dictionary", @@ -38,9 +40,7 @@ documents = loader.load_data() The sample below will download all files in a container, by only specifying the storage account's connection string and the container name. ```python -from llama_index import download_loader - -AzStorageBlobReader = download_loader("AzStorageBlobReader") +from llama_index.readers.azstorage_blob import AzStorageBlobReader loader = AzStorageBlobReader( container_name="", @@ -57,12 +57,11 @@ Ensure the Azure Identity library is available `pip install azure-identity` The sample below downloads all files in the container using the default credential, alternative credential options are available such as a service principal `ClientSecretCredential` ```python -from llama_index import download_loader from azure.identity import DefaultAzureCredential default_credential = DefaultAzureCredential() -AzStorageBlobReader = download_loader("AzStorageBlobReader") +from llama_index.readers.azstorage_blob import AzStorageBlobReader loader = AzStorageBlobReader( container_name="scrabble-dictionary", diff --git a/llama-index-integrations/readers/llama-index-readers-bilibili/README.md b/llama-index-integrations/readers/llama-index-readers-bilibili/README.md index 36bc3b949e0fd..66e93880f7716 100644 --- a/llama-index-integrations/readers/llama-index-readers-bilibili/README.md +++ b/llama-index-integrations/readers/llama-index-readers-bilibili/README.md @@ -1,5 +1,9 @@ # Bilibili Transcript Loader +```bash +pip install llama-index-readers-bilibili +``` + This loader utilizes the `bilibili_api` to fetch the text transcript from Bilibili, one of the most beloved long-form video sites in China. With this BilibiliTranscriptReader, users can easily obtain the transcript of their desired video content on the platform. @@ -9,9 +13,8 @@ With this BilibiliTranscriptReader, users can easily obtain the transcript of th To use this loader, you need to pass in an array of Bilibili video links. ```python -from llama_index import download_loader +from llama_index.readers.bilibili import BilibiliTranscriptReader -BilibiliTranscriptReader = download_loader("BilibiliTranscriptReader") loader = BilibiliTranscriptReader() documents = loader.load_data( video_urls=["https://www.bilibili.com/video/BV1yx411L73B/"] diff --git a/llama-index-integrations/readers/llama-index-readers-bitbucket/README.md b/llama-index-integrations/readers/llama-index-readers-bitbucket/README.md index a5f4c506593c0..2edc08dadefc4 100644 --- a/llama-index-integrations/readers/llama-index-readers-bitbucket/README.md +++ b/llama-index-integrations/readers/llama-index-readers-bitbucket/README.md @@ -1,5 +1,9 @@ # Bitbucket Loader +```bash +pip install llama-index-readers-bitbucket +``` + This loader utilizes the Bitbucket API to load the files inside a Bitbucket repository as Documents in an index. ## Usage @@ -8,7 +12,7 @@ To use this loader, you need to provide as environment variables the `BITBUCKET_ ```python import os -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader os.environ["BITBUCKET_USERNAME"] = "myusername" os.environ["BITBUCKET_API_KEY"] = "myapikey" @@ -16,7 +20,7 @@ os.environ["BITBUCKET_API_KEY"] = "myapikey" base_url = "https://myserver/bitbucket" project_key = "mykey" -BitbucketReader = download_loader("BitbucketReader") +from llama_index.readers.bitbucket import BitbucketReader loader = BitbucketReader( base_url=base_url, diff --git a/llama-index-integrations/readers/llama-index-readers-boarddocs/README.md b/llama-index-integrations/readers/llama-index-readers-boarddocs/README.md index d10393b5c8722..ce037d0cc14b1 100644 --- a/llama-index-integrations/readers/llama-index-readers-boarddocs/README.md +++ b/llama-index-integrations/readers/llama-index-readers-boarddocs/README.md @@ -1,5 +1,9 @@ # BoardDocs Loader +```bash +pip install llama-index-readers-boarddocs +``` + This loader retrieves an agenda and associated material from a BoardDocs site. This loader is not endorsed by, developed by, supported by, or in any way formally affiliated with Diligent Corporation. @@ -10,9 +14,7 @@ To use this loader, you'll need to specify which BoardDocs site you want to load as well as the committee on the site you want to scrape. ```python -from llama_index import download_loader - -BoardDocsReader = download_loader("BoardDocsReader") +from llama_index.readers.boarddocs import BoardDocsReader # For a site URL https://go.boarddocs.com/ca/redwood/Board.nsf/Public # your site should be set to 'ca/redwood' diff --git a/llama-index-integrations/readers/llama-index-readers-confluence/README.md b/llama-index-integrations/readers/llama-index-readers-confluence/README.md index e1cf202d4a724..4b55e6c1d6d52 100644 --- a/llama-index-integrations/readers/llama-index-readers-confluence/README.md +++ b/llama-index-integrations/readers/llama-index-readers-confluence/README.md @@ -1,5 +1,9 @@ # Confluence Loader +```bash +pip install llama-index-readers-confluence +``` + This loader loads pages from a given Confluence cloud instance. The user needs to specify the base URL for a Confluence instance to initialize the ConfluenceReader - base URL needs to end with `/wiki`. The user can optionally specify OAuth 2.0 credentials to authenticate with the Confluence instance. If no credentials are specified, the loader will @@ -42,7 +46,7 @@ Here's an example usage of the ConfluenceReader. ```python # Example that reads the pages with the `page_ids` -from llama_hub.confluence import ConfluenceReader +from llama_index.readers.confluence import ConfluenceReader token = {"access_token": "", "token_type": ""} oauth2_dict = {"client_id": "", "token": token} @@ -65,7 +69,7 @@ documents.extend( ```python # Example that fetches the first 5, then the next 5 pages from a space -from llama_hub.confluence import ConfluenceReader +from llama_index.readers.confluence import ConfluenceReader token = {"access_token": "", "token_type": ""} oauth2_dict = {"client_id": "", "token": token} @@ -95,7 +99,7 @@ documents.extend( ```python # Example that fetches the first 5 results froma cql query, the uses the cursor to pick up on the next element -from llama_hub.confluence import ConfluenceReader +from llama_index.readers.confluence import ConfluenceReader token = {"access_token": "", "token_type": ""} oauth2_dict = {"client_id": "", "token": token} diff --git a/llama-index-integrations/readers/llama-index-readers-couchbase/README.md b/llama-index-integrations/readers/llama-index-readers-couchbase/README.md index 574ba00b2a61b..f30013bf7a3ff 100644 --- a/llama-index-integrations/readers/llama-index-readers-couchbase/README.md +++ b/llama-index-integrations/readers/llama-index-readers-couchbase/README.md @@ -1,5 +1,9 @@ # LlamaIndex Readers Integration: Couchbase +```bash +pip install llama-index-readers-couchbase +``` + This loader loads documents from Couchbase cluster. The user specifies a Couchbase client or credentials to initialize the reader. They can specify the SQL++ query to fetch the relevant docs. @@ -9,10 +13,9 @@ fetch the relevant docs. Here's an example usage of the CouchbaseReader. ```python -from llama_index.core.readers import download_loader import os -CouchbaseLoader = download_loader("CouchbaseReader") +from llama_index.readers.couchbase import CouchbaseReader connection_string = ( "couchbase://localhost" # valid Couchbase connection string diff --git a/llama-index-integrations/readers/llama-index-readers-couchdb/README.md b/llama-index-integrations/readers/llama-index-readers-couchdb/README.md index d738eb39b9ef9..cd3b51286c6c8 100644 --- a/llama-index-integrations/readers/llama-index-readers-couchdb/README.md +++ b/llama-index-integrations/readers/llama-index-readers-couchdb/README.md @@ -1,5 +1,9 @@ # CouchDB Loader +```bash +pip install llama-index-readers-couchdb +``` + This loader loads documents from CouchDB. The loader currently supports CouchDB 3.x using the CouchDB3 python wrapper from https://github.com/n-vlahovic/couchdb3 The user specifies a CouchDB instance to initialize the reader. They then specify @@ -10,10 +14,9 @@ the database name and query params to fetch the relevant docs. Here's an example usage of the SimpleCouchDBReader. ```python -from llama_index import download_loader import os -SimpleCouchDBReader = download_loader("SimpleCouchDBReader") +from llama_index.readers.couchdb import SimpleCouchDBReader host = "" port = "" diff --git a/llama-index-integrations/readers/llama-index-readers-dad-jokes/README.md b/llama-index-integrations/readers/llama-index-readers-dad-jokes/README.md index f786ca3b513a3..6e07eeffcbda4 100644 --- a/llama-index-integrations/readers/llama-index-readers-dad-jokes/README.md +++ b/llama-index-integrations/readers/llama-index-readers-dad-jokes/README.md @@ -1,5 +1,9 @@ # DadJoke Loader +```bash +pip install llama-index-readers-dad-jokes +``` + This loader fetches a joke from icanhazdadjoke. ## Usage @@ -7,9 +11,7 @@ This loader fetches a joke from icanhazdadjoke. To use this loader, load it. ```python -from llama_index import download_loader - -DadJokesReader = download_loader("DadJokesReader") +from llama_index.readers.dad_jokes import DadJokesReader loader = DadJokesReader() documents = loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-discord/README.md b/llama-index-integrations/readers/llama-index-readers-discord/README.md index 522c28c3a63f1..c925cf9539d2a 100644 --- a/llama-index-integrations/readers/llama-index-readers-discord/README.md +++ b/llama-index-integrations/readers/llama-index-readers-discord/README.md @@ -1,5 +1,9 @@ # Discord Loader +```bash +pip install llama-index-readers-discord +``` + This loader loads conversations from Discord. The user specifies `channel_ids` and we fetch conversations from those `channel_ids`. @@ -8,10 +12,9 @@ those `channel_ids`. Here's an example usage of the DiscordReader. ```python -from llama_index import download_loader import os -DiscordReader = download_loader("DiscordReader") +from llama_index.readers.discord import DiscordReader discord_token = os.getenv("DISCORD_TOKEN") channel_ids = [1057178784895348746] # Replace with your channel_id diff --git a/llama-index-integrations/readers/llama-index-readers-docugami/README.md b/llama-index-integrations/readers/llama-index-readers-docugami/README.md index 764a6b603800d..e31a22c9eef45 100644 --- a/llama-index-integrations/readers/llama-index-readers-docugami/README.md +++ b/llama-index-integrations/readers/llama-index-readers-docugami/README.md @@ -1,5 +1,9 @@ # Docugami Loader +```bash +pip install llama-index-readers-docugami +``` + This loader takes in IDs of PDF, DOCX or DOC files processed by [Docugami](https://docugami.com) and returns nodes in a Document XML Knowledge Graph for each document. This is a rich representation that includes the semantic and structural characteristics of various chunks in the document as an XML tree. Entire sets of documents are processed, resulting in forests of XML semantic trees. ## Pre-requisites @@ -14,9 +18,7 @@ This loader takes in IDs of PDF, DOCX or DOC files processed by [Docugami](https To use this loader, you simply need to pass in a Docugami Doc Set ID, and optionally an array of Document IDs (by default, all documents in the Doc Set are loaded). ```python -from llama_index.core import download_loader - -DocugamiReader = download_loader("DocugamiReader") +from llama_index.readers.docugami import DocugamiReader docset_id = "tjwrr2ekqkc3" document_ids = ["ui7pkriyckwi", "1be3o7ch10iy"] diff --git a/llama-index-integrations/readers/llama-index-readers-earnings-call-transcript/README.md b/llama-index-integrations/readers/llama-index-readers-earnings-call-transcript/README.md index 1eb7b9f24708f..bd5e23b64f52d 100644 --- a/llama-index-integrations/readers/llama-index-readers-earnings-call-transcript/README.md +++ b/llama-index-integrations/readers/llama-index-readers-earnings-call-transcript/README.md @@ -1,5 +1,9 @@ # EARNING CALL TRANSCRIPTS LOADER +```bash +pip install llama-index-readers-earnings-call-transcript +``` + This loader fetches the earning call transcripts of US based companies from the website [discountingcashflows.com](https://discountingcashflows.com/). It is not available for commercial purposes Install the required dependencies @@ -17,9 +21,7 @@ The Earning call transcripts takes in three arguments ## Usage ```python -from llama_index import download_loader - -EarningsCallTranscript = download_loader("EarningsCallTranscript") +from llama_index.readers.earnings_call_transcript import EarningsCallTranscript loader = EarningsCallTranscript(2023, "AAPL", "Q3") docs = loader.load_data() @@ -37,10 +39,9 @@ The metadata of the transcripts are the following #### Llama Index ```python -from llama_index import download_loader -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -EarningsCallTranscript = download_loader("EarningsCallTranscript") +from llama_index.readers.earnings_call_transcript import EarningsCallTranscript loader = EarningsCallTranscript(2023, "AAPL", "Q3") docs = loader.load_data() @@ -57,13 +58,12 @@ print(response) #### Langchain ```python -from llama_index import download_loader from langchain.agents import Tool from langchain.agents import initialize_agent from langchain.chat_models import ChatOpenAI from langchain.llms import OpenAI -EarningsCallTranscript = download_loader("EarningsCallTranscript") +from llama_index.readers.earnings_call_transcript import EarningsCallTranscript loader = EarningsCallTranscript(2023, "AAPL", "Q3") docs = loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-feedly-rss/README.md b/llama-index-integrations/readers/llama-index-readers-feedly-rss/README.md index fa201e53b933f..3072a55fe6b9c 100644 --- a/llama-index-integrations/readers/llama-index-readers-feedly-rss/README.md +++ b/llama-index-integrations/readers/llama-index-readers-feedly-rss/README.md @@ -1,13 +1,15 @@ # Feedly Loader +```bash +pip install llama-index-readers-feedly-rss +``` + This loader fetches the entries from a list of RSS feeds subscribed in [Feedly](https://feedly.com). You must initialize the loader with your [Feedly API token](https://developer.feedly.com), and then pass the category name which you want to extract. ## Usage ```python -from llama_index import download_loader - -feedlyRssReader = download_loader("FeedlyRssReader") +from llama_index.readers.feedly_rss import FeedlyRssReader loader = feedlyRssReader(bearer_token="[YOUR_TOKEN]") documents = loader.load_data(category_name="news", max_count=100) diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-docs/README.md b/llama-index-integrations/readers/llama-index-readers-feishu-docs/README.md index b28f90cfd6302..2c378943de1ee 100644 --- a/llama-index-integrations/readers/llama-index-readers-feishu-docs/README.md +++ b/llama-index-integrations/readers/llama-index-readers-feishu-docs/README.md @@ -1,5 +1,9 @@ # Feishu Doc Loader +```bash +pip install llama-index-readers-feishu-docs +``` + This loader takes in IDs of Feishu Docs and parses their text into `documents`. You can extract a Feishu Doc's ID directly from its URL. For example, the ID of `https://test-csl481dfkgqf.feishu.cn/docx/HIH2dHv21ox9kVxjRuwc1W0jnkf` is `HIH2dHv21ox9kVxjRuwc1W0jnkf`. As a prerequisite, you will need to register with Feishu and build an custom app. See [here](https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process) for instructions. ## Usage @@ -7,12 +11,11 @@ This loader takes in IDs of Feishu Docs and parses their text into `documents`. To use this loader, you simply need to pass in an array of Feishu Doc IDs. The default API endpoints are for Feishu, in order to switch to Lark, we should use `set_lark_domain`. ```python -from llama_index import download_loader - app_id = "cli_slkdjalasdkjasd" app_secret = "dskLLdkasdjlasdKK" doc_ids = ["HIH2dHv21ox9kVxjRuwc1W0jnkf"] -FeishuDocsReader = download_loader("FeishuDocsReader") +from llama_index.readers.feishu_docs import FeishuDocsReader + loader = FeishuDocsReader(app_id, app_secret) documents = loader.load_data(document_ids=doc_ids) ``` diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/.gitignore b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/.gitignore new file mode 100644 index 0000000000000..990c18de22908 --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/.gitignore @@ -0,0 +1,153 @@ +llama_index/_static +.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +bin/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +etc/ +include/ +lib/ +lib64/ +parts/ +sdist/ +share/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.ruff_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints +notebooks/ + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pyvenv.cfg + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Jetbrains +.idea +modules/ +*.swp + +# VsCode +.vscode + +# pipenv +Pipfile +Pipfile.lock + +# pyright +pyrightconfig.json diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/BUILD b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/BUILD new file mode 100644 index 0000000000000..0896ca890d8bf --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/BUILD @@ -0,0 +1,3 @@ +poetry_requirements( + name="poetry", +) diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/CHANGELOG.md b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/CHANGELOG.md new file mode 100644 index 0000000000000..36bff877abcbe --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/CHANGELOG.md @@ -0,0 +1,5 @@ +# CHANGELOG + +## [0.1.2] - 2024-02-13 + +- Add maintainers and keywords from library.json (llamahub) diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/Makefile b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/Makefile new file mode 100644 index 0000000000000..b9eab05aa3706 --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/Makefile @@ -0,0 +1,17 @@ +GIT_ROOT ?= $(shell git rev-parse --show-toplevel) + +help: ## Show all Makefile targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' + +format: ## Run code autoformatters (black). + pre-commit install + git ls-files | xargs pre-commit run black --files + +lint: ## Run linters: pre-commit (black, ruff, codespell) and mypy + pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files + +test: ## Run tests via pytest. + pytest tests + +watch-docs: ## Build and watch documentation. + sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/ diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/README.md b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/README.md new file mode 100644 index 0000000000000..1f1b384ba6847 --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/README.md @@ -0,0 +1,22 @@ +# Feishu Wiki Loader + +This loader can traverse all feishu documents under the feishi space. + +## Usage + +To use this loader, you need to: + +1. apply the permission(`wiki:wiki:readonly`) of the feishu app +2. add the feishu app as the admin of your feishu space, see [here](https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#b5da330b) for more help +3. finally, pass your feishu space id to this loader + +```python +app_id = "xxx" +app_secret = "xxx" +space_id = "xxx" +FeishuWikiReader = download_loader("FeishuWikiReader") +loader = FeishuWikiReader(app_id, app_secret) +documents = loader.load_data(space_id=space_id) +``` + +This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. See [here](https://github.com/emptycrown/llama-hub/tree/main) for examples. diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/BUILD b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/__init__.py b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/__init__.py new file mode 100644 index 0000000000000..3a4f56d259dcc --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/__init__.py @@ -0,0 +1,3 @@ +from llama_index.readers.feishu_wiki.base import FeishuWikiReader + +__all__ = ["FeishuWikiReader"] diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/base.py b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/base.py new file mode 100644 index 0000000000000..e4f6e003c87db --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/llama_index/readers/feishu_wiki/base.py @@ -0,0 +1,150 @@ +"""Feishu wiki reader.""" +import json +import os +import time +from typing import List + +import requests +from llama_index.core.readers.base import BaseReader +from llama_index.core.schema import Document + +# Copyright (2023) Bytedance Ltd. and/or its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class FeishuWikiReader(BaseReader): + """Feishu Wiki reader. + + Reads pages from Feishu wiki under the space + + """ + + host = "https://open.feishu.cn" + wiki_nodes_url_path = "/open-apis/wiki/v2/spaces/{}/nodes" + documents_raw_content_url_path = "/open-apis/docx/v1/documents/{}/raw_content" + tenant_access_token_internal_url_path = ( + "/open-apis/auth/v3/tenant_access_token/internal" + ) + + def __init__(self, app_id: str, app_secret: str) -> None: + """ + + Args: + app_id: The unique identifier of the application is obtained after the application is created. + app_secret: Application key, obtained after creating the application. + """ + super().__init__() + self.app_id = app_id + self.app_secret = app_secret + + self.tenant_access_token = "" + self.expire = 0 + + def load_data(self, space_id: str, parent_node_token: str = None) -> List[Document]: + """Load data from the input directory. + + Args: + space_id (str): a space id. + parent_node_token (str[optional]): a parent node token of the space + """ + if space_id is None: + raise ValueError('Must specify a "space_id" in `load_kwargs`.') + + document_ids = self._load_space(space_id, parent_node_token=parent_node_token) + document_ids = list(set(document_ids)) + + results = [] + for document_id in document_ids: + doc = self._load_doc(document_id) + results.append(Document(text=doc, extra_info={"document_id": document_id})) + return results + + def _load_space(self, space_id: str, parent_node_token: str = None) -> str: + if self.tenant_access_token == "" or self.expire < time.time(): + self._update_tenant_access_token() + headers = { + "Authorization": f"Bearer {self.tenant_access_token}", + "Content-Type": "application/json; charset=utf-8", + } + + url = self.host + self.wiki_spaces_url_path.format(space_id) + if parent_node_token: + url += f"?parent_node_token={parent_node_token}" + try: + response = requests.get(url, headers=headers) + result = response.json() + except Exception: + return [] + if not result.get("data"): + return [] + obj_token_list = [] + for item in result["data"]["items"]: + obj_token_list.append(item["obj_token"]) + if item["has_child"]: + child_obj_token_list = self._load_space( + space_id=space_id, parent_node_token=item["node_token"] + ) + if child_obj_token_list: + obj_token_list.extend(child_obj_token_list) + return obj_token_list + + def _load_doc(self, document_id: str) -> str: + """Load a document from Feishu Docs. + + Args: + document_id: the document id. + + Returns: + The document text. + """ + url = self.host + self.documents_raw_content_url_path.format(document_id) + if self.tenant_access_token == "" or self.expire < time.time(): + self._update_tenant_access_token() + headers = { + "Authorization": f"Bearer {self.tenant_access_token}", + "Content-Type": "application/json; charset=utf-8", + } + try: + response = requests.get(url, headers=headers) + result = response.json() + except Exception: + return None + if not result.get("data"): + return None + return result["data"]["content"] + + def _update_tenant_access_token(self) -> None: + """For update tenant_access_token.""" + url = self.host + self.tenant_access_token_internal_url_path + headers = {"Content-Type": "application/json; charset=utf-8"} + data = {"app_id": self.app_id, "app_secret": self.app_secret} + response = requests.post(url, data=json.dumps(data), headers=headers) + self.tenant_access_token = response.json()["tenant_access_token"] + self.expire = time.time() + response.json()["expire"] + + def set_lark_domain(self, host: str) -> None: + """Set lark domain.""" + self.host = host + + +if __name__ == "__main__": + app_id = os.environ.get("FEISHU_APP_ID") + app_secret = os.environ.get("FEISHU_APP_SECRET") + reader = FeishuWikiReader(app_id, app_secret) + print( + reader.load_data( + space_id=os.environ.get("FEISHU_SPACE_ID"), + parent_node_token=os.environ.get("FEISHU_PARENT_NODE_TOKEN"), + ) + ) diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/pyproject.toml b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/pyproject.toml new file mode 100644 index 0000000000000..f8b9c264233fa --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.codespell] +check-filenames = true +check-hidden = true +skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb" + +[tool.llamahub] +contains_example = false +import_path = "llama_index.readers.feishu_wiki" + +[tool.llamahub.class_authors] +FeishuWikiReader = "zhourunlai" + +[tool.mypy] +disallow_untyped_defs = true +exclude = ["_static", "build", "examples", "notebooks", "venv"] +ignore_missing_imports = true +python_version = "3.8" + +[tool.poetry] +authors = ["Your Name "] +description = "llama-index readers feishu_wiki integration" +exclude = ["**/BUILD"] +license = "MIT" +maintainers = ["zhourunlai"] +name = "llama-index-readers-feishu-wiki" +readme = "README.md" +version = "0.1.0" + +[tool.poetry.dependencies] +python = ">=3.8.1,<4.0" +llama-index-core = "^0.10.1" +requests = "^2.31.0" + +[tool.poetry.group.dev.dependencies] +ipython = "8.10.0" +jupyter = "^1.0.0" +mypy = "0.991" +pre-commit = "3.2.0" +pylint = "2.15.10" +pytest = "7.2.1" +pytest-mock = "3.11.1" +ruff = "0.0.292" +tree-sitter-languages = "^1.8.0" +types-Deprecated = ">=0.1.0" +types-PyYAML = "^6.0.12.12" +types-protobuf = "^4.24.0.4" +types-redis = "4.5.5.0" +types-requests = "2.28.11.8" +types-setuptools = "67.1.0.0" + +[tool.poetry.group.dev.dependencies.black] +extras = ["jupyter"] +version = "<=23.9.1,>=23.7.0" + +[tool.poetry.group.dev.dependencies.codespell] +extras = ["toml"] +version = ">=v2.2.6" + +[[tool.poetry.packages]] +include = "llama_index/" diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/BUILD b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/BUILD new file mode 100644 index 0000000000000..dabf212d7e716 --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/BUILD @@ -0,0 +1 @@ +python_tests() diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/__init__.py b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/test_readers_feishu_wiki.py b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/test_readers_feishu_wiki.py new file mode 100644 index 0000000000000..2fef634282e38 --- /dev/null +++ b/llama-index-integrations/readers/llama-index-readers-feishu-wiki/tests/test_readers_feishu_wiki.py @@ -0,0 +1,7 @@ +from llama_index.core.readers.base import BaseReader +from llama_index.readers.feishu_wiki import FeishuWikiReader + + +def test_class(): + names_of_base_classes = [b.__name__ for b in FeishuWikiReader.__mro__] + assert BaseReader.__name__ in names_of_base_classes diff --git a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/image_deplot/README.md b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/image_deplot/README.md index 05ba4fe82d25e..c4869ab48d24e 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/image_deplot/README.md +++ b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/image_deplot/README.md @@ -1,5 +1,9 @@ # Image Tabular Chart Loader (Deplot) +```bash +pip install llama-index-readers-file +``` + This loader captions an image file containing a tabular chart (bar chart, line charts) using deplot. ## Usage @@ -8,7 +12,7 @@ To use this loader, you need to pass in a `Path` to a local file. ```python from pathlib import Path -from llama_hub.file.image_deplot import ImageTabularChartReader +from llama_index.readers.file import ImageTabularChartReader loader = ImageTabularChartReader() documents = loader.load_data(file=Path("./image.png")) diff --git a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/paged_csv/README.md b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/paged_csv/README.md index e46a6d4c9a1d8..a5a0eae2e09e7 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/paged_csv/README.md +++ b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/paged_csv/README.md @@ -1,5 +1,9 @@ # Paged CSV Loader +```bash +pip install llama-index-readers-file +``` + This loader extracts the text from a local .csv file by formatting each row in an LLM-friendly way and inserting it into a separate Document. A single local file is passed in each time you call `load_data`. For example, a Document might look like: ``` @@ -15,9 +19,8 @@ To use this loader, you need to pass in a `Path` to a local file. ```python from pathlib import Path -from llama_index.core.readers import download_loader -PagedCSVReader = download_loader("PagedCSVReader") +from llama_index.readers.file import PagedCSVReader loader = PagedCSVReader(encoding="utf-8") documents = loader.load_data(file=Path("./transactions.csv")) diff --git a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/pymu_pdf/README.md b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/pymu_pdf/README.md index 9abd1e99e3d66..cfde1701d6e3a 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/pymu_pdf/README.md +++ b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/pymu_pdf/README.md @@ -1,6 +1,10 @@ # PyMuPDF Loader -This loader extracts text from a local PDF file using the `PyMuPDF` Python library. This is the fastest among all other PDF parsing options available in `llama_hub`. If `metadata` is passed as True while calling `load` function; extracted documents will include basic metadata such as page numbers, file path and total number of pages in pdf. +```bash +pip install llama-index-readers-file +``` + +This loader extracts text from a local PDF file using the `PyMuPDF` Python library. If `metadata` is passed as True while calling `load` function; extracted documents will include basic metadata such as page numbers, file path and total number of pages in pdf. ## Usage @@ -8,9 +12,8 @@ To use this loader, you need to pass file path of the local file as string or `P ```python from pathlib import Path -from llama_index import download_loader -PyMuPDFReader = download_loader("PyMuPDFReader") +from llama_index.readers.file import PyMuPDFReader loader = PyMuPDFReader() documents = loader.load_data(file_path=Path("./article.pdf"), metadata=True) diff --git a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/tabular/base.py b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/tabular/base.py index 9b1bc097b1ac8..02008b258f816 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/tabular/base.py +++ b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/tabular/base.py @@ -46,10 +46,15 @@ def load_data( csv_reader = csv.reader(fp) for row in csv_reader: text_list.append(", ".join(row)) + + metadata = {"filename": file.name, "extension": file.suffix} + if extra_info: + metadata = {**metadata, **extra_info} + if self._concat_rows: - return [Document(text="\n".join(text_list), metadata=extra_info)] + return [Document(text="\n".join(text_list), metadata=metadata)] else: - return [Document(text=text, metadata=extra_info) for text in text_list] + return [Document(text=text, metadata=metadata) for text in text_list] class PandasCSVReader(BaseReader): diff --git a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/unstructured/README.md b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/unstructured/README.md index 5a59f69ab6a87..a08ad57e71009 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/unstructured/README.md +++ b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/unstructured/README.md @@ -1,5 +1,9 @@ # Unstructured.io File Loader +```bash +pip install llama-index-readers-file +``` + This loader extracts the text from a variety of unstructured text files using [Unstructured.io](https://github.com/Unstructured-IO/unstructured). Currently, the file extensions that are supported are `.txt`, `.docx`, `.pptx`, `.jpg`, `.png`, `.eml`, `.html`, and `.pdf` documents. A single local file is passed in each time you call `load_data`. Check out their documentation to see more details, but notably, this enables you to parse the unstructured data of many use-cases. For example, you can download the 10-K SEC filings of public companies (e.g. [Coinbase](https://www.sec.gov/ix?doc=/Archives/edgar/data/0001679788/000167978822000031/coin-20211231.htm)), and feed it directly into this loader without worrying about cleaning up the formatting or HTML tags. @@ -10,7 +14,7 @@ To use this loader, you need to pass in a `Path` to a local file. Optionally, yo ```python from pathlib import Path -from llama_hub.file.unstructured import UnstructuredReader +from llama_index.readers.file import UnstructuredReader loader = UnstructuredReader() documents = loader.load_data(file=Path("./10k_filing.html")) @@ -20,10 +24,9 @@ You can also easily use this loader in conjunction with `SimpleDirectoryReader` ```python from pathlib import Path -from llama_index import download_loader -from llama_index import SimpleDirectoryReader +from llama_index.core import SimpleDirectoryReader -UnstructuredReader = download_loader("UnstructuredReader") +from llama_index.readers.file import UnstructuredReader dir_reader = SimpleDirectoryReader( "./data", diff --git a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/xml/README.md b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/xml/README.md index c7860ddc0e0ee..1fa813908046d 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/xml/README.md +++ b/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/xml/README.md @@ -1,5 +1,9 @@ # XML Loader +```bash +pip install llama-index-readers-file +``` + This loader extracts the text from a local XML file. A single local file is passed in each time you call `load_data`. ## Usage @@ -8,12 +12,11 @@ To use this loader, you need to pass in a `Path` to a local file. ```python from pathlib import Path -from llama_index import download_loader -XMLReader = download_loader("XMLReader") +from llama_index.readers.file import XMLReader loader = XMLReader() documents = loader.load_data(file=Path("../example.xml")) ``` -This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. See [here](https://github.com/run-llama/llama-hub/tree/main/llama_hub) for examples. +This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. diff --git a/llama-index-integrations/readers/llama-index-readers-file/pyproject.toml b/llama-index-integrations/readers/llama-index-readers-file/pyproject.toml index d1ad309d6cb52..ea3cca17bf187 100644 --- a/llama-index-integrations/readers/llama-index-readers-file/pyproject.toml +++ b/llama-index-integrations/readers/llama-index-readers-file/pyproject.toml @@ -49,7 +49,7 @@ license = "MIT" maintainers = ["FarisHijazi", "Haowjy", "ephe-meral", "hursh-desai", "iamarunbrahma", "jon-chuang", "mmaatouk", "ravi03071991", "sangwongenip", "thejessezhang"] name = "llama-index-readers-file" readme = "README.md" -version = "0.1.6" +version = "0.1.7" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/readers/llama-index-readers-firebase-realtimedb/README.md b/llama-index-integrations/readers/llama-index-readers-firebase-realtimedb/README.md index ad5ea2f6e4e40..0894b91d8a622 100644 --- a/llama-index-integrations/readers/llama-index-readers-firebase-realtimedb/README.md +++ b/llama-index-integrations/readers/llama-index-readers-firebase-realtimedb/README.md @@ -1,5 +1,9 @@ # Firebase Realtime Database Loader +```bash +pip install llama-index-readers-firebase-realtimedb +``` + This loader retrieves documents from Firebase Realtime Database. The user specifies the Firebase Realtime Database URL and, optionally, the path to a service account key file for authentication. ## Usage @@ -7,10 +11,8 @@ This loader retrieves documents from Firebase Realtime Database. The user specif Here's an example usage of the FirebaseRealtimeDatabaseReader. ```python -from llama_index import download_loader - -FirebaseRealtimeDatabaseReader = download_loader( - "FirebaseRealtimeDatabaseReader" +from llama_index.readers.firebase_realtimedb import ( + FirebaseRealtimeDatabaseReader, ) database_url = "" @@ -20,4 +22,4 @@ reader = FirebaseRealtimeDatabaseReader(database_url, service_account_key_path) documents = reader.load_data(path) ``` -This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. See [here](https://github.com/emptycrown/llama-hub/tree/main) for examples. +This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. diff --git a/llama-index-integrations/readers/llama-index-readers-firestore/README.md b/llama-index-integrations/readers/llama-index-readers-firestore/README.md index fb3d9b3329018..05cad520aeaee 100644 --- a/llama-index-integrations/readers/llama-index-readers-firestore/README.md +++ b/llama-index-integrations/readers/llama-index-readers-firestore/README.md @@ -1,5 +1,9 @@ # Firestore Loader +```bash +pip install llama-index-readers-firestore +``` + This loader loads from a Firestore collection or a specific document from Firestore. The loader assumes your project already has the google cloud credentials loaded. To find out how to set up credentials, [see here](https://cloud.google.com/docs/authentication/provide-credentials-adc). ## Usage @@ -9,9 +13,8 @@ To initialize the loader, provide the project-id of the google cloud project. ## Initializing the reader ```python -from llama_index import download_loader +from llama_index.readers.firestore import FirestoreReader -FirestoreReader = download_loader("FirestoreReader") reader = FirestoreReader(project_id="") ``` diff --git a/llama-index-integrations/readers/llama-index-readers-genius/README.md b/llama-index-integrations/readers/llama-index-readers-genius/README.md index cce1ff910532e..5ebf06cc78645 100644 --- a/llama-index-integrations/readers/llama-index-readers-genius/README.md +++ b/llama-index-integrations/readers/llama-index-readers-genius/README.md @@ -1,5 +1,9 @@ # LlamaIndex Readers Integration: Genius +```bash +pip install llama-index-readers-genius +``` + This loader connects to the Genius API and loads lyrics, metadata, and album art into `Documents`. As a prerequisite, you will need to register with [Genius API](https://genius.com/api-clients) and create an app in order to get a `client_id` and a `client_secret`. You should then set a `redirect_uri` for the app. The `redirect_uri` does not need to be functional. You should then generate an access token as an instantiator for the GeniusReader. @@ -60,9 +64,7 @@ Here's an example usage of the GeniusReader. It will retrieve songs that match s - **Returns**: List of `Document` objects with song lyrics. ```python -from llama_index.core.readers import download_loader - -GeniusReader = download_loader("GeniusReader") +from llama_index.readers.genius import GeniusReader access_token = "your_generated_access_token" @@ -79,7 +81,7 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ```python from llama_index.core import VectorStoreIndex, download_loader -GeniusReader = download_loader("GeniusReader") +from llama_index.readers.genius import GeniusReader access_token = "your_generated_access_token" diff --git a/llama-index-integrations/readers/llama-index-readers-gpt-repo/README.md b/llama-index-integrations/readers/llama-index-readers-gpt-repo/README.md index 286383911b739..2609090e89be8 100644 --- a/llama-index-integrations/readers/llama-index-readers-gpt-repo/README.md +++ b/llama-index-integrations/readers/llama-index-readers-gpt-repo/README.md @@ -1,5 +1,9 @@ # GPT Repository Loader +```bash +pip install llama-index-readers-gpt-repo +``` + This loader is an adaptation of https://github.com/mpoon/gpt-repository-loader to LlamaHub. Full credit goes to mpoon for coming up with this! @@ -8,9 +12,7 @@ to LlamaHub. Full credit goes to mpoon for coming up with this! To use this loader, you need to pass in a path to a local Git repository ```python -from llama_index import download_loader - -GPTRepoReader = download_loader("GPTRepoReader") +from llama_index.readers.gpt_repo import GPTRepoReader loader = GPTRepoReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-graphdb-cypher/README.md b/llama-index-integrations/readers/llama-index-readers-graphdb-cypher/README.md index e39cf1bfcf9b4..ee59f64a200d3 100644 --- a/llama-index-integrations/readers/llama-index-readers-graphdb-cypher/README.md +++ b/llama-index-integrations/readers/llama-index-readers-graphdb-cypher/README.md @@ -1,5 +1,9 @@ # Graph Database Cypher Loader +```bash +pip install llama-index-readers-graphdb-cypher +``` + This loader populates documents from results of Cypher queries from a Graph database endpoint. The user specifies a GraphDB endpoint URL with optional credentials to initialize the reader. By declaring the Cypher query and optional parameters the loader can fetch the nested result docs. @@ -14,10 +18,9 @@ Here's an example usage of the `GraphDBCypherReader`. You can test out queries directly with the Neo4j labs demo server: demo.neo4jlabs.com or with a free instance https://neo4j.com/aura ```python -from llama_index import download_loader import os -GraphDBCypherReader = download_loader("GraphDBCypherReader") +from llama_index.readers.graphdb_cypher import GraphDBCypherReader uri = "neo4j+s://demo.neo4jlabs.com" username = "stackoverflow" diff --git a/llama-index-integrations/readers/llama-index-readers-graphql/README.md b/llama-index-integrations/readers/llama-index-readers-graphql/README.md index 4d779719226a0..adc08fb338d64 100644 --- a/llama-index-integrations/readers/llama-index-readers-graphql/README.md +++ b/llama-index-integrations/readers/llama-index-readers-graphql/README.md @@ -1,5 +1,9 @@ # GraphQL Loader +```bash +pip install llama-index-readers-graphql +``` + This loader loads documents via GraphQL queries from a GraphQL endpoint. The user specifies a GraphQL endpoint URL with optional credentials to initialize the reader. By declaring the GraphQL query and optional variables (parameters) the loader can fetch the nested result docs. @@ -10,10 +14,9 @@ Here's an example usage of the GraphQLReader. You can test out queries directly [on the site](https://countries.trevorblades.com/) ```python -from llama_index import download_loader import os -GraphQLReader = download_loader("GraphQLReader") +from llama_index.readers.graphql import GraphQLReader uri = "https://countries.trevorblades.com/" headers = {} diff --git a/llama-index-integrations/readers/llama-index-readers-guru/README.md b/llama-index-integrations/readers/llama-index-readers-guru/README.md index 147a5ef9e6911..d580cc60322fa 100644 --- a/llama-index-integrations/readers/llama-index-readers-guru/README.md +++ b/llama-index-integrations/readers/llama-index-readers-guru/README.md @@ -1,5 +1,9 @@ # Guru Loader +```bash +pip install llama-index-readers-guru +``` + This loader loads documents from [Guru](https://www.getguru.com/). The user specifies a username and api key to initialize the GuruReader. Note this is not your password. You need to create a new api key in the admin tab of the portal. @@ -9,9 +13,7 @@ Note this is not your password. You need to create a new api key in the admin ta Here's an example usage of the GuruReader. ```python -from llama_index import download_loader - -GuruReader = download_loader("GuruReader") +from llama_index.readers.guru import GuruReader reader = GuruReader(username="", api_key="") diff --git a/llama-index-integrations/readers/llama-index-readers-hatena-blog/README.md b/llama-index-integrations/readers/llama-index-readers-hatena-blog/README.md index 777749cb6004c..ef17a2892f64c 100644 --- a/llama-index-integrations/readers/llama-index-readers-hatena-blog/README.md +++ b/llama-index-integrations/readers/llama-index-readers-hatena-blog/README.md @@ -1,5 +1,9 @@ # Hatena Blog Loader +```bash +pip install llama-index-readers-hatena-blog +``` + This loader fetches article from your own [Hatena Blog](https://hatenablog.com/) blog posts using the AtomPub API. You can get AtomPub info from the admin page after logging into Hatena Blog. @@ -9,10 +13,9 @@ You can get AtomPub info from the admin page after logging into Hatena Blog. Here's an example usage of the HatenaBlogReader. ```python -from llama_index import download_loader import os -HatenaBlogReader = download_loader("HatenaBlogReader") +from llama_index.readers.hatena_blog import HatenaBlogReader root_endpoint = os.getenv("ATOM_PUB_ROOT_ENDPOINT") api_key = os.getenv("ATOM_PUB_API_KEY") diff --git a/llama-index-integrations/readers/llama-index-readers-hive/README.md b/llama-index-integrations/readers/llama-index-readers-hive/README.md index 39e721c1c02d7..dd9be6afe87d5 100644 --- a/llama-index-integrations/readers/llama-index-readers-hive/README.md +++ b/llama-index-integrations/readers/llama-index-readers-hive/README.md @@ -1,5 +1,9 @@ # Hive Loader +```bash +pip install llama-index-readers-hive +``` + The Hive Loader returns a set of texts corresponding to documents from Hive based on the customized query. The user initializes the loader with Hive connection args and then using query to fetch data from Hive. @@ -8,9 +12,7 @@ The user initializes the loader with Hive connection args and then using query t Here's an example usage of the hiveReader to load 100 documents. ```python -from llama_index import download_loader - -HiveReader = download_loader("HiveReader") +from llama_index.readers.hive import HiveReader reader = HiveReader( host="localhost", @@ -24,4 +26,4 @@ query = "SELECT * FROM p1 LIMIT 100" documents = reader.load_data(query=query) ``` -This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. See [here](https://github.com/run-llama/llama-hub/tree/main/llama_hub) for examples. +This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. diff --git a/llama-index-integrations/readers/llama-index-readers-hubspot/README.md b/llama-index-integrations/readers/llama-index-readers-hubspot/README.md index d7682e2437a9b..0660c3369fc8d 100644 --- a/llama-index-integrations/readers/llama-index-readers-hubspot/README.md +++ b/llama-index-integrations/readers/llama-index-readers-hubspot/README.md @@ -1,5 +1,9 @@ # Hubspot Loader +```bash +pip install llama-index-readers-hubspot +``` + This loader loads documents from Hubspot. The user specifies an access token to initialize the HubspotReader. At the moment, this loader only supports access token authentication. To obtain an access token, you will need to create a private app by following instructions [here](https://developers.hubspot.com/docs/api/private-apps). @@ -9,10 +13,9 @@ At the moment, this loader only supports access token authentication. To obtain Here's an example usage of the HubspotReader. ```python -from llama_index import download_loader import os -HubspotReader = download_loader("HubspotReader") +from llama_index.readers.hubspot import HubspotReader reader = HubspotReader("") documents = reader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-huggingface-fs/README.md b/llama-index-integrations/readers/llama-index-readers-huggingface-fs/README.md index bcfc874039b24..ebc6ece29e457 100644 --- a/llama-index-integrations/readers/llama-index-readers-huggingface-fs/README.md +++ b/llama-index-integrations/readers/llama-index-readers-huggingface-fs/README.md @@ -1,5 +1,9 @@ # Hugging Face FS Loader +```bash +pip install llama-index-readers-huggingface-fs +``` + This loader uses Hugging Face Hub's Filesystem API (> 0.14) to load datasets. @@ -12,9 +16,8 @@ To use this loader, you need to pass in a path to a Hugging Face dataset. ```python from pathlib import Path -from llama_index import download_loader -HuggingFaceFSReader = download_loader("HuggingFaceFSReader") +from llama_index.readers.huggingface_fs import HuggingFaceFSReader # load documents loader = HuggingFaceFSReader() diff --git a/llama-index-integrations/readers/llama-index-readers-hwp/README.md b/llama-index-integrations/readers/llama-index-readers-hwp/README.md index 0f45d30d0103b..a330901c19d5e 100644 --- a/llama-index-integrations/readers/llama-index-readers-hwp/README.md +++ b/llama-index-integrations/readers/llama-index-readers-hwp/README.md @@ -1,5 +1,9 @@ # HWP Loader +```bash +pip install llama-index-readers-file +``` + This loader reads the HWP file, which is the format of many official documents in South Korea. ## Usage @@ -7,7 +11,7 @@ This loader reads the HWP file, which is the format of many official documents i To use this loader, you need to pass in a file name. It's fine whether the file is compressed or not. ```python -from llama_hub.hangeul import HWPReader +from llama_index.readers.file import HWPReader from pathlib import Path hwp_path = Path("/path/to/hwp") diff --git a/llama-index-integrations/readers/llama-index-readers-imdb-review/README.md b/llama-index-integrations/readers/llama-index-readers-imdb-review/README.md index 844e67ced77db..20d8daf2668a4 100644 --- a/llama-index-integrations/readers/llama-index-readers-imdb-review/README.md +++ b/llama-index-integrations/readers/llama-index-readers-imdb-review/README.md @@ -1,5 +1,9 @@ ## IMDB MOVIE REVIEWS LOADER +```bash +pip install llama-index-readers-imdb-review +``` + This loader fetches all the reviews of a movie or a TV-series from IMDB official site. This loader is working on Windows machine and it requires further debug on Linux. Fixes are on the way Install the required dependencies @@ -18,9 +22,7 @@ The IMDB downloader takes in two attributes ## Usage ```python -from llama_index import download_loader - -IMDBReviewsloader = download_loader("IMDBReviews") +from llama_index.readers.imdb_review import IMDBReviews loader = IMDBReviews( movie_name_year="The Social Network 2010", webdriver_engine="edge" @@ -47,10 +49,10 @@ This loader can be used with both Langchain and LlamaIndex. ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader -from llama_index import VectorStoreIndex +from llama_index.core import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex -IMDBReviewsloader = download_loader("IMDBReviews") +from llama_index.readers.imdb_review import IMDBReviews loader = IMDBReviewsloader( movie_name_year="The Social Network 2010", @@ -72,7 +74,6 @@ print(response) ### Langchain ```python -from llama_index import download_loader from langchain.llms import OpenAI from langchain.agents.agent_toolkits.pandas import ( create_pandas_dataframe_agent, @@ -81,7 +82,7 @@ from langchain.agents import Tool from langchain.agents import initialize_agent from langchain.chat_models import ChatOpenAI -IMDBReviewsloader = download_loader("IMDBReviews") +from llama_index.readers.imdb_review import IMDBReviews loader = IMDBReviewsloader( movie_name_year="The Social Network 2010", diff --git a/llama-index-integrations/readers/llama-index-readers-intercom/README.md b/llama-index-integrations/readers/llama-index-readers-intercom/README.md index 7c6c7163198a9..f15265703a64e 100644 --- a/llama-index-integrations/readers/llama-index-readers-intercom/README.md +++ b/llama-index-integrations/readers/llama-index-readers-intercom/README.md @@ -1,5 +1,9 @@ # Intercom Loader +```bash +pip install llama-index-readers-intercom +``` + This loader fetches the text from Intercom help articles using the Intercom API. It also uses the BeautifulSoup library to parse the HTML and extract the text from the articles. ## Usage @@ -7,9 +11,7 @@ This loader fetches the text from Intercom help articles using the Intercom API. To use this loader, you need to pass in an Intercom account access token. ```python -from llama_index import download_loader - -IntercomReader = download_loader("IntercomReader") +from llama_index.readers.intercom import IntercomReader loader = IntercomReader(intercom_access_token="my_access_token") documents = loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-jira/README.md b/llama-index-integrations/readers/llama-index-readers-jira/README.md index a36d63872b7b6..2e76552ba9317 100644 --- a/llama-index-integrations/readers/llama-index-readers-jira/README.md +++ b/llama-index-integrations/readers/llama-index-readers-jira/README.md @@ -1,5 +1,9 @@ # JIRA Reader +```bash +pip install llama-index-readers-jira +``` + The Jira loader returns a set of issues based on the query provided to the dataloader. We can follow two methods to initialize the loader- 1- basic_auth -> this takes a dict with the following keys @@ -21,7 +25,7 @@ You can follow this link for more information regarding Oauth2 -> https://develo Here's an example of how to use it ```python -from llama_hub.jira import JiraReader +from llama_index.readers.jira import JiraReader reader = JiraReader( email=email, api_token=api_token, server_url="your-jira-server.com" @@ -32,9 +36,7 @@ documents = reader.load_data(query="project = ") Alternately, you can also use download_loader from llama_index ```python -from llama_index import download_loader - -JiraReader = download_loader("JiraReader") +from llama_index.readers.jira import JiraReader reader = JiraReader( email=email, api_token=api_token, server_url="your-jira-server.com" diff --git a/llama-index-integrations/readers/llama-index-readers-joplin/README.md b/llama-index-integrations/readers/llama-index-readers-joplin/README.md index bcd7afb5855cf..c8a14e07fd25b 100644 --- a/llama-index-integrations/readers/llama-index-readers-joplin/README.md +++ b/llama-index-integrations/readers/llama-index-readers-joplin/README.md @@ -1,5 +1,9 @@ # Joplin (Markdown) Loader +```bash +pip install llama-index-readers-joplin +``` + > [Joplin](https://joplinapp.org/) is an open source note-taking app. Capture your thoughts and securely access them from any device. This readme covers how to load documents from a `Joplin` database. @@ -20,10 +24,10 @@ An alternative to this approach is to export the `Joplin`'s note database to Mar Here's an example usage of the JoplinReader. ```python -from llama_index import download_loader import os -JoplinReader = download_loader("JoplinReader") +from llama_index.readers.joplin import JoplinReader + documents = JoplinReader( access_token="" ).load_data() # Returns list of documents diff --git a/llama-index-integrations/readers/llama-index-readers-kaltura/README.md b/llama-index-integrations/readers/llama-index-readers-kaltura/README.md index 6aba072088d86..1d5fae146e88d 100644 --- a/llama-index-integrations/readers/llama-index-readers-kaltura/README.md +++ b/llama-index-integrations/readers/llama-index-readers-kaltura/README.md @@ -1,5 +1,9 @@ # Kaltura eSearch Loader +```bash +pip install llama-index-readers-kaltura-esearch +``` + This loader reads Kaltura Entries from [Kaltura](https://corp.kaltura.com) based on a Kaltura eSearch API call. Search queries can be passed as a pre-defined object of KalturaESearchEntryParams, or through a simple free text query. The result is a list of documents containing the Kaltura Entries and Captions json. @@ -64,9 +68,7 @@ Each dictionary in the response represents a Kaltura media entry, where the keys First, instantiate the KalturaReader (aka Kaltura Loader) with your Kaltura configuration credentials: ```python -from llama_index import download_loader - -KalturaESearchReader = download_loader("KalturaESearchReader") +from llama_index.readers.kaltura_esearch import KalturaESearchReader loader = KalturaESearchReader( partnerId="INSERT_YOUR_PARTNER_ID", diff --git a/llama-index-integrations/readers/llama-index-readers-kibela/README.md b/llama-index-integrations/readers/llama-index-readers-kibela/README.md index 97323863e872c..d4a39b1a455d4 100644 --- a/llama-index-integrations/readers/llama-index-readers-kibela/README.md +++ b/llama-index-integrations/readers/llama-index-readers-kibela/README.md @@ -1,5 +1,9 @@ # Kibela Reader +```bash +pip install llama-index-readers-kibela +``` + This reader fetches article from your [Kibela](https://kibe.la/) notes using the GraphQL API. # Usage @@ -8,7 +12,7 @@ Here's an example of how to use it. You can get your access token from [here](ht ```python import os -from llama_hub.kibela import KibelaReader +from llama_index.readers.kibela import KibelaReader team = os.environ["KIBELA_TEAM"] token = os.environ["KIBELA_TOKEN"] @@ -21,9 +25,8 @@ Alternately, you can also use download_loader from llama_index ```python import os -from llama_index import download_loader -KibelaReader = download_loader("KibelaReader") +from llama_index.readers.kibela import KibelaReader team = os.environ["KIBELA_TEAM"] token = os.environ["KIBELA_TOKEN"] diff --git a/llama-index-integrations/readers/llama-index-readers-lilac/README.md b/llama-index-integrations/readers/llama-index-readers-lilac/README.md index 0a396b701a2c7..210d23f6bbfce 100644 --- a/llama-index-integrations/readers/llama-index-readers-lilac/README.md +++ b/llama-index-integrations/readers/llama-index-readers-lilac/README.md @@ -1,5 +1,11 @@ # Lilac reader +```bash +pip install llama-index-readers-papers + +pip install llama-index-readers-lilac +``` + [Lilac](https://lilacml.com/) is an open-source product that helps you analyze, enrich, and clean unstructured data with AI. It can be used to analyze, clean, structure, and label data that can be used in downstream LlamaIndex and LangChain applications. @@ -17,11 +23,10 @@ You can use any LlamaIndex loader to load data into Lilac, clean data, and then See [this notebook](https://github.com/lilacai/lilac/blob/main/notebooks/LlamaIndexLoader.ipynb) for getting data into Lilac from LlamaHub. ```python -from llama_index import download_loader import lilac as ll # See: https://llamahub.ai/l/papers-arxiv -ArxivReader = download_loader("ArxivReader") +from llama_index.readers.papers import ArxivReader loader = ArxivReader() documents = loader.load_data(search_query="au:Karpathy") @@ -49,9 +54,9 @@ ll.start_server(project_dir="./data") ### Lilac => LlamaIndex Documents ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -LilacReader = download_loader("LilacReader") +from llama_index.readers.lilac import LilacReader loader = LilacReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-linear/README.md b/llama-index-integrations/readers/llama-index-readers-linear/README.md index 59e14cdc7184a..3c3a0f97ccafe 100644 --- a/llama-index-integrations/readers/llama-index-readers-linear/README.md +++ b/llama-index-integrations/readers/llama-index-readers-linear/README.md @@ -1,5 +1,9 @@ # Linear Reader +```bash +pip install llama-index-readers-linear +``` + The Linear loader returns issue based on the query. ## Usage @@ -7,7 +11,7 @@ The Linear loader returns issue based on the query. Here's an example of how to use it ```python -from llama_hub.linear import LinearReader +from llama_index.readers.linear import LinearReader reader = LinearReader(api_key=api_key) query = """ @@ -38,9 +42,7 @@ documents = reader.load_data(query=query) Alternately, you can also use download_loader from llama_index ```python -from llama_index import download_loader - -LinearReader = download_loader("LinearReader") +from llama_index.readers.linear import LinearReader reader = LinearReader(api_key=api_key) query = """ diff --git a/llama-index-integrations/readers/llama-index-readers-macrometa-gdn/README.md b/llama-index-integrations/readers/llama-index-readers-macrometa-gdn/README.md index e2fd8a41bef7b..a9fae3033bdcb 100644 --- a/llama-index-integrations/readers/llama-index-readers-macrometa-gdn/README.md +++ b/llama-index-integrations/readers/llama-index-readers-macrometa-gdn/README.md @@ -1,5 +1,9 @@ # Macrometa GDN Loader +```bash +pip install llama-index-readers-macrometa-gdn +``` + This loader takes in a Macrometa federation URL, API key, and collection name and returns a list of vectors. ## Usage @@ -7,9 +11,7 @@ This loader takes in a Macrometa federation URL, API key, and collection name an To use this loader, you need to pass the URL and API key through the class constructor, and then load the data using an array of collection names. ```python -from llama_index import download_loader - -MacrometaGDNReader = download_loader("MacrometaGDNReader") +from llama_index.readers.macrometa_gdn import MacrometaGDNReader collections = ["test_collection"] loader = MacrometaGDNReader(url="https://api-macrometa.io", apikey="test") diff --git a/llama-index-integrations/readers/llama-index-readers-mangadex/README.md b/llama-index-integrations/readers/llama-index-readers-mangadex/README.md index 685c59af9f735..893aadd634106 100644 --- a/llama-index-integrations/readers/llama-index-readers-mangadex/README.md +++ b/llama-index-integrations/readers/llama-index-readers-mangadex/README.md @@ -1,13 +1,15 @@ # MangaDex Loader +```bash +pip install llama-index-readers-mangadex +``` + This loader fetches information from the MangaDex API, by manga title. ## Usage ```python -from llama_index import download_loader - -MangaDexReader = download_loader("MangaDexReader") +from llama_index.readers.mangadex import MangaDexReader loader = MangaDexReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-mangoapps-guides/README.md b/llama-index-integrations/readers/llama-index-readers-mangoapps-guides/README.md index 87e03688c8104..51f392bd8c660 100644 --- a/llama-index-integrations/readers/llama-index-readers-mangoapps-guides/README.md +++ b/llama-index-integrations/readers/llama-index-readers-mangoapps-guides/README.md @@ -1,5 +1,9 @@ # MangoppsGuides Loader +```bash +pip install llama-index-readers-mangoapps-guides +``` + This loader fetches the text from Mangopps Guides. ## Usage @@ -7,9 +11,7 @@ This loader fetches the text from Mangopps Guides. To use this loader, you need to pass base url of the MangoppsGuides installation (e.g. `https://guides.mangoapps.com/`) and the limit , i.e. max number of links it should crawl ```python -from llama_index import download_loader - -MangoppsGuidesReader = download_loader("MangoppsGuidesReader") +from llama_index.readers.mangoapps_guides import MangoppsGuidesReader loader = MangoppsGuidesReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-maps/README.md b/llama-index-integrations/readers/llama-index-readers-maps/README.md index 920dfdd45fd0c..47f318eb75077 100644 --- a/llama-index-integrations/readers/llama-index-readers-maps/README.md +++ b/llama-index-integrations/readers/llama-index-readers-maps/README.md @@ -1,5 +1,9 @@ # **_Osmmap Loader_** +```bash +pip install llama-index-readers-maps +``` + The Osmmap Loader will fetch map data from the [Overpass](https://wiki.openstreetmap.org/wiki/Main_Page) api for a certain place or area. Version **Overpass API 0.7.60** is used by this loader. The api will provide you with all the **nodes, relations, and ways** for the particular region when you request data for a region or location. @@ -27,9 +31,7 @@ She requires all the nodes, routes, and relations within a five-kilometer radius ### And the code snippet looks like ```python -from llama_index import download_loader - -MapReader = download_loader("OpenMap") +from llama_index.readers.maps import OpenMap loader = MapReader() documents = loader.load_data( @@ -46,9 +48,7 @@ documents = loader.load_data( - so she search for hospital tag in the [Taginfo](https://taginfo.openstreetmap.org/tags) and she got ```python -from llama_index import download_loader - -MapReader = download_loader("OpenMap") +from llama_index.readers.maps import OpenMap loader = MapReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-memos/README.md b/llama-index-integrations/readers/llama-index-readers-memos/README.md index 85dfd31c08430..849e4dd1fedcb 100644 --- a/llama-index-integrations/readers/llama-index-readers-memos/README.md +++ b/llama-index-integrations/readers/llama-index-readers-memos/README.md @@ -1,5 +1,9 @@ # Memos Loader +```bash +pip install llama-index-readers-memos +``` + This loader fetches text from self-hosted [memos](https://github.com/usememos/memos). ## Usage @@ -7,9 +11,8 @@ This loader fetches text from self-hosted [memos](https://github.com/usememos/me To use this loader, you need to specify the host where memos is deployed. If you need to filter, pass the [corresponding parameter](https://github.com/usememos/memos/blob/4fe8476169ecd2fc4b164a25611aae6861e36812/api/memo.go#L76) in `load_data`. ```python -from llama_index import download_loader +from llama_index.readers.memos import MemosReader -MemosReader = download_loader("MemosReader") loader = MemosReader("https://demo.usememos.com/") documents = loader.load_data({"creatorId": 101}) ``` diff --git a/llama-index-integrations/readers/llama-index-readers-microsoft-onedrive/README.md b/llama-index-integrations/readers/llama-index-readers-microsoft-onedrive/README.md index e2df15698bcaa..05a43f3de03d4 100644 --- a/llama-index-integrations/readers/llama-index-readers-microsoft-onedrive/README.md +++ b/llama-index-integrations/readers/llama-index-readers-microsoft-onedrive/README.md @@ -1,5 +1,9 @@ # Microsoft OneDrive Loader +```bash +pip install llama-index-readers-microsoft-onedrive +``` + This loader reads files from: - Microsoft OneDrive Personal [(https://onedrive.live.com/)](https://onedrive.live.com/) and @@ -61,9 +65,7 @@ For example, the file_id of `https://onedrive.live.com/?cid=0B5AF52BE769DFDE4&id #### OneDrive Personal Example Usage: ```python -from llama_index import download_loader - -OneDriveReader = download_loader("OneDriveReader") +from llama_index.readers.microsoft_onedrive import OneDriveReader # User Authentication flow: Replace client id with your own id loader = OneDriveReader(client_id="82ee706e-2439-47fa-877a-95048ead9318") @@ -108,9 +110,7 @@ For example, the path of file "demo_doc.docx" within test subfolder from previou #### OneDrive For Business Example Usage: ```python -from llama_index import download_loader - -OneDriveReader = download_loader("OneDriveReader") +from llama_index.readers.microsoft_onedrive import OneDriveReader loader = OneDriveReader( client_id="82ee706e-2439-47fa-877a-95048ead9318", diff --git a/llama-index-integrations/readers/llama-index-readers-microsoft-outlook/README.md b/llama-index-integrations/readers/llama-index-readers-microsoft-outlook/README.md index 3869c7a6cd386..c2f556fb67155 100644 --- a/llama-index-integrations/readers/llama-index-readers-microsoft-outlook/README.md +++ b/llama-index-integrations/readers/llama-index-readers-microsoft-outlook/README.md @@ -1,5 +1,9 @@ # Outlook Local Calendar Loader +```bash +pip install llama-index-readers-microsoft-outlook +``` + This loader reads your past and upcoming Calendar events from your local Outlook .ost or .pst and parses the relevant info into `Documents`. It runs on Windows only and has only been tested with Windows 11. It has been designed to have a supoerset of the functionality of the Google Calendar reader. @@ -11,9 +15,7 @@ Here's an example usage of the OutlookCalendar Reader. It will retrieve up to 10 It always returns Start, End, Subject, Location, and Organizer attributes and optionally returns additional attributes specified in the `more_attributes` parameter, which, if specified, must be a list of strings eg. ['Body','someotherattribute',...]. Attributes which don't exist in a calendar entry are ignored without warning. ```python -from llama_index import download_loader - -OutlookCalendarReader = download_loader("OutlookLocalCalendarReader") +from llama_index.readers.microsoft_outlook import OutlookLocalCalendarReader loader = OutlookCalendarReader() documents = loader.load_data() @@ -26,9 +28,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -OutlookCalendarReader = download_loader("OutlookLocalCalendarReader") +from llama_index.readers.microsoft_outlook import OutlookLocalCalendarReader loader = OutlookCalendarReader( start_date="2022-01-01", number_of_documents=1000 diff --git a/llama-index-integrations/readers/llama-index-readers-microsoft-sharepoint/README.md b/llama-index-integrations/readers/llama-index-readers-microsoft-sharepoint/README.md index e382b0f013194..9ea404eae410f 100644 --- a/llama-index-integrations/readers/llama-index-readers-microsoft-sharepoint/README.md +++ b/llama-index-integrations/readers/llama-index-readers-microsoft-sharepoint/README.md @@ -1,5 +1,9 @@ # Microsoft SharePoint Reader +```bash +pip install llama-index-readers-microsoft-sharepoint +``` + The loader loads the files from a folder in sharepoint site. It also supports traversing recursively through the sub-folders. @@ -27,9 +31,7 @@ If the files are present in the `Test` folder in SharePoint Site under `root` di ![FilePath](file_path_info.png) ```python -from llama_index import download_loader - -SharePointLoader = download_loader("SharePointReader") +from llama_index.readers.microsoft_sharepoint import SharePointReader loader = SharePointLoader( client_id="", diff --git a/llama-index-integrations/readers/llama-index-readers-minio/README.md b/llama-index-integrations/readers/llama-index-readers-minio/README.md index d0bb5d20dd2d1..6fa3d65e706ce 100644 --- a/llama-index-integrations/readers/llama-index-readers-minio/README.md +++ b/llama-index-integrations/readers/llama-index-readers-minio/README.md @@ -6,4 +6,4 @@ ## Import -`from llama_index.readers.minio import MinioReader, BotoMinioReader` +from llama_index.core.readers.minio import MinioReader, BotoMinioReader` diff --git a/llama-index-integrations/readers/llama-index-readers-minio/llama_index/README.md b/llama-index-integrations/readers/llama-index-readers-minio/llama_index/README.md index e17e81f4d6bcc..3ae38a238236b 100644 --- a/llama-index-integrations/readers/llama-index-readers-minio/llama_index/README.md +++ b/llama-index-integrations/readers/llama-index-readers-minio/llama_index/README.md @@ -13,8 +13,6 @@ To use this loader, you need to pass in the name of your Minio Bucket. After tha Otherwise, you may specify a prefix if you only want to parse certain files in the Bucket, or a subdirectory. ```python -from llama_index import download_loader - MinioReader = download_loader("BotoMinioReader") loader = MinioReader( bucket="documents", @@ -40,8 +38,6 @@ Otherwise, you may specify a prefix if you only want to parse certain files in t You can now use the client with a TLS-secured MinIO instance (`minio_secure=True`), even if server's certificate isn't trusted (`minio_cert_check=False`). ```python -from llama_index import download_loader - MinioReader = download_loader("MinioReader") loader = MinioReader( bucket="documents", diff --git a/llama-index-integrations/readers/llama-index-readers-mondaydotcom/README.md b/llama-index-integrations/readers/llama-index-readers-mondaydotcom/README.md index 47e4b14b946e7..fd9d1f696fa5d 100644 --- a/llama-index-integrations/readers/llama-index-readers-mondaydotcom/README.md +++ b/llama-index-integrations/readers/llama-index-readers-mondaydotcom/README.md @@ -1,5 +1,9 @@ # Monday Loader +```bash +pip install llama-index-readers-mondaydotcom +``` + This loader loads data from monday.com. The user specifies an API token to initialize the MondayReader. They then specify a monday.com board id to load in the corresponding Document objects. ## Usage @@ -7,9 +11,7 @@ This loader loads data from monday.com. The user specifies an API token to initi Here's an example usage of the MondayReader. ```python -from llama_index import download_loader - -MondayReader = download_loader("MondayReader") +from llama_index.readers.mondaydotcom import MondayReader reader = MondayReader("") documents = reader.load_data("") diff --git a/llama-index-integrations/readers/llama-index-readers-nougat-ocr/README.md b/llama-index-integrations/readers/llama-index-readers-nougat-ocr/README.md index d38c41f7a01ad..56b3285ae302d 100644 --- a/llama-index-integrations/readers/llama-index-readers-nougat-ocr/README.md +++ b/llama-index-integrations/readers/llama-index-readers-nougat-ocr/README.md @@ -1,5 +1,9 @@ # Nougat OCR loader +```bash +pip install llama-index-readers-nougat-ocr +``` + This loader reads the equations, symbols, and tables included in the PDF. Users can input the path of the academic PDF document `file` which they want to parse. This OCR understands LaTeX math and tables. @@ -9,7 +13,7 @@ Users can input the path of the academic PDF document `file` which they want to Here's an example usage of the PDFNougatOCR. ```python -from llama_hub.nougat_ocr import PDFNougatOCR +from llama_index.readers.nougat_ocr import PDFNougatOCR reader = PDFNougatOCR() diff --git a/llama-index-integrations/readers/llama-index-readers-openalex/README.md b/llama-index-integrations/readers/llama-index-readers-openalex/README.md index a171d0697be41..3ba2fb3b3bc11 100644 --- a/llama-index-integrations/readers/llama-index-readers-openalex/README.md +++ b/llama-index-integrations/readers/llama-index-readers-openalex/README.md @@ -1,11 +1,15 @@ # OpenAlex Reader +```bash +pip install llama-index-readers-openalex +``` + This loader will search for papers in OpenAlex and load them in llama-index. The main advantage of using OpenAlex is that you can search the full-text for Open Access papers as well. ## Usage ```python -from llama_hub.openalex_loader import OpenAlexReader +from llama_index.readers.openalex import OpenAlexReader openalex_reader = OpenAlexReader(email="shauryr@gmail.com") query = "biases in large language models" diff --git a/llama-index-integrations/readers/llama-index-readers-opendal/README.md b/llama-index-integrations/readers/llama-index-readers-opendal/README.md index 86cf7176efcb8..8890a2cc0aa78 100644 --- a/llama-index-integrations/readers/llama-index-readers-opendal/README.md +++ b/llama-index-integrations/readers/llama-index-readers-opendal/README.md @@ -1,5 +1,9 @@ # OpenDAL Loaders +```bash +pip install llama-index-readers-opendal +``` + ## Base OpendalReader This loader parses any file via [Apache OpenDAL](https://github.com/apache/incubator-opendal). @@ -11,9 +15,7 @@ All files are temporarily downloaded locally and subsequently parsed with `Simpl `OpendalReader` can read data from any supported storage services including `s3`, `azblob`, `gcs` and so on. ```python -from llama_index import download_loader - -OpendalReader = download_loader("OpendalReader") +from llama_index.readers.opendal import OpendalReader loader = OpendalReader( scheme="s3", @@ -40,9 +42,7 @@ All files are temporarily downloaded locally and subsequently parsed with `Simpl ### Usage ```python -from llama_index import download_loader - -OpendalAzblobReader = download_loader("OpendalAzblobReader") +from llama_index.readers.opendal import OpendalAzblobReader loader = OpendalAzblobReader( container="container", @@ -69,9 +69,7 @@ All files are temporarily downloaded locally and subsequently parsed with `Simpl ### Usage ```python -from llama_index import download_loader - -OpendalGcsReader = download_loader("OpendalGcsReader") +from llama_index.readers.opendal import OpendalGcsReader loader = OpendalGcsReader( bucket="bucket", @@ -99,10 +97,6 @@ All files are temporarily downloaded locally and subsequently parsed with `Simpl ### Usage ```python -from llama_index import download_loader - -OpendalS3Reader = download_loader("OpendalS3Reader") - loader = OpendalS3Reader( bucket="bucket", path="path/to/data/", diff --git a/llama-index-integrations/readers/llama-index-readers-opensearch/README.md b/llama-index-integrations/readers/llama-index-readers-opensearch/README.md index 2af8cd6372232..b9a172008b24f 100644 --- a/llama-index-integrations/readers/llama-index-readers-opensearch/README.md +++ b/llama-index-integrations/readers/llama-index-readers-opensearch/README.md @@ -1,5 +1,9 @@ # Opensearch Loader +```bash +pip install llama-index-readers-opensearch +``` + The Opensearch Loader returns a set of texts corresponding to documents retrieved from an Opensearch index. The user initializes the loader with an Opensearch index. They then pass in a field, and optionally a JSON query DSL object to fetch the fields they want. @@ -8,9 +12,7 @@ The user initializes the loader with an Opensearch index. They then pass in a fi Here's an example usage of the OpensearchReader to load 100 documents. ```python -from llama_index import download_loader - -OpensearchReader = download_loader("OpensearchReader") +from llama_index.readers.opensearch import OpensearchReader reader = OpensearchReader( host="localhost", diff --git a/llama-index-integrations/readers/llama-index-readers-pandas-ai/README.md b/llama-index-integrations/readers/llama-index-readers-pandas-ai/README.md index 7c5dc5d16c2a2..15124e6e2f9ec 100644 --- a/llama-index-integrations/readers/llama-index-readers-pandas-ai/README.md +++ b/llama-index-integrations/readers/llama-index-readers-pandas-ai/README.md @@ -1,5 +1,9 @@ # Pandas AI Loader +```bash +pip install llama-index-readers-pandas-ai +``` + This loader is a light wrapper around the `PandasAI` Python package. See here: https://github.com/gventuri/pandas-ai. @@ -10,7 +14,6 @@ you can choose to load in `Document` objects via `load_data`. ## Usage ```python -from llama_index import download_loader from pandasai.llm.openai import OpenAI import pandas as pd @@ -47,7 +50,7 @@ df = pd.DataFrame( llm = OpenAI() -PandasAIReader = download_loader("PandasAIReader") +from llama_index.readers.pandas_ai import PandasAIReader # use run_pandas_ai directly # set is_conversational_answer=False to get parsed output diff --git a/llama-index-integrations/readers/llama-index-readers-papers/README.md b/llama-index-integrations/readers/llama-index-readers-papers/README.md index 54d66b6bc3da6..7dbebcfb7499b 100644 --- a/llama-index-integrations/readers/llama-index-readers-papers/README.md +++ b/llama-index-integrations/readers/llama-index-readers-papers/README.md @@ -1,5 +1,9 @@ # Papers Loaders +```bash +pip install llama-index-readers-papers +``` + ## Arxiv Papers Loader This loader fetches the text from the most relevant scientific papers on Arxiv specified by a search query (e.g. "Artificial Intelligence"). For each paper, the abstract is extracted and put in a separate document. The search query may be any string, Arxiv paper id, or a general Arxiv query string (see the full list of capabilities [here](https://info.arxiv.org/help/api/user-manual.html#query_details)). @@ -9,9 +13,7 @@ This loader fetches the text from the most relevant scientific papers on Arxiv s To use this loader, you need to pass in the search query. You may also optionally specify a local directory to temporarily store the paper PDFs (they are deleted automatically) and the maximum number of papers you want to parse for your search query (default is 10). ```python -from llama_index import download_loader - -ArxivReader = download_loader("ArxivReader") +from llama_index.readers.papers import ArxivReader loader = ArxivReader() documents = loader.load_data(search_query="au:Karpathy") @@ -20,9 +22,7 @@ documents = loader.load_data(search_query="au:Karpathy") Alternatively, if you would like to load papers and abstracts separately: ```python -from llama_index import download_loader - -ArxivReader = download_loader("ArxivReader") +from llama_index.readers.papers import ArxivReader loader = ArxivReader() documents, abstracts = loader.load_papers_and_abstracts( @@ -41,9 +41,7 @@ This loader fetches the text from the most relevant scientific papers on Pubmed To use this loader, you need to pass in the search query. You may also optionally specify the maximum number of papers you want to parse for your search query (default is 10). ```python -from llama_index import download_loader - -PubmedReader = download_loader("PubmedReader") +from llama_index.readers.papers import PubmedReader loader = PubmedReader() documents = loader.load_data(search_query="amyloidosis") diff --git a/llama-index-integrations/readers/llama-index-readers-patentsview/README.md b/llama-index-integrations/readers/llama-index-readers-patentsview/README.md index 127d653495ab3..c283ffa887e4e 100644 --- a/llama-index-integrations/readers/llama-index-readers-patentsview/README.md +++ b/llama-index-integrations/readers/llama-index-readers-patentsview/README.md @@ -1,5 +1,9 @@ # Patentsview Loader +```bash +pip install llama-index-readers-patentsview +``` + This loader loads patent abstract from `a list of patent numbers` with API provided by [Patentsview](https://patentsview.org/). ## Usage @@ -7,9 +11,8 @@ This loader loads patent abstract from `a list of patent numbers` with API provi Here'a an example usage of PatentsviewReader. ```python -from llama_index import download_loader +from llama_index.readers.patentsview import PatentsviewReader -PatentsviewReader = download_loader("PatentsviewReader") loader = PatentsviewReader() patents = ["8848839", "10452978"] abstracts = loader.load_data(patents) diff --git a/llama-index-integrations/readers/llama-index-readers-pdb/README.md b/llama-index-integrations/readers/llama-index-readers-pdb/README.md index 9997c20ac48ec..b82f352b14e29 100644 --- a/llama-index-integrations/readers/llama-index-readers-pdb/README.md +++ b/llama-index-integrations/readers/llama-index-readers-pdb/README.md @@ -1,5 +1,9 @@ # Protein Data Bank (PDB) publication Loader +```bash +pip install llama-index-readers-pdb +``` + This loader fetches the abstract of PDB entries using the RCSB (Research Collaboratory for Structural Bioinformatics) or EBI (European Bioinformatics Institute) REST api. ## Usage @@ -7,7 +11,7 @@ This loader fetches the abstract of PDB entries using the RCSB (Research Collabo To use this loader, simply pass an array of PDB ids into `load_data`: ```python -from llama_hub.pdb import PdbAbstractReader +from llama_index.readers.pdb import PdbAbstractReader loader = PdbAbstractReader() documents = loader.load_data(pdb_id=["1cbs"]) diff --git a/llama-index-integrations/readers/llama-index-readers-pdf-table/README.md b/llama-index-integrations/readers/llama-index-readers-pdf-table/README.md index bfe9e0271a333..5036097ec1e85 100644 --- a/llama-index-integrations/readers/llama-index-readers-pdf-table/README.md +++ b/llama-index-integrations/readers/llama-index-readers-pdf-table/README.md @@ -1,5 +1,9 @@ # PDF Table Loader +```bash +pip install llama-index-readers-pdf-table +``` + This loader reads the tables included in the PDF. Users can input the PDF `file` and the `pages` from which they want to extract tables, and they can read the tables included on those pages. @@ -10,7 +14,7 @@ Here's an example usage of the PDFTableReader. `pages` parameter is the same as camelot's `pages`. Therefore, you can use patterns such as `all`, `1,2,3`, `10-20`, and so on. ```python -from llama_hub.pdf_table import PDFTableReader +from llama_index.readers.pdf_table import PDFTableReader from pathlib import Path reader = PDFTableReader() diff --git a/llama-index-integrations/readers/llama-index-readers-preprocess/README.md b/llama-index-integrations/readers/llama-index-readers-preprocess/README.md index ae514fbdf4893..ebd34f3fdea04 100644 --- a/llama-index-integrations/readers/llama-index-readers-preprocess/README.md +++ b/llama-index-integrations/readers/llama-index-readers-preprocess/README.md @@ -1,5 +1,9 @@ # Preprocess Loader +```bash +pip install llama-index-readers-preprocess +``` + [Preprocess](https://preprocess.co) is an API service that splits any kind of document into optimal chunks of text for use in language model tasks. Given documents in input `Preprocess` splits them into chunks of text that respect the layout and semantics of the original document. We split the content by taking into account sections, paragraphs, lists, images, data tables, text tables, and slides, and following the content semantics for long texts. @@ -26,10 +30,9 @@ To chunk a file pass a valid filepath and the reader will start converting and c If you want to handle the nodes directly: ```python -from llama_index import VectorStoreIndex -from llama_index import download_loader +from llama_index.core import VectorStoreIndex -PreprocessReader = download_loader("PreprocessReader") +from llama_index.readers.preprocess import PreprocessReader # pass a filepath and get the chunks as nodes loader = PreprocessReader( @@ -45,10 +48,9 @@ query_engine = index.as_query_engine() By default load_data() returns a document for each chunk, remember to not apply any splitting to these documents ```python -from llama_index import VectorStoreIndex -from llama_index import download_loader +from llama_index.core import VectorStoreIndex -PreprocessReader = download_loader("PreprocessReader") +from llama_index.readers.preprocess import PreprocessReader # pass a filepath and get the chunks as nodes loader = PreprocessReader( diff --git a/llama-index-integrations/readers/llama-index-readers-rayyan/README.md b/llama-index-integrations/readers/llama-index-readers-rayyan/README.md index d5dae921eac7c..05c589fa43c8e 100644 --- a/llama-index-integrations/readers/llama-index-readers-rayyan/README.md +++ b/llama-index-integrations/readers/llama-index-readers-rayyan/README.md @@ -1,5 +1,9 @@ # Rayyan Loader +```bash +pip install llama-index-readers-rayyan +``` + This loader fetches review articles from [Rayyan](https://www.rayyan.ai/) using the [Rayyan SDK](https://github.com/rayyansys/rayyan-python-sdk). All articles for a given review are fetched by default unless a filter is specified. @@ -11,9 +15,8 @@ and optionally the API server URL if different from the default. More details about these parameters can be found in the official Rayyan SDK repository. ```python -from llama_index import download_loader +from llama_index.readers.rayyan import RayyanReader -RayyanReader = download_loader("RayyanReader") loader = RayyanReader(credentials_path="path/to/rayyan-creds.json") ``` diff --git a/llama-index-integrations/readers/llama-index-readers-readwise/README.md b/llama-index-integrations/readers/llama-index-readers-readwise/README.md index 525cfe8b25917..9aa461c5822f3 100644 --- a/llama-index-integrations/readers/llama-index-readers-readwise/README.md +++ b/llama-index-integrations/readers/llama-index-readers-readwise/README.md @@ -1,5 +1,9 @@ # Readwise Reader +```bash +pip install llama-index-readers-readwise +``` + Use Readwise's export API to fetch your highlights from web articles, epubs, pdfs, Kindle, YouTube, and load the resulting text into LLMs. ## Setup @@ -12,9 +16,10 @@ Here is an example usage of the Readwise Reader: ```python import os -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader + +from llama_index.readers.readwise import ReadwiseReader -ReadwiseReader = download_loader("ReadwiseReader") token = os.getenv("READWISE_API_KEY") loader = ReadwiseReader(api_key=token) documents = loader.load_data() @@ -28,9 +33,10 @@ You can also query for highlights that have been created after a certain time: ```python import os import datetime -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader + +from llama_index.readers.readwise import ReadwiseReader -ReadwiseReader = download_loader("ReadwiseReader") token = os.getenv("READWISE_API_KEY") loader = ReadwiseReader(api_key=token) seven_days_ago = datetime.datetime.now() - datetime.timedelta(days=7) diff --git a/llama-index-integrations/readers/llama-index-readers-reddit/README.md b/llama-index-integrations/readers/llama-index-readers-reddit/README.md index 7153d344f6d81..ea964afc453eb 100644 --- a/llama-index-integrations/readers/llama-index-readers-reddit/README.md +++ b/llama-index-integrations/readers/llama-index-readers-reddit/README.md @@ -1,5 +1,9 @@ # Reddit Reader +```bash +pip install llama-index-readers-reddit +``` + For any subreddit(s) you're interested in, search for relevant posts using keyword(s) and load the resulting text in the post and and top-level comments into LLMs/ LangChains. ## Get your Reddit credentials ready @@ -15,9 +19,9 @@ For any subreddit(s) you're interested in, search for relevant posts using keywo ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -RedditReader = download_loader("RedditReader") +from llama_index.readers.reddit import RedditReader subreddits = ["MachineLearning"] search_keys = ["PyTorch", "deploy"] @@ -35,13 +39,13 @@ index.query("What are the pain points of PyTorch users?") ### LangChain ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -RedditReader = download_loader("RedditReader") +from llama_index.readers.reddit import RedditReader subreddits = ["MachineLearning"] search_keys = ["PyTorch", "deploy"] diff --git a/llama-index-integrations/readers/llama-index-readers-remote-depth/README.md b/llama-index-integrations/readers/llama-index-readers-remote-depth/README.md index 48ba8b0ab8159..886acec8ddeb0 100644 --- a/llama-index-integrations/readers/llama-index-readers-remote-depth/README.md +++ b/llama-index-integrations/readers/llama-index-readers-remote-depth/README.md @@ -1,5 +1,9 @@ # Remote Page/File Loader +```bash +pip install llama-index-readers-remote-depth +``` + This loader makes it easy to extract the text from the links available in a webpage URL, and extract the links presents in the page. It's based on `RemoteReader` (reading single page), that is based on `SimpleDirectoryReader` (parsing the document if file is a pdf, etc). It is an all-in-one tool for (almost) any group of urls. You can try with this MIT lecture link, it will be able to extract the syllabus, the PDFs, etc: @@ -10,9 +14,7 @@ You can try with this MIT lecture link, it will be able to extract the syllabus, You need to specify the parameter `depth` to specify how many levels of links you want to extract. For example, if you want to extract the links in the page, and the links in the links in the page, you need to specify `depth=2`. ```python -from llama_index import download_loader - -RemoteDepthReader = download_loader("RemoteDepthReader") +from llama_index.readers.remote_depth import RemoteDepthReader loader = RemoteDepthReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-remote/README.md b/llama-index-integrations/readers/llama-index-readers-remote/README.md index f9ff15c271201..c4f8e64a1c8f0 100644 --- a/llama-index-integrations/readers/llama-index-readers-remote/README.md +++ b/llama-index-integrations/readers/llama-index-readers-remote/README.md @@ -1,5 +1,9 @@ # Remote Page/File Loader +```bash +pip install llama-index-readers-remote +``` + This loader makes it easy to extract the text from any remote page or file using just its url. If there's a file at the url, this loader will download it temporarily and parse it using `SimpleDirectoryReader`. It is an all-in-one tool for (almost) any url. As a result, any page or type of file is supported. For instance, if a `.txt` url such as a [Project Gutenberg book](https://www.gutenberg.org/cache/epub/69994/pg69994.txt) is passed in, the text will be parsed as is. On the other hand, if a hosted .mp3 url is passed in, it will be downloaded and parsed using `AudioTranscriber`. @@ -9,9 +13,7 @@ As a result, any page or type of file is supported. For instance, if a `.txt` ur To use this loader, you need to pass in a `Path` to a local file. Optionally, you may specify a `file_extractor` for the `SimpleDirectoryReader` to use, other than the default one. ```python -from llama_index import download_loader - -RemoteReader = download_loader("RemoteReader") +from llama_index.readers.remote import RemoteReader loader = RemoteReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-s3/README.md b/llama-index-integrations/readers/llama-index-readers-s3/README.md index f4413b0f5a2ae..96c3ed4d95f27 100644 --- a/llama-index-integrations/readers/llama-index-readers-s3/README.md +++ b/llama-index-integrations/readers/llama-index-readers-s3/README.md @@ -11,10 +11,6 @@ To use this loader, you need to pass in the name of your S3 Bucket. After that, Otherwise, you may specify a prefix if you only want to parse certain files in the Bucket, or a subdirectory. AWS Access Key credentials may either be passed in during initialization or stored locally (see above). ```python -from llama_index import download_loader - -S3Reader = download_loader("S3Reader") - loader = S3Reader( bucket="scrabble-dictionary", key="dictionary.txt", diff --git a/llama-index-integrations/readers/llama-index-readers-sec-filings/README.md b/llama-index-integrations/readers/llama-index-readers-sec-filings/README.md index 15d0155f31f04..4a2e0a33abd02 100644 --- a/llama-index-integrations/readers/llama-index-readers-sec-filings/README.md +++ b/llama-index-integrations/readers/llama-index-readers-sec-filings/README.md @@ -1,5 +1,9 @@ # SEC DATA DOWNLOADER +```bash +pip install llama-index-readers-sec-filings +``` + Please checkout this repo that I am building on SEC Question Answering Agent [SEC-QA](https://github.com/Athe-kunal/SEC-QA-Agent) This repository downloads all the texts from SEC documents (10-K and 10-Q). Currently, it is not supporting documents that are amended, but that will be added in the near futures. @@ -21,9 +25,7 @@ The SEC Downloader expects 5 attributes ## Usage ```python -from llama_index import download_loader - -SECFilingsLoader = download_loader("SECFilingsLoader") +from llama_index.readers.sec_filings import SECFilingsLoader loader = SECFilingsLoader(tickers=["TSLA"], amount=3, filing_type="10-K") loader.load_data() @@ -95,10 +97,10 @@ This loader is can be used with both Langchain and LlamaIndex. ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader -from llama_index import SimpleDirectoryReader +from llama_index.core import VectorStoreIndex, download_loader +from llama_index.core import SimpleDirectoryReader -SECFilingsLoader = download_loader("SECFilingsLoader") +from llama_index.readers.sec_filings import SECFilingsLoader loader = SECFilingsLoader(tickers=["TSLA"], amount=3, filing_type="10-K") loader.load_data() @@ -111,13 +113,12 @@ index.query("What are the risk factors of Tesla for the year 2022?") ### Langchain ```python -from llama_index import download_loader from langchain.llms import OpenAI from langchain.chains import RetrievalQA from langchain.document_loaders import DirectoryLoader from langchain.indexes import VectorstoreIndexCreator -SECFilingsLoader = download_loader("SECFilingsLoader") +from llama_index.readers.sec_filings import SECFilingsLoader loader = SECFilingsLoader(tickers=["TSLA"], amount=3, filing_type="10-K") loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-semanticscholar/README.md b/llama-index-integrations/readers/llama-index-readers-semanticscholar/README.md index 0242f63cd6e20..08b6a486f6053 100644 --- a/llama-index-integrations/readers/llama-index-readers-semanticscholar/README.md +++ b/llama-index-integrations/readers/llama-index-readers-semanticscholar/README.md @@ -1,5 +1,11 @@ # Semantic Scholar Loader +```bash +pip install llama-index-readers-semanticscholar + +pip install llama-index-llms-openai +``` + Welcome to Semantic Scholar Loader. This module serves as a crucial utility for researchers and professionals looking to get scholarly articles and publications from the Semantic Scholar database. For any research topic you are interested in, this loader reads relevant papers from a search result in Semantic Scholar into `Documents`. @@ -27,13 +33,10 @@ Here is an example of how to use this loader in `llama_index` and get citations ### LlamaIndex ```python -from llama_index.llms import OpenAI -from llama_index.query_engine import CitationQueryEngine -from llama_index import ( - VectorStoreIndex, - ServiceContext, -) -from llama_hub.semanticscholar import SemanticScholarReader +from llama_index.llms.openai import OpenAI +from llama_index.core.query_engine import CitationQueryEngine +from llama_index.core import VectorStoreIndex, ServiceContext +from llama_index.readers.semanticscholar import SemanticScholarReader s2reader = SemanticScholarReader() diff --git a/llama-index-integrations/readers/llama-index-readers-singlestore/README.md b/llama-index-integrations/readers/llama-index-readers-singlestore/README.md index 5ab20d4d6c46f..1b98a72969891 100644 --- a/llama-index-integrations/readers/llama-index-readers-singlestore/README.md +++ b/llama-index-integrations/readers/llama-index-readers-singlestore/README.md @@ -1,5 +1,9 @@ # SingleStore Loader +```bash +pip install llama-index-readers-singlestore +``` + The SingleStore Loader retrieves a set of documents from a specified table in a SingleStore database. The user initializes the loader with database information and then provides a search embedding for retrieving similar documents. ## Usage @@ -7,7 +11,7 @@ The SingleStore Loader retrieves a set of documents from a specified table in a Here's an example usage of the SingleStoreReader: ```python -from llama_hub.singlestore import SingleStoreReader +from llama_index.readers.singlestore import SingleStoreReader # Initialize the reader with your SingleStore database credentials and other relevant details reader = SingleStoreReader( diff --git a/llama-index-integrations/readers/llama-index-readers-smart-pdf-loader/README.md b/llama-index-integrations/readers/llama-index-readers-smart-pdf-loader/README.md index ceccf5377e664..1184ce086a6a6 100644 --- a/llama-index-integrations/readers/llama-index-readers-smart-pdf-loader/README.md +++ b/llama-index-integrations/readers/llama-index-readers-smart-pdf-loader/README.md @@ -1,5 +1,9 @@ # Smart PDF Loader +```bash +pip install llama-index-readers-smart-pdf-loader +``` + SmartPDFLoader is a super fast PDF reader that understands the layout structure of PDFs such as nested sections, nested lists, paragraphs and tables. It uses layout information to smartly chunk PDFs into optimal short contexts for LLMs. @@ -16,7 +20,7 @@ pip install llmsherpa Here's an example usage of the SmartPDFLoader: ```python -from llama_hub.smart_pdf_loader import SmartPDFLoader +from llama_index.readers.smart_pdf_loader import SmartPDFLoader llmsherpa_api_url = "https://readers.llmsherpa.com/api/document/developer/parseDocument?renderFormat=all" pdf_url = "https://arxiv.org/pdf/1910.13461.pdf" # also allowed is a file path e.g. /home/downloads/xyz.pdf @@ -27,7 +31,7 @@ documents = pdf_loader.load_data(pdf_url) Now you can use the documents with other LlamaIndex components. For example, for retrieval augmented generation, try this: ```python -from llama_index import VectorStoreIndex +from llama_index.core import VectorStoreIndex index = VectorStoreIndex.from_documents(documents) query_engine = index.as_query_engine() diff --git a/llama-index-integrations/readers/llama-index-readers-snowflake/README.md b/llama-index-integrations/readers/llama-index-readers-snowflake/README.md index d0302f8343088..c6c0daec2cf5b 100644 --- a/llama-index-integrations/readers/llama-index-readers-snowflake/README.md +++ b/llama-index-integrations/readers/llama-index-readers-snowflake/README.md @@ -1,5 +1,9 @@ # Snowflake Loader +```bash +pip install llama-index-readers-snowflake +``` + This loader connects to Snowflake (using SQLAlchemy under the hood). The user specifies a query and extracts Document objects corresponding to the results. You can use this loader to easily connect to a database on Snowflake and pass the documents into a `GPTSQLStructStoreIndex` from LlamaIndex. ## Usage @@ -9,9 +13,7 @@ This loader connects to Snowflake (using SQLAlchemy under the hood). The user sp Here's an example usage of the SnowflakeReader. ```python -from llama_index import download_loader - -SnowflakeReader = download_loader("SnowflakeReader") +from llama_index.readers.snowflake import SnowflakeReader reader = SnowflakeReader( engine=your_sqlalchemy_engine, @@ -27,9 +29,7 @@ documents = reader.load_data(query=query) Here's an example usage of the SnowflakeReader. ```python -from llama_index import download_loader - -SnowflakeReader = download_loader("SnowflakeReader") +from llama_index.readers.snowflake import SnowflakeReader reader = SnowflakeReader( account="your_account", diff --git a/llama-index-integrations/readers/llama-index-readers-snscrape-twitter/README.md b/llama-index-integrations/readers/llama-index-readers-snscrape-twitter/README.md index 681155514845a..e6d7b0f9aee60 100644 --- a/llama-index-integrations/readers/llama-index-readers-snscrape-twitter/README.md +++ b/llama-index-integrations/readers/llama-index-readers-snscrape-twitter/README.md @@ -1,5 +1,9 @@ # Snscrape twitter Loader +```bash +pip install llama-index-readers-snscrape-twitter +``` + This loader loads documents from Twitter using the Snscrape Python package. ## Usage @@ -7,10 +11,9 @@ This loader loads documents from Twitter using the Snscrape Python package. Here's an example usage of the SnscrapeReader. ```python -from llama_index import download_loader import os -SnscrapeReader = download_loader("SnscrapeTwitterReader") +from llama_index.readers.snscrape_twitter import SnscrapeTwitterReader loader = SnscrapeReader() documents = loader.load_data(username="elonmusk", num_tweets=10) diff --git a/llama-index-integrations/readers/llama-index-readers-spotify/README.md b/llama-index-integrations/readers/llama-index-readers-spotify/README.md index 502d91d282b8a..59cc209d622e3 100644 --- a/llama-index-integrations/readers/llama-index-readers-spotify/README.md +++ b/llama-index-integrations/readers/llama-index-readers-spotify/README.md @@ -1,5 +1,9 @@ # Spotify Loader +```bash +pip install llama-index-readers-spotify +``` + This loader reads your Spotify account and loads saved albums, tracks, or playlists into `Documents`. As a prerequisite, you will need to register with [Spotify for Developers](https://developer.spotify.com) and create an app in order to get a `client_id` and a `client_secret`. You should then set a `redirect_uri` for the app (in the web dashboard under app settings). The `redirect_uri` does not need to be functional. You should then set the `client_id`, `client_secret`, and `redirect_uri` as environmental variables. @@ -13,9 +17,7 @@ As a prerequisite, you will need to register with [Spotify for Developers](https Here's an example usage of the SpotifyReader. It will retrieve your saved albums, unless an optional `collection` argument is passed. Acceptable arguments are "albums", "tracks", and "playlists". ```python -from llama_index import download_loader - -SpotifyReader = download_loader("SpotifyReader") +from llama_index.readers.spotify import SpotifyReader loader = SpotifyReader() documents = loader.load_data() @@ -28,9 +30,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -SpotifyReader = download_loader("SpotifyReader") +from llama_index.readers.spotify import SpotifyReader loader = SpotifyReader() documents = loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-stripe-docs/README.md b/llama-index-integrations/readers/llama-index-readers-stripe-docs/README.md index 33d669b7aeb49..15dddba1fab8e 100644 --- a/llama-index-integrations/readers/llama-index-readers-stripe-docs/README.md +++ b/llama-index-integrations/readers/llama-index-readers-stripe-docs/README.md @@ -1,5 +1,9 @@ # StripeDocs Loader +```bash +pip install llama-index-readers-stripe-docs +``` + This loader asynchronously loads data from the [Stripe documentation](https://stripe.com/docs). It iterates through the Stripe sitemap to get all `/docs` references. It is based on the [Async Website Loader](https://llamahub.ai/l/web-async_web). @@ -7,8 +11,8 @@ It is based on the [Async Website Loader](https://llamahub.ai/l/web-async_web). ## Usage ```python -from llama_index import VectorStoreIndex -from llama_hub.stripe_docs import StripeDocsReader +from llama_index.core import VectorStoreIndex +from llama_index.readers.stripe_docs import StripeDocsReader loader = StripeDocsReader() documents = loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-telegram/README.md b/llama-index-integrations/readers/llama-index-readers-telegram/README.md index 0ee82e3344510..6b05fdc064223 100644 --- a/llama-index-integrations/readers/llama-index-readers-telegram/README.md +++ b/llama-index-integrations/readers/llama-index-readers-telegram/README.md @@ -1,5 +1,9 @@ # Telegram Loader +```bash +pip install llama-index-readers-telegram +``` + This loader fetches posts/chat messages/comments from Telegram channels or chats into `Document`s. Before working with Telegram’s API, you need to get your own API ID and hash: @@ -31,9 +35,8 @@ If the `.session` file already existed, it will not login again, so be aware of To use this loader, you simply need to pass in a entity name. ```python -from llama_index.core import download_loader +from llama_index.readers.telegram import TelegramReader -TelegramReader = download_loader("TelegramReader") loader = TelegramReader( session_name="[YOUR_SESSION_NAME]", api_id="[YOUR_API_ID]", diff --git a/llama-index-integrations/readers/llama-index-readers-trello/README.md b/llama-index-integrations/readers/llama-index-readers-trello/README.md index 787e53605bb80..aaf2fe018b78a 100644 --- a/llama-index-integrations/readers/llama-index-readers-trello/README.md +++ b/llama-index-integrations/readers/llama-index-readers-trello/README.md @@ -1,5 +1,9 @@ # Trello Loader +```bash +pip install llama-index-readers-trello +``` + This loader loads documents from Trello. The user specifies an API key and API token to initialize the TrelloReader. They then specify a board_id to load in the corresponding Document objects representing Trello cards. @@ -8,10 +12,9 @@ load in the corresponding Document objects representing Trello cards. Here's an example usage of the TrelloReader. ```python -from llama_index import download_loader import os -TrelloReader = download_loader("TrelloReader") +from llama_index.readers.trello import TrelloReader reader = TrelloReader("", "") documents = reader.load_data(board_id="") diff --git a/llama-index-integrations/readers/llama-index-readers-weather/README.md b/llama-index-integrations/readers/llama-index-readers-weather/README.md index f20eb54baa6cf..93de04fc5ceef 100644 --- a/llama-index-integrations/readers/llama-index-readers-weather/README.md +++ b/llama-index-integrations/readers/llama-index-readers-weather/README.md @@ -1,5 +1,9 @@ # Weather Loader +```bash +pip install llama-index-readers-weather +``` + This loader fetches the weather data from the [OpenWeatherMap](https://openweathermap.org/api)'s OneCall API, using the `pyowm` Python package. You must initialize the loader with your OpenWeatherMap API token, and then pass in the names of the cities you want the weather data for. OWM's One Call API provides the following weather data for any geographical coordinate: - Current weather - Hourly forecast for 48 hours - Daily forecast for 7 days @@ -9,9 +13,7 @@ OWM's One Call API provides the following weather data for any geographical coor To use this loader, you need to pass in an array of city names (eg. [chennai, chicago]). Pass in the country codes as well for better accuracy. ```python -from llama_index import download_loader - -WeatherReader = download_loader("WeatherReader") +from llama_index.readers.weather import WeatherReader loader = WeatherReader(token="[YOUR_TOKEN]") documents = loader.load_data(places=["Chennai, IN", "Dublin, IE"]) diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/async_web/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/async_web/README.md index 44c8985196a36..0706ae999e574 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/async_web/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/async_web/README.md @@ -1,5 +1,9 @@ # Async Website Loader +```bash +pip install llama-index-readers-web +``` + This loader is an asynchronous web scraper that fetches the text from static websites by converting the HTML to text. ## Usage @@ -7,7 +11,7 @@ This loader is an asynchronous web scraper that fetches the text from static web To use this loader, you need to pass in an array of URLs. ```python -from llama_index.readers.web.async_web.base import AsyncWebPageReader +from llama_index.readers.web import AsyncWebPageReader # for jupyter notebooks uncomment the following two lines of code: # import nest_asyncio diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/beautiful_soup_web/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/beautiful_soup_web/README.md index 331cd5fce7661..f3506e6818ad6 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/beautiful_soup_web/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/beautiful_soup_web/README.md @@ -1,5 +1,9 @@ # Beautiful Soup Website Loader +```bash +pip install llama-index-readers-web +``` + This loader is a web scraper that fetches the text from websites using the `Beautiful Soup` (aka `bs4`) Python package. Furthermore, the flexibility of Beautiful Soup allows for custom templates that enable the loader to extract the desired text from specific website designs, such as Substack. Check out the code to see how to add your own. ## Usage @@ -7,9 +11,7 @@ This loader is a web scraper that fetches the text from websites using the `Beau To use this loader, you need to pass in an array of URLs. ```python -from llama_index import download_loader - -BeautifulSoupWebReader = download_loader("BeautifulSoupWebReader") +from llama_index.readers.web import BeautifulSoupWebReader loader = BeautifulSoupWebReader() documents = loader.load_data(urls=["https://google.com"]) @@ -38,9 +40,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -BeautifulSoupWebReader = download_loader("BeautifulSoupWebReader") +from llama_index.readers.web import BeautifulSoupWebReader loader = BeautifulSoupWebReader() documents = loader.load_data(urls=["https://google.com"]) @@ -53,12 +55,12 @@ index.query("What language is on this website?") Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -BeautifulSoupWebReader = download_loader("BeautifulSoupWebReader") +from llama_index.readers.web import BeautifulSoupWebReader loader = BeautifulSoupWebReader() documents = loader.load_data(urls=["https://google.com"]) diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/knowledge_base/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/knowledge_base/README.md index 816e70f3e7a61..397380d64829d 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/knowledge_base/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/knowledge_base/README.md @@ -1,5 +1,9 @@ # Knowledge Base Website Loader +```bash +pip install llama-index-readers-web +``` + This loader is a web crawler and scraper that fetches text content from websites hosting public knowledge bases. Examples are the [Intercom help center](https://www.intercom.com/help/en/) or the [Robinhood help center](https://robinhood.com/us/en/support/). Typically these sites have a directory structure with several sections and many articles in each section. This loader crawls and finds all links that match the article path provided, and scrapes the content of each article. This can be used to create bots that answer customer questions based on public documentation. It uses [Playwright](https://playwright.dev/python/) to drive a browser. This reduces the chance of getting blocked by Cloudflare or other CDNs, but makes it a bit more challenging to run on cloud services. @@ -17,9 +21,7 @@ This installs the browsers that Playwright requires. To use this loader, you need to pass in the root URL and the string to search for in the URL to tell if the crawler has reached an article. You also need to pass in several CSS selectors so the cralwer knows which links to follow and which elements to extract content from. use ```python -from llama_index import download_loader - -KnowledgeBaseWebReader = download_loader("KnowledgeBaseWebReader") +from llama_index.readers.web import KnowledgeBaseWebReader loader = KnowledgeBaseWebReader() documents = loader.load_data( @@ -39,9 +41,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -KnowledgeBaseWebReader = download_loader("KnowledgeBaseWebReader") +from llama_index.readers.web import KnowledgeBaseWebReader loader = KnowledgeBaseWebReader() documents = loader.load_data( @@ -61,12 +63,12 @@ index.query("What languages does Intercom support?") Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -KnowledgeBaseWebReader = download_loader("KnowledgeBaseWebReader") +from llama_index.readers.web import KnowledgeBaseWebReader loader = KnowledgeBaseWebReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/main_content_extractor/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/main_content_extractor/README.md index 6fb33b7b5e7b5..1dea93a5890da 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/main_content_extractor/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/main_content_extractor/README.md @@ -1,5 +1,9 @@ # MainContentExtractor Website Loader +```bash +pip install llama-index-readers-web +``` + This loader is a web scraper that fetches the text from static websites using the `MainContentExtractor` Python package. For information on how to extract main content, README in the following github repository @@ -11,9 +15,7 @@ For information on how to extract main content, README in the following github r To use this loader, you need to pass in an array of URLs. ```python -from llama_index import download_loader - -MainContentExtractorReader = download_loader("MainContentExtractorReader") +from llama_index.readers.web import MainContentExtractorReader loader = MainContentExtractorReader() documents = loader.load_data(urls=["https://google.com"]) @@ -24,9 +26,9 @@ documents = loader.load_data(urls=["https://google.com"]) ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -MainContentExtractorReader = download_loader("MainContentExtractorReader") +from llama_index.readers.web import MainContentExtractorReader loader = MainContentExtractorReader() documents = loader.load_data(urls=["https://google.com"]) @@ -39,12 +41,12 @@ index.query("What language is on this website?") Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -MainContentExtractorReader = download_loader("MainContentExtractorReader") +from llama_index.readers.web import MainContentExtractorReader loader = MainContentExtractorReader() documents = loader.load_data(urls=["https://google.com"]) diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/news/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/news/README.md index a20e912379d80..b56b7b83265b4 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/news/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/news/README.md @@ -1,5 +1,9 @@ # News Article Loader +```bash +pip install llama-index-readers-web +``` + This loader makes use of the `newspaper3k` library to parse web page urls which have news articles in them. @@ -12,7 +16,7 @@ pip install newspaper3k Pass in an array of individual page URLs: ```python -from llama_index.readers.web.news import NewsArticleReader +from llama_index.readers.web import NewsArticleReader reader = NewsArticleReader(use_nlp=False) documents = reader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/readability_web/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/readability_web/README.md index 75da465912bc2..d41b4fe2d97af 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/readability_web/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/readability_web/README.md @@ -1,5 +1,9 @@ # Readability Webpage Loader +```bash +pip install llama-index-readers-web +``` + Extracting relevant information from a fully rendered web page. During the processing, it is always assumed that web pages used as data sources contain textual content. @@ -13,9 +17,7 @@ It is particularly effective for websites that use client-side rendering. To use this loader, you need to pass in a single of URL. ```python -from llama_index import download_loader - -ReadabilityWebPageReader = download_loader("ReadabilityWebPageReader") +from llama_index.readers.web import ReadabilityWebPageReader # or set proxy server for playwright: loader = ReadabilityWebPageReader(proxy="http://your-proxy-server:port") # For some specific web pages, you may need to set "wait_until" to "networkidle". loader = ReadabilityWebPageReader(wait_until="networkidle") @@ -33,9 +35,7 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import download_loader - -ReadabilityWebPageReader = download_loader("ReadabilityWebPageReader") +from llama_index.readers.web import ReadabilityWebPageReader loader = ReadabilityWebPageReader() documents = loader.load_data( @@ -51,12 +51,12 @@ print(index.query("What is pages?")) Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -ReadabilityWebPageReader = download_loader("ReadabilityWebPageReader") +from llama_index.readers.web import ReadabilityWebPageReader loader = ReadabilityWebPageReader() documents = loader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss/README.md index 4431bbb140f28..5e4e4e5d5440b 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss/README.md @@ -1,5 +1,9 @@ # RSS Loader +```bash +pip install llama-index-readers-web +``` + This loader allows fetching text from an RSS feed. It uses the `feedparser` module to fetch the feed and optionally the `html2text` module to sanitize it. @@ -8,9 +12,7 @@ to fetch the feed and optionally the `html2text` module to sanitize it. To use this loader, pass in an array of URL's. ```python -from llama_index import download_loader - -RssReader = download_loader("RssReader") +from llama_index.readers.web import RssReader reader = RssReader() documents = reader.load_data( diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss_news/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss_news/README.md index 7b6965399f4e6..fb345385a3be8 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss_news/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/rss_news/README.md @@ -9,7 +9,7 @@ To use this loader, pass in an array of URLs of RSS feeds. It will download the combine them: ```python -from llama_index.readers.web.rss_news import RSSNewsReader +from llama_index.core.readers.web.rss_news import RSSNewsReader urls = [ "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml", diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/simple_web/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/simple_web/README.md index f14354eb2016a..b6f9d0ffa433a 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/simple_web/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/simple_web/README.md @@ -1,5 +1,9 @@ # Simple Website Loader +```bash +pip install llama-index-readers-web +``` + This loader is a simple web scraper that fetches the text from static websites by converting the HTML to text. ## Usage @@ -7,9 +11,7 @@ This loader is a simple web scraper that fetches the text from static websites b To use this loader, you need to pass in an array of URLs. ```python -from llama_index import download_loader - -SimpleWebPageReader = download_loader("SimpleWebPageReader") +from llama_index.readers.web import SimpleWebPageReader loader = SimpleWebPageReader() documents = loader.load_data(urls=["https://google.com"]) @@ -22,9 +24,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -SimpleWebPageReader = download_loader("SimpleWebPageReader") +from llama_index.readers.web import SimpleWebPageReader loader = SimpleWebPageReader() documents = loader.load_data(urls=["https://google.com"]) @@ -37,12 +39,12 @@ index.query("What language is on this website?") Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -SimpleWebPageReader = download_loader("SimpleWebPageReader") +from llama_index.readers.web import SimpleWebPageReader loader = SimpleWebPageReader() documents = loader.load_data(urls=["https://google.com"]) diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/sitemap/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/sitemap/README.md index 67066ecceb2e8..b7b5f557fe368 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/sitemap/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/sitemap/README.md @@ -1,5 +1,9 @@ # Sitemap Loader +```bash +pip install llama-index-readers-web +``` + This loader is an asynchronous web scraper that fetches the text from static websites by using its sitemap and optionally converting the HTML to text. It is based on the [Async Website Loader](https://llama-hub-ui.vercel.app/l/web-async_web) @@ -9,7 +13,7 @@ It is based on the [Async Website Loader](https://llama-hub-ui.vercel.app/l/web- To use this loader, you just declare the sitemap.xml url like this: ```python -from llama_index.readers.web.sitemap import SitemapReader +from llama_index.readers.web import SitemapReader # for jupyter notebooks uncomment the following two lines of code: # import nest_asyncio diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/trafilatura_web/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/trafilatura_web/README.md index 2dc5b29dadee6..a75908bb1d6c9 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/trafilatura_web/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/trafilatura_web/README.md @@ -1,5 +1,9 @@ # Trafilatura Website Loader +```bash +pip install llama-index-readers-web +``` + This loader is a web scraper that fetches the text from static websites using the `trafilatura` Python package. ## Usage @@ -7,9 +11,7 @@ This loader is a web scraper that fetches the text from static websites using th To use this loader, you need to pass in an array of URLs. ```python -from llama_index import download_loader - -TrafilaturaWebReader = download_loader("TrafilaturaWebReader") +from llama_index.readers.web import TrafilaturaWebReader loader = TrafilaturaWebReader() documents = loader.load_data(urls=["https://google.com"]) @@ -22,9 +24,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -TrafilaturaWebReader = download_loader("TrafilaturaWebReader") +from llama_index.readers.web import TrafilaturaWebReader loader = TrafilaturaWebReader() documents = loader.load_data(urls=["https://google.com"]) @@ -37,12 +39,12 @@ index.query("What language is on this website?") Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -TrafilaturaWebReader = download_loader("TrafilaturaWebReader") +from llama_index.readers.web import TrafilaturaWebReader loader = TrafilaturaWebReader() documents = loader.load_data(urls=["https://google.com"]) diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/unstructured_web/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/unstructured_web/README.md index de555a882e7b0..671e1d915bd3b 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/unstructured_web/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/unstructured_web/README.md @@ -1,14 +1,16 @@ # Unstructured.io URL Loader +```bash +pip install llama-index-readers-web +``` + This loader extracts the text from URLs using [Unstructured.io](https://github.com/Unstructured-IO/unstructured). The partition_html function partitions an HTML document and returns a list of document Element objects. ## Usage ```python -from llama_index import download_loader - -UnstructuredURLLoader = download_loader("UnstructuredURLLoader") +from llama_index.readers.web import UnstructuredURLLoader urls = [ "https://www.understandingwar.org/backgrounder/russian-offensive-campaign-assessment-february-8-2023", diff --git a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/whole_site/README.md b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/whole_site/README.md index 4a7f9268f950f..7a758d467393c 100644 --- a/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/whole_site/README.md +++ b/llama-index-integrations/readers/llama-index-readers-web/llama_index/readers/web/whole_site/README.md @@ -1,5 +1,9 @@ # WholeSiteReader +```bash +pip install llama-index-readers-web +``` + The WholeSiteReader is a sophisticated web scraping tool that employs a breadth-first search (BFS) algorithm. It's designed to methodically traverse and extract content from entire websites, focusing specifically on predefined URL paths. ## Features @@ -10,9 +14,8 @@ The WholeSiteReader is a sophisticated web scraping tool that employs a breadth- - **Selenium-Based:** Leverages Selenium for dynamic interaction with web pages, supporting JavaScript-rendered content. ```python -from llama_index import download_loader +from llama_index.readers.web import WholeSiteReader -WholeSiteReader = download_loader("WholeSiteReader") # Initialize the scraper with a prefix URL and maximum depth scraper = WholeSiteReader( prefix="https://www.paulgraham.com/", max_depth=10 # Example prefix @@ -31,9 +34,9 @@ This loader is designed to be used as a way to load data into [LlamaIndex](https ### LlamaIndex ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader -WholeSiteReader = download_loader("WholeSiteReader") +from llama_index.readers.web import WholeSiteReader # Initialize the scraper with a prefix URL and maximum depth scraper = WholeSiteReader( @@ -54,12 +57,12 @@ index.query("What language is on this website?") Note: Make sure you change the description of the `Tool` to match your use-case. ```python -from llama_index import VectorStoreIndex, download_loader +from llama_index.core import VectorStoreIndex, download_loader from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI from langchain.chains.conversation.memory import ConversationBufferMemory -WholeSiteReader = download_loader("WholeSiteReader") +from llama_index.readers.web import WholeSiteReader # Initialize the scraper with a prefix URL and maximum depth scraper = WholeSiteReader( diff --git a/llama-index-integrations/readers/llama-index-readers-whatsapp/README.md b/llama-index-integrations/readers/llama-index-readers-whatsapp/README.md index 062ddad7e41c3..7e02868f20b02 100644 --- a/llama-index-integrations/readers/llama-index-readers-whatsapp/README.md +++ b/llama-index-integrations/readers/llama-index-readers-whatsapp/README.md @@ -1,5 +1,9 @@ # Whatsapp chat loader +```bash +pip install llama-index-readers-whatsapp +``` + ## Export a Whatsapp chat 1. Open a chat @@ -16,9 +20,8 @@ For more info see [Whatsapp's Help Center](https://faq.whatsapp.com/118041407917 ```python from pathlib import Path -from llama_index import download_loader -WhatsappChatLoader = download_loader("WhatsappChatLoader") +from llama_index.readers.whatsapp import WhatsappChatLoader path = "whatsapp.txt" loader = WhatsappChatLoader(path=path) diff --git a/llama-index-integrations/readers/llama-index-readers-wordlift/README.md b/llama-index-integrations/readers/llama-index-readers-wordlift/README.md index 38a2b9c3c7948..ef3f79e7acc93 100644 --- a/llama-index-integrations/readers/llama-index-readers-wordlift/README.md +++ b/llama-index-integrations/readers/llama-index-readers-wordlift/README.md @@ -1,5 +1,9 @@ # WordLift Reader +```bash +pip install llama-index-readers-wordlift +``` + The WordLift GraphQL Reader is a connector to fetch and transform data from a WordLift Knowledge Graph using your the WordLift Key. The connector provides a convenient way to load data from WordLift using a GraphQL query and transform it into a list of documents for further processing. ## Usage @@ -15,10 +19,10 @@ Here's an example of how to use the WordLift GraphQL Reader: ```python import json -from llama_index import VectorStoreIndex -from llama_index.readers.schema import Document +from llama_index.core import VectorStoreIndex +from llama_index.core import Document from langchain.llms import OpenAI -from llama_hub.wordlift import WordLiftLoader +from llama_index.readers.wordlift import WordLiftLoader # Set up the necessary configuration options endpoint = "https://api.wordlift.io/graphql" diff --git a/llama-index-integrations/readers/llama-index-readers-wordpress/README.md b/llama-index-integrations/readers/llama-index-readers-wordpress/README.md index e46aadebd707d..7c57428410868 100644 --- a/llama-index-integrations/readers/llama-index-readers-wordpress/README.md +++ b/llama-index-integrations/readers/llama-index-readers-wordpress/README.md @@ -1,5 +1,9 @@ # Wordpress Loader +```bash +pip install llama-index-readers-wordpress +``` + This loader fetches the text from Wordpress blog posts using the Wordpress API. It also uses the BeautifulSoup library to parse the HTML and extract the text from the articles. ## Usage @@ -7,9 +11,7 @@ This loader fetches the text from Wordpress blog posts using the Wordpress API. To use this loader, you need to pass base url of the Wordpress installation (e.g. `https://www.mysite.com`), a username, and an application password for the user (more about application passwords [here](https://www.paidmembershipspro.com/create-application-password-wordpress/)) ```python -from llama_index import download_loader - -WordpressReader = download_loader("WordpressReader") +from llama_index.readers.wordpress import WordpressReader loader = WordpressReader( url="https://www.mysite.com", diff --git a/llama-index-integrations/readers/llama-index-readers-youtube-transcript/README.md b/llama-index-integrations/readers/llama-index-readers-youtube-transcript/README.md index cbd593ac41045..86333283ffed1 100644 --- a/llama-index-integrations/readers/llama-index-readers-youtube-transcript/README.md +++ b/llama-index-integrations/readers/llama-index-readers-youtube-transcript/README.md @@ -1,5 +1,11 @@ # Youtube Transcript Loader +```bash +pip install llama-hub-youtube-transcript + +pip install llama-index-readers-youtube-transcript +``` + This loader fetches the text transcript of Youtube videos using the `youtube_transcript_api` Python package. ## Usage @@ -9,7 +15,7 @@ To use this loader, you will need to first `pip install youtube_transcript_api`. Then, simply pass an array of YouTube links into `load_data`: ```python -from llama_hub.youtube_transcript import YoutubeTranscriptReader +from llama_index.readers.youtube_transcript import YoutubeTranscriptReader loader = YoutubeTranscriptReader() documents = loader.load_data( @@ -22,10 +28,10 @@ Supported URL formats: + youtube.com/watch?v={video_id} (with or without 'www.') To programmatically check if a URL is supported: ```python -from llama_hub.youtube_transcript import is_youtube_video +from llama_index.readers.youtube_transcript.utils import is_youtube_video is_youtube_video("https://youtube.com/watch?v=j83jrh2") # => True is_youtube_video("https://vimeo.com/272134160") # => False ``` -This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. See [here](https://github.com/emptycrown/llama-hub/tree/main) for examples. +This loader is designed to be used as a way to load data into [LlamaIndex](https://github.com/run-llama/llama_index/tree/main/llama_index) and/or subsequently used as a Tool in a [LangChain](https://github.com/hwchase17/langchain) Agent. diff --git a/llama-index-integrations/readers/llama-index-readers-zendesk/README.md b/llama-index-integrations/readers/llama-index-readers-zendesk/README.md index 11aeec68fb237..126da790a3309 100644 --- a/llama-index-integrations/readers/llama-index-readers-zendesk/README.md +++ b/llama-index-integrations/readers/llama-index-readers-zendesk/README.md @@ -1,5 +1,9 @@ # Zendesk Loader +```bash +pip install llama-index-readers-zendesk +``` + This loader fetches the text from Zendesk help articles using the Zendesk API. It also uses the BeautifulSoup library to parse the HTML and extract the text from the articles. ## Usage @@ -7,9 +11,7 @@ This loader fetches the text from Zendesk help articles using the Zendesk API. I To use this loader, you need to pass in the subdomain of a Zendesk account. No authentication is required. You can also set the locale of articles as needed. ```python -from llama_index import download_loader - -ZendeskReader = download_loader("ZendeskReader") +from llama_index.readers.zendesk import ZendeskReader loader = ZendeskReader(zendesk_subdomain="my_subdomain", locale="en-us") documents = loader.load_data() diff --git a/llama-index-integrations/readers/llama-index-readers-zep/README.md b/llama-index-integrations/readers/llama-index-readers-zep/README.md index dae8826f386e9..5b940f9e853b7 100644 --- a/llama-index-integrations/readers/llama-index-readers-zep/README.md +++ b/llama-index-integrations/readers/llama-index-readers-zep/README.md @@ -1,5 +1,9 @@ # Zep Reader +```bash +pip install llama-index-readers-zep +``` + The Zep Reader returns a set of texts corresponding to a text query or embeddings retrieved from a Zep Collection. The Reader is initialized with a Zep API URL and optionally an API key. The Reader can then be used to load data from a Zep Document Collection. @@ -23,14 +27,13 @@ results. import time from uuid import uuid4 -from llama_index.node_parser import SimpleNodeParser -from llama_index.readers.schema import Document +from llama_index.core.node_parser import SimpleNodeParser +from llama_index.core import Document from zep_python import ZepClient from zep_python.document import Document as ZepDocument -from llama_index import download_loader -ZepReader = download_loader("ZepReader") +from llama_index.readers.zep import ZepReader # Create a Zep collection zep_api_url = "http://localhost:8000" # replace with your Zep API URL diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/.gitignore b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/.gitignore new file mode 100644 index 0000000000000..990c18de22908 --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/.gitignore @@ -0,0 +1,153 @@ +llama_index/_static +.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +bin/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +etc/ +include/ +lib/ +lib64/ +parts/ +sdist/ +share/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.ruff_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints +notebooks/ + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pyvenv.cfg + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Jetbrains +.idea +modules/ +*.swp + +# VsCode +.vscode + +# pipenv +Pipfile +Pipfile.lock + +# pyright +pyrightconfig.json diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/BUILD b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/BUILD new file mode 100644 index 0000000000000..0896ca890d8bf --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/BUILD @@ -0,0 +1,3 @@ +poetry_requirements( + name="poetry", +) diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/MAKEFILE b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/MAKEFILE new file mode 100644 index 0000000000000..b9eab05aa3706 --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/MAKEFILE @@ -0,0 +1,17 @@ +GIT_ROOT ?= $(shell git rev-parse --show-toplevel) + +help: ## Show all Makefile targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' + +format: ## Run code autoformatters (black). + pre-commit install + git ls-files | xargs pre-commit run black --files + +lint: ## Run linters: pre-commit (black, ruff, codespell) and mypy + pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files + +test: ## Run tests via pytest. + pytest tests + +watch-docs: ## Build and watch documentation. + sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/ diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/README.md b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/README.md new file mode 100644 index 0000000000000..a24da704669bd --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/README.md @@ -0,0 +1,34 @@ +# VideoDB Retriever + +## Overview + +[VideoDB](https://videodb.io) is a serverless database designed to streamline the storage, search, editing, and streaming of video content. VideoDB offers random access to sequential video data by building indexes and developing interfaces for querying and browsing video content. Learn more at [docs.videodb.io](https://docs.videodb.io). + +## Getting Started + +### Prerequisites + +- Obtain API keys from [VideoDB dashboard](https://console.videodb.io) + +### Installation + +Install the necessary packages with the following command: + +``` +pip install llama-index llama-index-retrievers-videodb videodb +``` + +## Building Your Pipeline + +1. **Data Ingestion**: Upload your videos to VideoDB and leverage its managed indexing for efficient data organization, choosing between semantic or scene-based indexing. +2. **Querying**: Utilize `VideoDBRetriever` to retrieve relevant video segments and `llama-index` for constructing your RAG pipeline, enhancing your LLM's context with video-based insights. + +## 👨‍👩‍👧‍👦 Support & Community + +If you have any questions or feedback. +Please feel free to reach out to us + +- [Discord](https://discord.gg/py9P639jGz) +- [Github](https://github.com/video-db) +- [VideoDB](https://videodb.io) +- [Email](mailto:ashu@videodb.io) diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/BUILD b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/__init__.py b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/__init__.py new file mode 100644 index 0000000000000..d32cd9bb921f7 --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/__init__.py @@ -0,0 +1,4 @@ +from llama_index.retrievers.videodb.base import VideoDBRetriever + + +__all__ = ["VideoDBRetriever"] diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/base.py b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/base.py new file mode 100644 index 0000000000000..df317dac1486a --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/llama_index/retrievers/videodb/base.py @@ -0,0 +1,88 @@ +from llama_index.core.base.base_retriever import BaseRetriever +from llama_index.core.callbacks.base import CallbackManager +from llama_index.core.schema import NodeWithScore, QueryBundle, TextNode + +import logging +import os + +from typing import List, Optional +from videodb import connect + + +logger = logging.getLogger(__name__) + + +class SearchType: + keyword = "keyword" + semantic = "semantic" + + +class VideoDBRetriever(BaseRetriever): + def __init__( + self, + api_key: Optional[str] = None, + collection: Optional[str] = "default", + video: Optional[str] = None, + score_threshold: Optional[float] = 0.2, + result_threshold: Optional[int] = 5, + search_type: Optional[str] = SearchType.semantic, + base_url: Optional[str] = None, + callback_manager: Optional[CallbackManager] = None, + ) -> None: + """Creates a new VideoDB Retriever.""" + if api_key is None: + api_key = os.environ.get("VIDEO_DB_API_KEY") + if api_key is None: + raise Exception( + "No API key provided. Set an API key either as an environment variable (VIDEO_DB_API_KEY) or pass it as an argument." + ) + self._api_key = api_key + self._base_url = base_url + self.video = video + self.collection = collection + self.score_threshold = score_threshold + self.result_threshold = result_threshold + self.search_type = search_type + super().__init__(callback_manager) + + def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: + """Retrieve.""" + kwargs = {"api_key": self._api_key} + if self._base_url is not None: + kwargs["base_url"] = self._base_url + conn = connect(**kwargs) + if self.video: + coll = conn.get_collection(self.collection) + video = coll.get_video(self.video) + search_res = video.search( + query_bundle.query_str, + search_type=self.search_type, + score_threshold=self.score_threshold, + result_threshold=self.result_threshold, + ) + else: + coll = conn.get_collection(self.collection) + search_res = coll.search( + query_bundle.query_str, + search_type=self.search_type, + score_threshold=self.score_threshold, + result_threshold=self.result_threshold, + ) + + nodes = [] + collection_id = search_res.collection_id + for shot in search_res.get_shots(): + score = shot.search_score + textnode = TextNode( + text=shot.text, + metadata={ + "collection_id": collection_id, + "video_id": shot.video_id, + "length": shot.video_length, + "title": shot.video_title, + "start": shot.start, + "end": shot.end, + }, + ) + nodes.append(NodeWithScore(node=textnode, score=score)) + return nodes diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/pyproject.toml b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/pyproject.toml new file mode 100644 index 0000000000000..b0b5c24878516 --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/pyproject.toml @@ -0,0 +1,59 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.codespell] +check-filenames = true +check-hidden = true +# Feel free to un-skip examples, and experimental, you will just need to +# work through many typos (--write-changes and --interactive will help) +skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb" + +[tool.llamahub] +classes = ["VideoDBRetriever"] +contains_example = true +import_path = "llama_index.retrievers.videodb" + +[tool.llamahub.class_authors] +VideoDBRetriever = "video-db" + +[tool.mypy] +disallow_untyped_defs = true +# Remove venv skip when integrated with pre-commit +exclude = ["_static", "build", "examples", "notebooks", "venv"] +ignore_missing_imports = true +python_version = "3.8" + +[tool.poetry] +authors = ["VideoDB "] +description = "llama-index VideoDB Retriever integration" +license = "MIT" +maintainers = ["Rohit Garg "] +name = "llama-index-retrievers-videodb" +packages = [{include = "llama_index/"}] +readme = "README.md" +version = "0.1.0" + +[tool.poetry.dependencies] +python = ">=3.8.1,<4.0" +llama-index-core = "^0.10.0" +videodb = "^0.0" + +[tool.poetry.group.dev.dependencies] +black = {extras = ["jupyter"], version = "<=23.9.1,>=23.7.0"} +codespell = {extras = ["toml"], version = ">=v2.2.6"} +ipython = "8.10.0" +jupyter = "^1.0.0" +mypy = "0.991" +pre-commit = "3.2.0" +pylint = "2.15.10" +pytest = "7.2.1" +pytest-mock = "3.11.1" +ruff = "0.0.292" +tree-sitter-languages = "^1.8.0" +types-Deprecated = ">=0.1.0" +types-PyYAML = "^6.0.12.12" +types-protobuf = "^4.24.0.4" +types-redis = "4.5.5.0" +types-requests = "2.28.11.8" # TODO: unpin when mypy>0.991 +types-setuptools = "67.1.0.0" diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/BUILD b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/BUILD new file mode 100644 index 0000000000000..dabf212d7e716 --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/BUILD @@ -0,0 +1 @@ +python_tests() diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/__init__.py b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/test_retrievers_videdb.py b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/test_retrievers_videdb.py new file mode 100644 index 0000000000000..63e6392e00d6f --- /dev/null +++ b/llama-index-integrations/retrievers/llama-index-retrievers-videodb/tests/test_retrievers_videdb.py @@ -0,0 +1,7 @@ +from llama_index.core.base.base_retriever import BaseRetriever +from llama_index.retrievers.videodb import VideoDBRetriever + + +def test_class(): + names_of_base_classes = [b.__name__ for b in VideoDBRetriever.__mro__] + assert BaseRetriever.__name__ in names_of_base_classes diff --git a/llama-index-integrations/tools/llama-index-tools-arxiv/README.md b/llama-index-integrations/tools/llama-index-tools-arxiv/README.md index 7faed436c7979..32b4f0b0c7927 100644 --- a/llama-index-integrations/tools/llama-index-tools-arxiv/README.md +++ b/llama-index-integrations/tools/llama-index-tools-arxiv/README.md @@ -10,7 +10,7 @@ Here's an example usage of the ArxivToolSpec. ```python from llama_index.tools.arxiv import ArxivToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = ArxivToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-azure-cv/README.md b/llama-index-integrations/tools/llama-index-tools-azure-cv/README.md index d63a39a2020a6..4005de47f5af2 100644 --- a/llama-index-integrations/tools/llama-index-tools-azure-cv/README.md +++ b/llama-index-integrations/tools/llama-index-tools-azure-cv/README.md @@ -12,7 +12,7 @@ Here's an example usage of the AzureCVToolSpec. ```python from llama_index.tools.azure_cv import AzureCVToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = AzureCVToolSpec(api_key="your-key", resource="your-resource") diff --git a/llama-index-integrations/tools/llama-index-tools-azure-speech/README.md b/llama-index-integrations/tools/llama-index-tools-azure-speech/README.md index 227278529a138..d0e573a223a3a 100644 --- a/llama-index-integrations/tools/llama-index-tools-azure-speech/README.md +++ b/llama-index-integrations/tools/llama-index-tools-azure-speech/README.md @@ -8,7 +8,7 @@ This tool has a more extensive example usage documented in a Jupyter notebook [h ```python from llama_index.tools.azure_speech import AzureSpeechToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent speech_tool = AzureSpeechToolSpec(speech_key="your-key", region="eastus") diff --git a/llama-index-integrations/tools/llama-index-tools-azure-translate/README.md b/llama-index-integrations/tools/llama-index-tools-azure-translate/README.md index 69124ea986f0d..ce5554c1b8102 100644 --- a/llama-index-integrations/tools/llama-index-tools-azure-translate/README.md +++ b/llama-index-integrations/tools/llama-index-tools-azure-translate/README.md @@ -13,7 +13,7 @@ This tool has a more extensive example usage documented in a Jupyter notebook [h Here's an example usage of the AzureTranslateToolSpec. ```python -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent from llama_index.tools.azure_translate import AzureTranslateToolSpec translate_tool = AzureTranslateToolSpec(api_key="your-key", region="eastus") diff --git a/llama-index-integrations/tools/llama-index-tools-bing-search/README.md b/llama-index-integrations/tools/llama-index-tools-bing-search/README.md index 762c18eecd7be..d23c9f8773c6e 100644 --- a/llama-index-integrations/tools/llama-index-tools-bing-search/README.md +++ b/llama-index-integrations/tools/llama-index-tools-bing-search/README.md @@ -12,7 +12,7 @@ Here's an example usage of the BingSearchToolSpec. ```python from llama_index.tools.bing_search import BingSearchToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = BingSearchToolSpec(api_key="your-key") diff --git a/llama-index-integrations/tools/llama-index-tools-chatgpt-plugin/README.md b/llama-index-integrations/tools/llama-index-tools-chatgpt-plugin/README.md index 6e607721802c9..e17ade24dd852 100644 --- a/llama-index-integrations/tools/llama-index-tools-chatgpt-plugin/README.md +++ b/llama-index-integrations/tools/llama-index-tools-chatgpt-plugin/README.md @@ -17,7 +17,7 @@ f = requests.get( manifest = yaml.safe_load(f) from llama_index.tools.chatgpt_plugin import ChatGPTPluginToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent from llama_index.tools.requests import RequestsToolSpec requests_spec = RequestsToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-code-interpreter/README.md b/llama-index-integrations/tools/llama-index-tools-code-interpreter/README.md index 879fb398d942e..82bf5353c6056 100644 --- a/llama-index-integrations/tools/llama-index-tools-code-interpreter/README.md +++ b/llama-index-integrations/tools/llama-index-tools-code-interpreter/README.md @@ -12,7 +12,7 @@ Here's an example usage of the CodeInterpreterToolSpec. ```python from llama_index.tools.code_interpreter import CodeInterpreterToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent code_spec = CodeInterpreterToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-cogniswitch/README.md b/llama-index-integrations/tools/llama-index-tools-cogniswitch/README.md index deb89d56e1aff..e90f35f43cb12 100644 --- a/llama-index-integrations/tools/llama-index-tools-cogniswitch/README.md +++ b/llama-index-integrations/tools/llama-index-tools-cogniswitch/README.md @@ -36,7 +36,7 @@ import warnings warnings.filterwarnings("ignore") import os from llama_index.tools.cogniswitch import CogniswitchToolSpec -from llama_index.agent import ReActAgent +from llama_index.core.agent import ReActAgent ``` ### Cogniswitch Credentials and OpenAI token diff --git a/llama-index-integrations/tools/llama-index-tools-database/README.md b/llama-index-integrations/tools/llama-index-tools-database/README.md index 28d91a29c4b01..e7e7e84257a83 100644 --- a/llama-index-integrations/tools/llama-index-tools-database/README.md +++ b/llama-index-integrations/tools/llama-index-tools-database/README.md @@ -10,7 +10,7 @@ Here's an example usage of the DatabaseToolSpec. ```python from llama_index.tools.database import DatabaseToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent db_tools = DatabaseToolSpec( scheme="postgresql", # Database Scheme diff --git a/llama-index-integrations/tools/llama-index-tools-duckduckgo/README.md b/llama-index-integrations/tools/llama-index-tools-duckduckgo/README.md index 81cea3fd91fa7..2eabbc8288d87 100644 --- a/llama-index-integrations/tools/llama-index-tools-duckduckgo/README.md +++ b/llama-index-integrations/tools/llama-index-tools-duckduckgo/README.md @@ -10,7 +10,7 @@ Here's an example usage of the DuckDuckGoSearchToolSpec. ```python from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = DuckDuckGoSearchToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-exa/README.md b/llama-index-integrations/tools/llama-index-tools-exa/README.md index c1bbf5d78588b..0bd2e13572172 100644 --- a/llama-index-integrations/tools/llama-index-tools-exa/README.md +++ b/llama-index-integrations/tools/llama-index-tools-exa/README.md @@ -13,9 +13,7 @@ Here's an example usage of the ExaToolSpec. ```python from llama_index.tools.exa import ExaToolSpec -from llama_index.agent.openai import ( - OpenAIAgent, -) # requires llama-index-agent-openai +from llama_index.agent.openai import OpenAIAgent exa_tool = ExaToolSpec( api_key="your-key", diff --git a/llama-index-integrations/tools/llama-index-tools-graphql/README.md b/llama-index-integrations/tools/llama-index-tools-graphql/README.md index 669e3638ff08a..7924ba3637a16 100644 --- a/llama-index-integrations/tools/llama-index-tools-graphql/README.md +++ b/llama-index-integrations/tools/llama-index-tools-graphql/README.md @@ -12,7 +12,7 @@ This tool works best when the Agent has access to the GraphQL schema for the ser ```python from llama_index.tools.graphql import GraphQLToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = GraphQLToolSpec( url="https://spacex-production.up.railway.app/", diff --git a/llama-index-integrations/tools/llama-index-tools-ionic-shopping/README.md b/llama-index-integrations/tools/llama-index-tools-ionic-shopping/README.md index c4cf64e448b29..57670a76558f3 100644 --- a/llama-index-integrations/tools/llama-index-tools-ionic-shopping/README.md +++ b/llama-index-integrations/tools/llama-index-tools-ionic-shopping/README.md @@ -1,5 +1,9 @@ # LlamaIndex Tools Integration: Ionic Shopping +```bash +pip install llama-index-tools-ionic-shopping +``` + [Ionic](https://ioniccommerce.com) is a plug and play ecommerce marketplace for AI Assistants. By including the Ionic Tool in your agent, you are effortlessly providing your users with the ability to shop and transact directly within your agent, and you’ll get a cut of the transaction. @@ -10,7 +14,7 @@ Llearn more about how [Ionic attributes sales](https://docs.ioniccommerce.com/gu to your agent. Provide your Ionic API Key when instantiating the tool: ```python -from llama_hub.tools.ionic_shopping.base import IonicShoppingToolSpec +from llama_index.tools.ionic_shopping import IonicShoppingToolSpec ionic_tool = IonicShoppingToolSpec(api_key="").to_tool_list() ``` @@ -21,8 +25,10 @@ Try it out using the [Jupyter notebook](https://github.com/run-llama/llama-hub/b ```python import openai -from llama_index.agent import OpenAIAgent # requires llama-index-agent-openai -from llama_hub.tools.ionic_shopping.base import IonicShoppingToolSpec +from llama_index.core.agent import ( + OpenAIAgent, +) # requires llama-index-agent-openai +from llama_index.tools.ionic_shopping import IonicShoppingToolSpec openai.api_key = "sk-api-key" diff --git a/llama-index-integrations/tools/llama-index-tools-metaphor/README.md b/llama-index-integrations/tools/llama-index-tools-metaphor/README.md index 067f5964aece5..21f0d0514dc5d 100644 --- a/llama-index-integrations/tools/llama-index-tools-metaphor/README.md +++ b/llama-index-integrations/tools/llama-index-tools-metaphor/README.md @@ -19,7 +19,7 @@ Here's an example usage of the MetaphorToolSpec. ```python from llama_index.tools.metaphor import MetaphorToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent metaphor_tool = MetaphorToolSpec( api_key="your-key", diff --git a/llama-index-integrations/tools/llama-index-tools-multion/README.md b/llama-index-integrations/tools/llama-index-tools-multion/README.md index 9788c99f3c1aa..5eb221b6d3560 100644 --- a/llama-index-integrations/tools/llama-index-tools-multion/README.md +++ b/llama-index-integrations/tools/llama-index-tools-multion/README.md @@ -1,5 +1,9 @@ # MultiOn Tool +```bash +pip install llama-index-tools-multion +``` + This tool connects to [MultiOn](https://www.multion.ai/) to enable your agent to easily connect to the internet through your Chrome Web browser and act on your behalf @@ -13,8 +17,8 @@ This tool has more a extensive example usage documented in a Jupyter notebook [h Here's an example usage of the MultionToolSpec. ```python -from llama_index.tools.metaphor import MultionToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.tools.multion import MultionToolSpec +from llama_index.agent.openai import OpenAIAgent multion_tool = MultionToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-neo4j/README.md b/llama-index-integrations/tools/llama-index-tools-neo4j/README.md index 1f30d99f8bc10..9ae5a9c10b4f4 100644 --- a/llama-index-integrations/tools/llama-index-tools-neo4j/README.md +++ b/llama-index-integrations/tools/llama-index-tools-neo4j/README.md @@ -1,5 +1,9 @@ # Neo4j Schema Query Builder +```bash +pip install llama-index-tools-neo4j +``` + The `Neo4jQueryToolSpec` class provides a way to query a Neo4j graph database based on a provided schema definition. The class uses a language model to generate Cypher queries from user questions and has the capability to recover from Cypher syntax errors through a self-healing mechanism. ## Table of Contents @@ -16,9 +20,9 @@ The `Neo4jQueryToolSpec` class provides a way to query a Neo4j graph database ba Initialize the `Neo4jQueryToolSpec` class with: ```python -from llama_index.tools.neo4j_db import Neo4jQueryToolSpec -from llama_index.llms import OpenAI -from llama_index.agent import OpenAIAgent +from llama_index.tools.neo4j import Neo4jQueryToolSpec +from llama_index.llms.openai import OpenAI +from llama_index.agent.openai import OpenAIAgent llm = OpenAI(model="gpt-4", openai_api_key="XXXX-XXXX", temperature=0) diff --git a/llama-index-integrations/tools/llama-index-tools-notion/README.md b/llama-index-integrations/tools/llama-index-tools-notion/README.md index 9b574669358fd..6945e6ad9e458 100644 --- a/llama-index-integrations/tools/llama-index-tools-notion/README.md +++ b/llama-index-integrations/tools/llama-index-tools-notion/README.md @@ -10,7 +10,7 @@ Here's an example usage of the NotionToolSpec. ```python from llama_index.tools.notion import NotionToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = NotionToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-openai/README.md b/llama-index-integrations/tools/llama-index-tools-openai/README.md index f91e6ad6d7b63..fb1c3367fcbe5 100644 --- a/llama-index-integrations/tools/llama-index-tools-openai/README.md +++ b/llama-index-integrations/tools/llama-index-tools-openai/README.md @@ -9,9 +9,7 @@ This tool has a more extensive example usage documented in a Jupyter notebook [h ### Usage with Agent ```python -from llama_index.tools.openai.image_generation import ( - OpenAIImageGenerationToolSpec, -) +from llama_index.tools.openai import OpenAIImageGenerationToolSpec image_generation_tool = OpenAIImageGenerationToolSpec( api_key=os.environ["OPENAI_API_KEY"] @@ -32,9 +30,7 @@ print(response) ### Usage directly ```python -from llama_index.tools.openai.image_generation import ( - OpenAIImageGenerationToolSpec, -) +from llama_index.tools.openai import OpenAIImageGenerationToolSpec image_generation_tool = OpenAIImageGenerationToolSpec( api_key=os.environ["OPENAI_API_KEY"] diff --git a/llama-index-integrations/tools/llama-index-tools-openapi/README.md b/llama-index-integrations/tools/llama-index-tools-openapi/README.md index c010ad25e1260..58bda6d03d99a 100644 --- a/llama-index-integrations/tools/llama-index-tools-openapi/README.md +++ b/llama-index-integrations/tools/llama-index-tools-openapi/README.md @@ -1,5 +1,9 @@ # OpenAPI Tool +```bash +pip install llama-index-tools-openapi +``` + This tool loads an OpenAPI spec and allow the Agent to retrieve endpoints and details about endpoints. The RequestsToolSpec can also be loaded into the agent to allow the agent to hit the necessary endpoints with a REST request. ## Usage @@ -9,8 +13,8 @@ This tool has more extensive example usage documented in a Jupyter notebook [her Here's an example usage of the OpenAPIToolSpec. ```python -from llama_hub.tools.openapi import OpenAPIToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.tools.openapi import OpenAPIToolSpec +from llama_index.agent.openai import OpenAIAgent f = requests.get( "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/openai.com/1.2.0/openapi.yaml" diff --git a/llama-index-integrations/tools/llama-index-tools-playgrounds/README.md b/llama-index-integrations/tools/llama-index-tools-playgrounds/README.md index 084401a3da10a..10e41c325aeb3 100644 --- a/llama-index-integrations/tools/llama-index-tools-playgrounds/README.md +++ b/llama-index-integrations/tools/llama-index-tools-playgrounds/README.md @@ -2,6 +2,10 @@ ## playgrounds_subgraph_connector +```bash +pip install llama-index-tools-playgrounds +``` + Playgrounds API is a service provided by [Playgrounds Analytics](https://playgrounds.network) to streamline interfacing with decentralized subgraphs (indexed blockchain datasets). The `PlaygroundsSubgraphConnector` is a tool designed for LLM agents to seamlessly interface with and query subgraphs on The Graph's decentralized network via Playgrounds API. @@ -23,10 +27,8 @@ To utilize the tool, simply initialize it with the appropriate `identifier` (Sub ```python import openai -from llama_index.agent import OpenAIAgent -from llama_index.tools.playgrounds_subgraph_connector import ( - PlaygroundsSubgraphConnectorToolSpec, -) +from llama_index.agent.openai import OpenAIAgent +from llama_index.tools.playgrounds import PlaygroundsSubgraphConnectorToolSpec def simple_test(): @@ -85,10 +87,8 @@ To utilize the tool, initialize it with the appropriate `identifier` (Subgraph I ```python import openai -from llama_index.agent import OpenAIAgent -from llama_index.tools.playgrounds_subgraph_inspector import ( - PlaygroundsSubgraphInspectorToolSpec, -) +from llama_index.agent.openai import OpenAIAgent +from llama_index.tools.playgrounds import PlaygroundsSubgraphInspectorToolSpec def inspect_subgraph( diff --git a/llama-index-integrations/tools/llama-index-tools-python-file/README.md b/llama-index-integrations/tools/llama-index-tools-python-file/README.md index bb21b84c7705e..424ed6212d2f5 100644 --- a/llama-index-integrations/tools/llama-index-tools-python-file/README.md +++ b/llama-index-integrations/tools/llama-index-tools-python-file/README.md @@ -10,7 +10,7 @@ Here's an example usage of the PythonFileToolSpec. ```python from llama_index.tools.python_file import PythonFileToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent pyfile = PythonFileToolSpec("./numpy_linalg.py") diff --git a/llama-index-integrations/tools/llama-index-tools-requests/README.md b/llama-index-integrations/tools/llama-index-tools-requests/README.md index ae2b99dc1a7e1..541f772c584d3 100644 --- a/llama-index-integrations/tools/llama-index-tools-requests/README.md +++ b/llama-index-integrations/tools/llama-index-tools-requests/README.md @@ -12,7 +12,7 @@ Here's an example usage of the RequestsToolSpec. ```python from llama_index.tools.requests import RequestsToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent domain_headers = { "api.openai.com": { diff --git a/llama-index-integrations/tools/llama-index-tools-shopify/README.md b/llama-index-integrations/tools/llama-index-tools-shopify/README.md index 32f530f504e77..c316c2f41f5f4 100644 --- a/llama-index-integrations/tools/llama-index-tools-shopify/README.md +++ b/llama-index-integrations/tools/llama-index-tools-shopify/README.md @@ -8,12 +8,16 @@ This tool has more extensive example usage documented in a Jupyter notebook [her In particular, the tool is very effective when combined with a method of retrieving data from the GraphQL schema definition. +```bash +pip install llama-index llama-index-readers-file llama-index-tools-shopify unstructured +``` + ```python from llama_index.tools.shopify import ShopifyToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent -from llama_index.file.unstructured import UnstructuredReader -from llama_index.tools.ondemand_loader_tool import OnDemandLoaderTool +from llama_index.readers.file import UnstructuredReader +from llama_index.core.tools.ondemand_loader_tool import OnDemandLoaderTool documentation_tool = OnDemandLoaderTool.from_defaults( UnstructuredReader(), diff --git a/llama-index-integrations/tools/llama-index-tools-slack/README.md b/llama-index-integrations/tools/llama-index-tools-slack/README.md index 50eef495a8227..77a5fc2fdd4c9 100644 --- a/llama-index-integrations/tools/llama-index-tools-slack/README.md +++ b/llama-index-integrations/tools/llama-index-tools-slack/README.md @@ -6,7 +6,7 @@ This tool fetches the text from a list of Slack channels. You will need to initi ```python from llama_index.tools.slack import SlackToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = SlackToolSpec(slack_token="token") diff --git a/llama-index-integrations/tools/llama-index-tools-tavily-research/README.md b/llama-index-integrations/tools/llama-index-tools-tavily-research/README.md index 36ed8c5aee8a2..60a1bc317be0f 100644 --- a/llama-index-integrations/tools/llama-index-tools-tavily-research/README.md +++ b/llama-index-integrations/tools/llama-index-tools-tavily-research/README.md @@ -20,7 +20,7 @@ Here's an example usage of the TavilyToolSpec. ```python from llama_index.tools.tavily_research import TavilyToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tavily_tool = TavilyToolSpec( api_key="your-key", diff --git a/llama-index-integrations/tools/llama-index-tools-text-to-image/README.md b/llama-index-integrations/tools/llama-index-tools-text-to-image/README.md index 32c697453d579..5c6a010901235 100644 --- a/llama-index-integrations/tools/llama-index-tools-text-to-image/README.md +++ b/llama-index-integrations/tools/llama-index-tools-text-to-image/README.md @@ -10,7 +10,7 @@ Another example showcases retrieval augmentation over a knowledge corpus with te ```python from llama_index.tools.text_to_image import TextToImageToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent openai.api_key = "sk-your-key" tool_spec = TextToImageToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-vector-db/README.md b/llama-index-integrations/tools/llama-index-tools-vector-db/README.md index d7a7140a7f96f..69f377837f590 100644 --- a/llama-index-integrations/tools/llama-index-tools-vector-db/README.md +++ b/llama-index-integrations/tools/llama-index-tools-vector-db/README.md @@ -6,9 +6,9 @@ This tool wraps a VectorStoreIndex and enables a agent to call it with queries a ```python from llama_index.tools.vector_db import VectorDB -from llama_index.agent import OpenAIAgent -from llama_index.vector_stores.types import VectorStoreInfo -from llama_index import VectorStoreIndex +from llama_index.agent.openai import OpenAIAgent +from llama_index.core.vector_stores import VectorStoreInfo +from llama_index.core import VectorStoreIndex index = VectorStoreIndex(nodes=nodes) tool_spec = VectorDB(index=index) diff --git a/llama-index-integrations/tools/llama-index-tools-waii/README.md b/llama-index-integrations/tools/llama-index-tools-waii/README.md index f1b5c6ca291ad..1da3f82813084 100644 --- a/llama-index-integrations/tools/llama-index-tools-waii/README.md +++ b/llama-index-integrations/tools/llama-index-tools-waii/README.md @@ -52,8 +52,8 @@ print(index.query("Which table contains most columns?")) #### Initialize the agent: ```python -from llama_index.agent import OpenAIAgent -from llama_index.llms import OpenAI +from llama_index.agent.openai import OpenAIAgent +from llama_index.llms.openai import OpenAI agent = OpenAIAgent.from_tools( waii_tool.to_tool_list(), llm=OpenAI(model="gpt-4-1106-preview") diff --git a/llama-index-integrations/tools/llama-index-tools-weather/README.md b/llama-index-integrations/tools/llama-index-tools-weather/README.md index 251f674566450..434a965cca14e 100644 --- a/llama-index-integrations/tools/llama-index-tools-weather/README.md +++ b/llama-index-integrations/tools/llama-index-tools-weather/README.md @@ -13,7 +13,7 @@ Here's an example usage of the OpenWeatherMapToolSpec. ```python from llama_index.tools.weather import OpenWeatherMapToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = OpenWeatherMapToolSpec(key="...") diff --git a/llama-index-integrations/tools/llama-index-tools-wikipedia/README.md b/llama-index-integrations/tools/llama-index-tools-wikipedia/README.md index 0b6915d30da48..9c8e83aa66f0b 100644 --- a/llama-index-integrations/tools/llama-index-tools-wikipedia/README.md +++ b/llama-index-integrations/tools/llama-index-tools-wikipedia/README.md @@ -8,7 +8,7 @@ This tool has more extensive example usage documented in a Jupyter notebook [her ```python from llama_index.tools.wikipedia import WikipediaToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = WikipediaToolSpec() diff --git a/llama-index-integrations/tools/llama-index-tools-wolfram-alpha/README.md b/llama-index-integrations/tools/llama-index-tools-wolfram-alpha/README.md index d7dc202b317fb..c6619a7015cae 100644 --- a/llama-index-integrations/tools/llama-index-tools-wolfram-alpha/README.md +++ b/llama-index-integrations/tools/llama-index-tools-wolfram-alpha/README.md @@ -12,7 +12,7 @@ Here's an example usage of the WolframAlphaToolSpec. ```python from llama_index.tools.wolfram_alpha import WolframAlphaToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent wolfram_spec = WolframAlphaToolSpec(app_id="API-key") diff --git a/llama-index-integrations/tools/llama-index-tools-yahoo-finance/README.md b/llama-index-integrations/tools/llama-index-tools-yahoo-finance/README.md index 21779bb953c08..b2c3a4ef84692 100644 --- a/llama-index-integrations/tools/llama-index-tools-yahoo-finance/README.md +++ b/llama-index-integrations/tools/llama-index-tools-yahoo-finance/README.md @@ -8,7 +8,7 @@ Here's an example of how to use this tool: ```python from llama_index.tools.yahoo_finance import YahooFinanceToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent tool_spec = YahooFinanceToolSpec() agent = OpenAIAgent.from_tools(tool_spec.to_tool_list()) diff --git a/llama-index-integrations/tools/llama-index-tools-zapier/README.md b/llama-index-integrations/tools/llama-index-tools-zapier/README.md index d26c068576aa8..cb3060d8299e5 100644 --- a/llama-index-integrations/tools/llama-index-tools-zapier/README.md +++ b/llama-index-integrations/tools/llama-index-tools-zapier/README.md @@ -10,7 +10,7 @@ Here's an example usage of the ZapierToolSpec. ```python from llama_index.tools.zapier import ZapierToolSpec -from llama_index.agent import OpenAIAgent +from llama_index.agent.openai import OpenAIAgent zapier_spec = ZapierToolSpec(api_key="sk-ak-your-key") diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/llama_index/vector_stores/azurecosmosmongo/base.py b/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/llama_index/vector_stores/azurecosmosmongo/base.py index 146807f16a136..9eb6dd10c166e 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/llama_index/vector_stores/azurecosmosmongo/base.py +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/llama_index/vector_stores/azurecosmosmongo/base.py @@ -3,14 +3,16 @@ An index that is built on top of an existing vector store. """ + import logging import os from typing import Any, Dict, List, Optional, cast import pymongo +from llama_index.core.bridge.pydantic import PrivateAttr from llama_index.core.schema import BaseNode, MetadataMode, TextNode from llama_index.core.vector_stores.types import ( - VectorStore, + BasePydanticVectorStore, VectorStoreQuery, VectorStoreQueryResult, ) @@ -23,7 +25,7 @@ logger = logging.getLogger(__name__) -class AzureCosmosDBMongoDBVectorSearch(VectorStore): +class AzureCosmosDBMongoDBVectorSearch(BasePydanticVectorStore): """Azure CosmosDB MongoDB vCore Vector Store. To use, you should have both: @@ -34,6 +36,18 @@ class AzureCosmosDBMongoDBVectorSearch(VectorStore): stores_text: bool = True flat_metadata: bool = True + _collection: Any = PrivateAttr() + _index_name: str = PrivateAttr() + _embedding_key: str = PrivateAttr() + _id_key: str = PrivateAttr() + _text_key: str = PrivateAttr() + _metadata_key: str = PrivateAttr() + _insert_kwargs: dict = PrivateAttr() + _db_name: str = PrivateAttr() + _collection_name: str = PrivateAttr() + _cosmos_search_kwargs: dict = PrivateAttr() + _mongodb_client: Any = PrivateAttr() + def __init__( self, mongodb_client: Optional[Any] = None, @@ -65,6 +79,8 @@ def __init__( contain search options, such as kind, numLists, similarity, and dimensions. insert_kwargs: The kwargs used during `insert`. """ + super().__init__() + if mongodb_client is not None: self._mongodb_client = cast(pymongo.MongoClient, mongodb_client) else: diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/pyproject.toml b/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/pyproject.toml index f3be49f41cbea..ed34b87db49fe 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/pyproject.toml +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-vector-stores-azurecosmosmongo" readme = "README.md" -version = "0.1.2" +version = "0.1.3" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/tests/test_vector_stores_azurecosmosmongo.py b/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/tests/test_vector_stores_azurecosmosmongo.py index 30ed0dd360de6..04f035c4c26ec 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/tests/test_vector_stores_azurecosmosmongo.py +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-azurecosmosmongo/tests/test_vector_stores_azurecosmosmongo.py @@ -1,4 +1,4 @@ -from llama_index.core.vector_stores.types import VectorStore +from llama_index.core.vector_stores.types import BasePydanticVectorStore from llama_index.vector_stores.azurecosmosmongo import AzureCosmosDBMongoDBVectorSearch @@ -6,4 +6,4 @@ def test_class(): names_of_base_classes = [ b.__name__ for b in AzureCosmosDBMongoDBVectorSearch.__mro__ ] - assert VectorStore.__name__ in names_of_base_classes + assert BasePydanticVectorStore.__name__ in names_of_base_classes diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/llama_index/vector_stores/clickhouse/base.py b/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/llama_index/vector_stores/clickhouse/base.py index 7f8d306d6a3b5..c10f2b206153f 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/llama_index/vector_stores/clickhouse/base.py +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/llama_index/vector_stores/clickhouse/base.py @@ -3,15 +3,15 @@ An index that is built on top of an existing ClickHouse cluster. """ + import importlib import json import logging import re from typing import Any, Dict, List, Optional, cast -from pydantic import PrivateAttr - from llama_index.core import ServiceContext +from llama_index.core.bridge.pydantic import PrivateAttr from llama_index.core.schema import ( BaseNode, MetadataMode, diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/pyproject.toml b/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/pyproject.toml index 3989d9967b141..a43bfd3335635 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/pyproject.toml +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-clickhouse/pyproject.toml @@ -27,7 +27,7 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-vector-stores-clickhouse" readme = "README.md" -version = "0.1.2" +version = "0.1.3" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-milvus/llama_index/vector_stores/milvus/base.py b/llama-index-integrations/vector_stores/llama-index-vector-stores-milvus/llama_index/vector_stores/milvus/base.py index 6358c077dc5be..c152cc89c7f45 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-milvus/llama_index/vector_stores/milvus/base.py +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-milvus/llama_index/vector_stores/milvus/base.py @@ -273,7 +273,7 @@ def query(self, query: VectorStoreQuery, **kwargs: Any) -> VectorStoreQueryResul # Convert to string expression string_expr = "" if len(expr) != 0: - string_expr = " and ".join(expr) + string_expr = f" {query.filters.condition.value} ".join(expr) # Perform the search res = self._milvusclient.search( diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/llama_index/vector_stores/opensearch/base.py b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/llama_index/vector_stores/opensearch/base.py index 508ef45752dea..baf04a321c8eb 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/llama_index/vector_stores/opensearch/base.py +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/llama_index/vector_stores/opensearch/base.py @@ -1,7 +1,12 @@ """Elasticsearch/Opensearch vector store.""" + +import asyncio import json import uuid from typing import Any, Dict, Iterable, List, Optional, Union, cast + +import nest_asyncio + from llama_index.core.bridge.pydantic import PrivateAttr from llama_index.core.schema import BaseNode, MetadataMode, TextNode @@ -16,9 +21,9 @@ metadata_dict_to_node, node_to_metadata_dict, ) -from opensearchpy import OpenSearch +from opensearchpy import AsyncOpenSearch from opensearchpy.exceptions import NotFoundError -from opensearchpy.helpers import bulk +from opensearchpy.helpers import async_bulk IMPORT_OPENSEARCH_PY_ERROR = ( "Could not import OpenSearch. Please install it with `pip install opensearch-py`." @@ -29,14 +34,14 @@ MATCH_ALL_QUERY = {"match_all": {}} # type: Dict -def _import_opensearch() -> Any: +def _import_async_opensearch() -> Any: """Import OpenSearch if available, otherwise raise error.""" - return OpenSearch + return AsyncOpenSearch -def _import_bulk() -> Any: +def _import_async_bulk() -> Any: """Import bulk if available, otherwise raise error.""" - return bulk + return async_bulk def _import_not_found_error() -> Any: @@ -44,21 +49,21 @@ def _import_not_found_error() -> Any: return NotFoundError -def _get_opensearch_client(opensearch_url: str, **kwargs: Any) -> Any: - """Get OpenSearch client from the opensearch_url, otherwise raise error.""" +def _get_async_opensearch_client(opensearch_url: str, **kwargs: Any) -> Any: + """Get AsyncOpenSearch client from the opensearch_url, otherwise raise error.""" try: - opensearch = _import_opensearch() + opensearch = _import_async_opensearch() client = opensearch(opensearch_url, **kwargs) except ValueError as e: raise ValueError( - f"OpenSearch client string provided is not in proper format. " + f"AsyncOpenSearch client string provided is not in proper format. " f"Got error: {e} " ) return client -def _bulk_ingest_embeddings( +async def _bulk_ingest_embeddings( client: Any, index_name: str, embeddings: List[List[float]], @@ -71,20 +76,20 @@ def _bulk_ingest_embeddings( max_chunk_bytes: Optional[int] = 1 * 1024 * 1024, is_aoss: bool = False, ) -> List[str]: - """Bulk Ingest Embeddings into given index.""" + """Async Bulk Ingest Embeddings into given index.""" if not mapping: mapping = {} - bulk = _import_bulk() + async_bulk = _import_async_bulk() not_found_error = _import_not_found_error() requests = [] return_ids = [] mapping = mapping try: - client.indices.get(index=index_name) + await client.indices.get(index=index_name) except not_found_error: - client.indices.create(index=index_name, body=mapping) + await client.indices.create(index=index_name, body=mapping) for i, text in enumerate(texts): metadata = metadatas[i] if metadatas else {} @@ -102,9 +107,9 @@ def _bulk_ingest_embeddings( request["_id"] = _id requests.append(request) return_ids.append(_id) - bulk(client, requests, max_chunk_bytes=max_chunk_bytes) + await async_bulk(client, requests, max_chunk_bytes=max_chunk_bytes) if not is_aoss: - client.indices.refresh(index=index_name) + await client.indices.refresh(index=index_name) return return_ids @@ -135,7 +140,8 @@ def _knn_search_query( k: int, filters: Optional[MetadataFilters] = None, ) -> Dict: - """Do knn search. + """ + Do knn search. If there are no filters do approx-knn search. If there are (pre)-filters, do an exhaustive exact knn search using 'painless @@ -243,7 +249,8 @@ def _is_aoss_enabled(http_auth: Any) -> bool: class OpensearchVectorClient: - """Object encapsulating an Opensearch index that has vector search enabled. + """ + Object encapsulating an Opensearch index that has vector search enabled. If the index does not yet exist, it is created during init. Therefore, the underlying index is assumed to either: @@ -311,15 +318,24 @@ def __init__( } }, } - self._os_client = _get_opensearch_client(self._endpoint, **kwargs) + self._os_client = _get_async_opensearch_client(self._endpoint, **kwargs) not_found_error = _import_not_found_error() + + nest_asyncio.apply() + event_loop = asyncio.get_event_loop() try: - self._os_client.indices.get(index=self._index) + event_loop.run_until_complete( + self._os_client.indices.get(index=self._index) + ) except not_found_error: - self._os_client.indices.create(index=self._index, body=idx_conf) - self._os_client.indices.refresh(index=self._index) + event_loop.run_until_complete( + self._os_client.indices.create(index=self._index, body=idx_conf) + ) + event_loop.run_until_complete( + self._os_client.indices.refresh(index=self._index) + ) - def index_results(self, nodes: List[BaseNode], **kwargs: Any) -> List[str]: + async def index_results(self, nodes: List[BaseNode], **kwargs: Any) -> List[str]: """Store results in the index.""" embeddings: List[List[float]] = [] texts: List[str] = [] @@ -331,7 +347,7 @@ def index_results(self, nodes: List[BaseNode], **kwargs: Any) -> List[str]: texts.append(node.get_content(metadata_mode=MetadataMode.NONE)) metadatas.append(node_to_metadata_dict(node, remove_text=True)) - return _bulk_ingest_embeddings( + return await _bulk_ingest_embeddings( self._os_client, self._index, embeddings, @@ -345,16 +361,16 @@ def index_results(self, nodes: List[BaseNode], **kwargs: Any) -> List[str]: is_aoss=self.is_aoss, ) - def delete_doc_id(self, doc_id: str) -> None: - """Delete a document. + async def delete_doc_id(self, doc_id: str) -> None: + """ + Delete a document. Args: doc_id (str): document id """ - body = {"query": {"match": {"metadata.ref_doc_id": doc_id}}} - self._os_client.delete_by_query(index=self._index, body=body) + await self._os_client.delete(index=self._index, id=doc_id) - def query( + async def aquery( self, query_mode: VectorStoreQueryMode, query_str: Optional[str], @@ -380,7 +396,7 @@ def query( ) params = None - res = self._os_client.search( + res = await self._os_client.search( index=self._index, body=search_query, params=params ) nodes = [] @@ -421,7 +437,8 @@ def query( class OpensearchVectorStore(BasePydanticVectorStore): - """Elasticsearch/Opensearch vector store. + """ + Elasticsearch/Opensearch vector store. Args: client (OpensearchVectorClient): Vector index client to use @@ -437,6 +454,7 @@ def __init__( ) -> None: """Initialize params.""" super().__init__() + nest_asyncio.apply() self._client = client @property @@ -449,13 +467,30 @@ def add( nodes: List[BaseNode], **add_kwargs: Any, ) -> List[str]: - """Add nodes to index. + """ + Add nodes to index. Args: nodes: List[BaseNode]: list of nodes with embeddings. """ - self._client.index_results(nodes) + return asyncio.get_event_loop().run_until_complete( + self.async_add(nodes, **add_kwargs) + ) + + async def async_add( + self, + nodes: List[BaseNode], + **add_kwargs: Any, + ) -> List[str]: + """ + Async add nodes to index. + + Args: + nodes: List[BaseNode]: list of nodes with embeddings. + + """ + await self._client.index_results(nodes) return [result.node_id for result in nodes] def delete(self, ref_doc_id: str, **delete_kwargs: Any) -> None: @@ -466,10 +501,35 @@ def delete(self, ref_doc_id: str, **delete_kwargs: Any) -> None: ref_doc_id (str): The doc_id of the document to delete. """ - self._client.delete_doc_id(ref_doc_id) + asyncio.get_event_loop().run_until_complete( + self.adelete(ref_doc_id, **delete_kwargs) + ) + + async def adelete(self, ref_doc_id: str, **delete_kwargs: Any) -> None: + """ + Async delete nodes using with ref_doc_id. + + Args: + ref_doc_id (str): The doc_id of the document to delete. + + """ + await self._client.delete_doc_id(ref_doc_id) def query(self, query: VectorStoreQuery, **kwargs: Any) -> VectorStoreQueryResult: - """Query index for top k most similar nodes. + """ + Query index for top k most similar nodes. + + Args: + query (VectorStoreQuery): Store query object. + + """ + return asyncio.get_event_loop().run_until_complete(self.aquery(query, **kwargs)) + + async def aquery( + self, query: VectorStoreQuery, **kwargs: Any + ) -> VectorStoreQueryResult: + """ + Async query index for top k most similar nodes. Args: query (VectorStoreQuery): Store query object. @@ -477,7 +537,7 @@ def query(self, query: VectorStoreQuery, **kwargs: Any) -> VectorStoreQueryResul """ query_embedding = cast(List[float], query.query_embedding) - return self._client.query( + return await self._client.aquery( query.mode, query.query_str, query_embedding, diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/pyproject.toml b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/pyproject.toml index 88d546fb78ebe..5b2ff54568f5e 100644 --- a/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/pyproject.toml +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/pyproject.toml @@ -27,12 +27,15 @@ exclude = ["**/BUILD"] license = "MIT" name = "llama-index-vector-stores-opensearch" readme = "README.md" -version = "0.1.4" +version = "0.1.6" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" llama-index-core = "^0.10.1" -opensearch-py = "^2.4.2" + +[tool.poetry.dependencies.opensearch-py] +extras = ["async"] +version = "^2.4.2" [tool.poetry.group.dev.dependencies] ipython = "8.10.0" diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/tests/docker-compose.yml b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/tests/docker-compose.yml new file mode 100644 index 0000000000000..753e6b3621132 --- /dev/null +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/tests/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3" + +services: + opensearch: + image: opensearchproject/opensearch:latest + environment: + - discovery.type=single-node + - plugins.security.disabled=true + - OPENSEARCH_INITIAL_ADMIN_PASSWORD=Asd234%@#% + ports: + - "9200:9200" diff --git a/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/tests/test_opensearch_client.py b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/tests/test_opensearch_client.py new file mode 100644 index 0000000000000..399749dd811a3 --- /dev/null +++ b/llama-index-integrations/vector_stores/llama-index-vector-stores-opensearch/tests/test_opensearch_client.py @@ -0,0 +1,158 @@ +import asyncio +import logging +import pytest +import uuid +from typing import List, Generator + +from llama_index.core.schema import TextNode +from llama_index.vector_stores.opensearch import ( + OpensearchVectorClient, + OpensearchVectorStore, +) +from llama_index.core.vector_stores.types import VectorStoreQuery + +## +# Start Opensearch locally +# cd tests +# docker-compose up +# +# Run tests +# pytest test_opensearch_client.py + +logging.basicConfig(level=logging.DEBUG) +evt_loop = asyncio.get_event_loop() + +try: + from opensearchpy import AsyncOpenSearch + + os_client = AsyncOpenSearch("localhost:9200") + evt_loop.run_until_complete(os_client.info()) + opensearch_not_available = False +except (ImportError, Exception): + opensearch_not_available = True +finally: + evt_loop.run_until_complete(os_client.close()) + + +@pytest.mark.skipif(opensearch_not_available, reason="opensearch is not available") +def test_connection() -> None: + assert True + + +@pytest.fixture() +def index_name() -> str: + """Return the index name.""" + return f"test_{uuid.uuid4().hex}" + + +@pytest.fixture() +def os_store(index_name: str) -> Generator[OpensearchVectorStore, None, None]: + client = OpensearchVectorClient( + endpoint="localhost:9200", + index=index_name, + dim=3, + ) + + yield OpensearchVectorStore(client) + + # teardown step + # delete index + evt_loop.run_until_complete(client._os_client.indices.delete(index=index_name)) + # close client aiohttp session + evt_loop.run_until_complete(client._os_client.close()) + + +@pytest.fixture(scope="session") +def node_embeddings() -> List[TextNode]: + return [ + TextNode( + text="lorem ipsum", + id_="c330d77f-90bd-4c51-9ed2-57d8d693b3b0", + # relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="test-0")}, + metadata={ + "author": "Stephen King", + "theme": "Friendship", + }, + embedding=[1.0, 0.0, 0.0], + ), + TextNode( + text="lorem ipsum", + id_="c3d1e1dd-8fb4-4b8f-b7ea-7fa96038d39d", + # relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="test-1")}, + metadata={ + "director": "Francis Ford Coppola", + "theme": "Mafia", + }, + embedding=[0.0, 1.0, 0.0], + ), + TextNode( + text="lorem ipsum", + id_="c3ew11cd-8fb4-4b8f-b7ea-7fa96038d39d", + # relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="test-2")}, + metadata={ + "director": "Christopher Nolan", + }, + embedding=[0.0, 0.0, 1.0], + ), + TextNode( + text="I was taught that the way of progress was neither swift nor easy.", + id_="0b31ae71-b797-4e88-8495-031371a7752e", + # relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="text-3")}, + metadate={ + "author": "Marie Curie", + }, + embedding=[0.0, 0.0, 0.9], + ), + TextNode( + text=( + "The important thing is not to stop questioning." + + " Curiosity has its own reason for existing." + ), + id_="bd2e080b-159a-4030-acc3-d98afd2ba49b", + # relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="text-4")}, + metadate={ + "author": "Albert Einstein", + }, + embedding=[0.0, 0.0, 0.5], + ), + TextNode( + text=( + "I am no bird; and no net ensnares me;" + + " I am a free human being with an independent will." + ), + id_="f658de3b-8cef-4d1c-8bed-9a263c907251", + # relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="text-5")}, + metadate={ + "author": "Charlotte Bronte", + }, + embedding=[0.0, 0.0, 0.3], + ), + ] + + +def count_docs_in_index(os_store: OpensearchVectorStore) -> int: + """Refresh indices and return the count of documents in the index.""" + evt_loop.run_until_complete( + os_store.client._os_client.indices.refresh(index=os_store.client._index) + ) + count = evt_loop.run_until_complete( + os_store.client._os_client.count(index=os_store.client._index) + ) + return count["count"] + + +@pytest.mark.skipif(opensearch_not_available, reason="opensearch is not available") +def test_functionality( + os_store: OpensearchVectorStore, node_embeddings: List[TextNode] +) -> None: + # add + assert len(os_store.add(node_embeddings)) == len(node_embeddings) + # query + exp_node = node_embeddings[3] + query = VectorStoreQuery(query_embedding=exp_node.embedding, similarity_top_k=1) + query_result = os_store.query(query) + assert query_result.nodes + assert query_result.nodes[0].get_content() == exp_node.text + # delete + os_store.delete(exp_node.id_) + assert count_docs_in_index(os_store) == len(node_embeddings) - 1 diff --git a/llama-index-legacy/llama_index/legacy/callbacks/__init__.py b/llama-index-legacy/llama_index/legacy/callbacks/__init__.py index d419baa947969..097353e3bd341 100644 --- a/llama-index-legacy/llama_index/legacy/callbacks/__init__.py +++ b/llama-index-legacy/llama_index/legacy/callbacks/__init__.py @@ -5,7 +5,6 @@ from .open_inference_callback import OpenInferenceCallbackHandler from .schema import CBEvent, CBEventType, EventPayload from .token_counting import TokenCountingHandler -from .uptrain_callback import UpTrainCallbackHandler from .utils import trace_method from .wandb_callback import WandbCallbackHandler @@ -22,5 +21,4 @@ "OpenAIFineTuningHandler", "GradientAIFineTuningHandler", "trace_method", - "UpTrainCallbackHandler", ] diff --git a/llama-index-legacy/llama_index/legacy/callbacks/global_handlers.py b/llama-index-legacy/llama_index/legacy/callbacks/global_handlers.py index c52ed3ed354f1..f191de2181bda 100644 --- a/llama-index-legacy/llama_index/legacy/callbacks/global_handlers.py +++ b/llama-index-legacy/llama_index/legacy/callbacks/global_handlers.py @@ -14,7 +14,6 @@ ) from llama_index.legacy.callbacks.promptlayer_handler import PromptLayerHandler from llama_index.legacy.callbacks.simple_llm_handler import SimpleLLMHandler -from llama_index.legacy.callbacks.uptrain_callback import UpTrainCallbackHandler from llama_index.legacy.callbacks.wandb_callback import WandbCallbackHandler @@ -41,8 +40,6 @@ def create_global_handler(eval_mode: str, **eval_params: Any) -> BaseCallbackHan handler = deepeval_callback_handler(**eval_params) elif eval_mode == "simple": handler = SimpleLLMHandler(**eval_params) - elif eval_mode == "uptrain": - handler = UpTrainCallbackHandler(**eval_params) elif eval_mode == "argilla": handler = argilla_callback_handler(**eval_params) else: diff --git a/llama-index-legacy/llama_index/legacy/core/embeddings/base.py b/llama-index-legacy/llama_index/legacy/core/embeddings/base.py index 263d96dd9eb7d..1e7edcd23cfcf 100644 --- a/llama-index-legacy/llama_index/legacy/core/embeddings/base.py +++ b/llama-index-legacy/llama_index/legacy/core/embeddings/base.py @@ -289,16 +289,13 @@ async def aget_text_embedding_batch( nested_embeddings = [] if show_progress: try: - from tqdm.auto import tqdm - - nested_embeddings = [ - await f - for f in tqdm( - asyncio.as_completed(embeddings_coroutines), - total=len(embeddings_coroutines), - desc="Generating embeddings", - ) - ] + from tqdm.asyncio import tqdm_asyncio + + nested_embeddings = await tqdm_asyncio.gather( + *embeddings_coroutines, + total=len(embeddings_coroutines), + desc="Generating embeddings", + ) except ImportError: nested_embeddings = await asyncio.gather(*embeddings_coroutines) else: diff --git a/llama-index-legacy/llama_index/legacy/embeddings/multi_modal_base.py b/llama-index-legacy/llama_index/legacy/embeddings/multi_modal_base.py index 82f5088099e75..a3a6eba662f82 100644 --- a/llama-index-legacy/llama_index/legacy/embeddings/multi_modal_base.py +++ b/llama-index-legacy/llama_index/legacy/embeddings/multi_modal_base.py @@ -155,16 +155,13 @@ async def aget_image_embedding_batch( nested_embeddings = [] if show_progress: try: - from tqdm.auto import tqdm - - nested_embeddings = [ - await f - for f in tqdm( - asyncio.as_completed(embeddings_coroutines), - total=len(embeddings_coroutines), - desc="Generating image embeddings", - ) - ] + from tqdm.asyncio import tqdm_asyncio + + nested_embeddings = await tqdm_asyncio.gather( + *embeddings_coroutines, + total=len(embeddings_coroutines), + desc="Generating embeddings", + ) except ImportError: nested_embeddings = await asyncio.gather(*embeddings_coroutines) else: diff --git a/llama-index-legacy/llama_index/legacy/retrievers/fusion_retriever.py b/llama-index-legacy/llama_index/legacy/retrievers/fusion_retriever.py index 8b9f2859307f1..caa900bacf9f7 100644 --- a/llama-index-legacy/llama_index/legacy/retrievers/fusion_retriever.py +++ b/llama-index-legacy/llama_index/legacy/retrievers/fusion_retriever.py @@ -127,7 +127,11 @@ def _simple_fusion( for nodes_with_scores in results.values(): for node_with_score in nodes_with_scores: text = node_with_score.node.get_content() - all_nodes[text] = node_with_score + if text in all_nodes: + score = max(node_with_score.score, all_nodes[text].score) + all_nodes[text].score = score + else: + all_nodes[text] = node_with_score return sorted(all_nodes.values(), key=lambda x: x.score or 0.0, reverse=True) diff --git a/llama-index-packs/llama-index-packs-chroma-autoretrieval/README.md b/llama-index-packs/llama-index-packs-chroma-autoretrieval/README.md index 7f70de593e16d..dc46644c7ba66 100644 --- a/llama-index-packs/llama-index-packs-chroma-autoretrieval/README.md +++ b/llama-index-packs/llama-index-packs-chroma-autoretrieval/README.md @@ -31,7 +31,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo vector_store_info = VectorStoreInfo( content_info="brief biography of celebrities", diff --git a/llama-index-packs/llama-index-packs-cogniswitch-agent/README.md b/llama-index-packs/llama-index-packs-cogniswitch-agent/README.md index 476ee35eb431c..0723ca6d75c41 100644 --- a/llama-index-packs/llama-index-packs-cogniswitch-agent/README.md +++ b/llama-index-packs/llama-index-packs-cogniswitch-agent/README.md @@ -42,7 +42,7 @@ llamaindex-cli download-llamapack CogniswitchAgentPack --download-dir ./cs_pack import warnings warnings.filterwarnings("ignore") -from llama_index.core.llama_packs import CogniswitchAgentPack +from llama_index.packs.cogniswitch_agent import CogniswitchAgentPack import os diff --git a/llama-index-packs/llama-index-packs-deeplake-deepmemory-retriever/README.md b/llama-index-packs/llama-index-packs-deeplake-deepmemory-retriever/README.md index 7546a55058a75..2be08da526be6 100644 --- a/llama-index-packs/llama-index-packs-deeplake-deepmemory-retriever/README.md +++ b/llama-index-packs/llama-index-packs-deeplake-deepmemory-retriever/README.md @@ -17,7 +17,7 @@ You can then inspect the files at `./deepmemory_pack` and use them as a template You can download the pack to a `./deepmemory_pack` directory: ```python -from llama_hub.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies DeepMemoryRetriever = download_llama_pack( @@ -31,7 +31,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo nodes = [...] diff --git a/llama-index-packs/llama-index-packs-deeplake-multimodal-retrieval/README.md b/llama-index-packs/llama-index-packs-deeplake-multimodal-retrieval/README.md index 50427fe9d3d16..72fc3f3910702 100644 --- a/llama-index-packs/llama-index-packs-deeplake-multimodal-retrieval/README.md +++ b/llama-index-packs/llama-index-packs-deeplake-multimodal-retrieval/README.md @@ -17,7 +17,7 @@ You can then inspect the files at `./deeplake_multimodal_pack` and use them as a You can download the pack to a `./deeplake_multimodal_pack` directory: ```python -from llama_hub.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies DeepLakeMultimodalRetriever = download_llama_pack( @@ -31,7 +31,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo # collection of image and text nodes nodes = [...] diff --git a/llama-index-packs/llama-index-packs-dense-x-retrieval/README.md b/llama-index-packs/llama-index-packs-dense-x-retrieval/README.md index 4219bb4489931..0b8bf6c6ae2a0 100644 --- a/llama-index-packs/llama-index-packs-dense-x-retrieval/README.md +++ b/llama-index-packs/llama-index-packs-dense-x-retrieval/README.md @@ -29,7 +29,7 @@ You can then inspect the files at `./dense_pack` and use them as a template for You can download the pack to a the `./dense_pack` directory: ```python -from llama_index import SimpleDirectoryReader +from llama_index.core import SimpleDirectoryReader from llama_index.core.llama_pack import download_llama_pack # download and install dependencies diff --git a/llama-index-packs/llama-index-packs-evaluator-benchmarker/README.md b/llama-index-packs/llama-index-packs-evaluator-benchmarker/README.md index 3fff5c2aca9e1..ba1ea63b00cd7 100644 --- a/llama-index-packs/llama-index-packs-evaluator-benchmarker/README.md +++ b/llama-index-packs/llama-index-packs-evaluator-benchmarker/README.md @@ -32,8 +32,8 @@ single-grading evaluation — in this case, the usage flow remains the same. from llama_index.core.llama_dataset import download_llama_dataset from llama_index.core.llama_pack import download_llama_pack from llama_index.core.evaluation import PairwiseComparisonEvaluator -from llama_index.core.llms import OpenAI -from llama_index import ServiceContext +from llama_index.llms.openai import OpenAI +from llama_index.core import ServiceContext # download a LabelledRagDataset from llama-hub pairwise_dataset = download_llama_dataset( diff --git a/llama-index-packs/llama-index-packs-fuzzy-citation/README.md b/llama-index-packs/llama-index-packs-fuzzy-citation/README.md index edb6f077ac1dc..4b90e932aafa6 100644 --- a/llama-index-packs/llama-index-packs-fuzzy-citation/README.md +++ b/llama-index-packs/llama-index-packs-fuzzy-citation/README.md @@ -21,7 +21,7 @@ You can then inspect the files at `./fuzzy_citation_pack` and use them as a temp You can download the pack to a the `./fuzzy_citation_pack` directory: ```python -from llama_index import Document, VectorStoreIndex +from llama_index.core import Document, VectorStoreIndex from llama_index.core.llama_pack import download_llama_pack # download and install dependencies diff --git a/llama-index-packs/llama-index-packs-gmail-openai-agent/README.md b/llama-index-packs/llama-index-packs-gmail-openai-agent/README.md index a8cd8cec53bbd..1e13d1329d6a2 100644 --- a/llama-index-packs/llama-index-packs-gmail-openai-agent/README.md +++ b/llama-index-packs/llama-index-packs-gmail-openai-agent/README.md @@ -43,7 +43,7 @@ agent = gmail_agent_pack.agent response = agent.chat("What is my most recent email?") # Use the tool spec in another agent -from llama_index.core.agents import ReActAgent +from llama_index.core.agent import ReActAgent tool_spec = gmail_agent_pack.tool_spec agent = ReActAgent.from_tools(tool_spec.to_tool_lost()) diff --git a/llama-index-packs/llama-index-packs-koda-retriever/README.md b/llama-index-packs/llama-index-packs-koda-retriever/README.md index 582d67388a106..c703e1467de2f 100644 --- a/llama-index-packs/llama-index-packs-koda-retriever/README.md +++ b/llama-index-packs/llama-index-packs-koda-retriever/README.md @@ -29,7 +29,7 @@ Please see the [examples](./examples/) folder for more specific examples. from llama_index.packs.koda_retriever import KodaRetriever from llama_index.core import VectorStoreIndex from llama_index.llms.openai import OpenAI -from llama_index.embeddings.openai.base import OpenAIEmbedding +from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.core.postprocessor import LLMRerank from llama_index.core import Settings diff --git a/llama-index-packs/llama-index-packs-multidoc-autoretrieval/README.md b/llama-index-packs/llama-index-packs-multidoc-autoretrieval/README.md index d8250aa7652d5..f5ddbcdd8dcc2 100644 --- a/llama-index-packs/llama-index-packs-multidoc-autoretrieval/README.md +++ b/llama-index-packs/llama-index-packs-multidoc-autoretrieval/README.md @@ -31,7 +31,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo import weaviate diff --git a/llama-index-packs/llama-index-packs-nebulagraph-query-engine/README.md b/llama-index-packs/llama-index-packs-nebulagraph-query-engine/README.md index a776ac5c2c001..5b3538fda58c9 100644 --- a/llama-index-packs/llama-index-packs-nebulagraph-query-engine/README.md +++ b/llama-index-packs/llama-index-packs-nebulagraph-query-engine/README.md @@ -37,11 +37,15 @@ From here, you can use the pack, or inspect and modify the pack in `./nebulagrap Then, you can set up the pack like so: +```bash +pip install llama-index-readers-wikipedia +``` + ```python # Load the docs (example of Paleo diet from Wikipedia) -from llama_index import download_loader -WikipediaReader = download_loader("WikipediaReader") +from llama_index.readers.wikipedia import WikipediaReader + loader = WikipediaReader() docs = loader.load_data(pages=["Paleolithic diet"], auto_suggest=False) print(f"Loaded {len(docs)} documents") @@ -75,7 +79,7 @@ nebulagraph_pack = NebulaGraphQueryEnginePack( Optionally, you can pass in the `query_engine_type` from `NebulaGraphQueryEngineType` to construct `NebulaGraphQueryEnginePack`. If `query_engine_type` is not defined, it defaults to Knowledge Graph vector based entity retrieval. ```python -from llama_index.packs.nebulagraph_query_engine.base import ( +from llama_index.core.packs.nebulagraph_query_engine.base import ( NebulaGraphQueryEngineType, ) diff --git a/llama-index-packs/llama-index-packs-neo4j-query-engine/README.md b/llama-index-packs/llama-index-packs-neo4j-query-engine/README.md index c508cc743a6d3..2582fe676a260 100644 --- a/llama-index-packs/llama-index-packs-neo4j-query-engine/README.md +++ b/llama-index-packs/llama-index-packs-neo4j-query-engine/README.md @@ -37,11 +37,15 @@ From here, you can use the pack, or inspect and modify the pack in `./neo4j_pack Then, you can set up the pack like so: +```bash +pip install llama-index-readers-wikipedia +``` + ```python # Load the docs (example of Paleo diet from Wikipedia) -from llama_index import download_loader -WikipediaReader = download_loader("WikipediaReader") +from llama_index.readers.wikipedia import WikipediaReader + loader = WikipediaReader() docs = loader.load_data(pages=["Paleolithic diet"], auto_suggest=False) print(f"Loaded {len(docs)} documents") @@ -63,7 +67,7 @@ neo4j_pack = Neo4jQueryEnginePack( Optionally, you can pass in the `query_engine_type` from `Neo4jQueryEngineType` to construct `Neo4jQueryEnginePack`. If `query_engine_type` is not defined, it defaults to Knowledge Graph vector based entity retrieval. ```python -from llama_index.packs.neo4j_query_engine.base import Neo4jQueryEngineType +from llama_index.core.packs.neo4j_query_engine.base import Neo4jQueryEngineType # create the pack neo4j_pack = Neo4jQueryEnginePack( diff --git a/llama-index-packs/llama-index-packs-rag-cli-local/README.md b/llama-index-packs/llama-index-packs-rag-cli-local/README.md index 90352835e6f1e..ee3ba4a0d2eab 100644 --- a/llama-index-packs/llama-index-packs-rag-cli-local/README.md +++ b/llama-index-packs/llama-index-packs-rag-cli-local/README.md @@ -21,7 +21,7 @@ which makes it hard to load directly. We will show you how to import the agent from these files! ```python -from llama_index.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies download_llama_pack("LocalRAGCLIPack", "./local_rag_cli_pack", skip_load=True) diff --git a/llama-index-packs/llama-index-packs-rag-evaluator/README.md b/llama-index-packs/llama-index-packs-rag-evaluator/README.md index abe136c89642a..d5a6d405f75f8 100644 --- a/llama-index-packs/llama-index-packs-rag-evaluator/README.md +++ b/llama-index-packs/llama-index-packs-rag-evaluator/README.md @@ -25,7 +25,7 @@ built off of its source documents. ```python from llama_index.core.llama_dataset import download_llama_dataset from llama_index.core.llama_pack import download_llama_pack -from llama_index import VectorStoreIndex +from llama_index.core import VectorStoreIndex # download a LabelledRagDataset from llama-hub rag_dataset, documents = download_llama_dataset( diff --git a/llama-index-packs/llama-index-packs-raptor/.gitignore b/llama-index-packs/llama-index-packs-raptor/.gitignore new file mode 100644 index 0000000000000..990c18de22908 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/.gitignore @@ -0,0 +1,153 @@ +llama_index/_static +.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +bin/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +etc/ +include/ +lib/ +lib64/ +parts/ +sdist/ +share/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.ruff_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints +notebooks/ + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pyvenv.cfg + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Jetbrains +.idea +modules/ +*.swp + +# VsCode +.vscode + +# pipenv +Pipfile +Pipfile.lock + +# pyright +pyrightconfig.json diff --git a/llama-index-packs/llama-index-packs-raptor/BUILD b/llama-index-packs/llama-index-packs-raptor/BUILD new file mode 100644 index 0000000000000..09bd1b6726c8f --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/BUILD @@ -0,0 +1,4 @@ +poetry_requirements( + name="poetry", + module_mapping={"umap-learn": ["umap"], "scikit-learn": ["sklearn"]} +) diff --git a/llama-index-packs/llama-index-packs-raptor/Makefile b/llama-index-packs/llama-index-packs-raptor/Makefile new file mode 100644 index 0000000000000..b9eab05aa3706 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/Makefile @@ -0,0 +1,17 @@ +GIT_ROOT ?= $(shell git rev-parse --show-toplevel) + +help: ## Show all Makefile targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' + +format: ## Run code autoformatters (black). + pre-commit install + git ls-files | xargs pre-commit run black --files + +lint: ## Run linters: pre-commit (black, ruff, codespell) and mypy + pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files + +test: ## Run tests via pytest. + pytest tests + +watch-docs: ## Build and watch documentation. + sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/ diff --git a/llama-index-packs/llama-index-packs-raptor/README.md b/llama-index-packs/llama-index-packs-raptor/README.md new file mode 100644 index 0000000000000..4b9b07fb5a549 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/README.md @@ -0,0 +1,78 @@ +# Raptor Retriever LlamaPack + +This LlamaPack shows how to use an implementation of RAPTOR with llama-index, leveraging the RAPTOR pack. + +RAPTOR works by recursively clustering and summarizing clusters in layers for retrieval. + +There two retrieval modes: + +- tree_traversal -- traversing the tree of clusters, performing top-k at each level in the tree. +- collapsed -- treat the entire tree as a giant pile of nodes, perform simple top-k. + +See [the paper](https://arxiv.org/abs/2401.18059) for full algorithm details. + +## CLI Usage + +You can download llamapacks directly using `llamaindex-cli`, which comes installed with the `llama-index` python package: + +```bash +llamaindex-cli download-llamapack RaptorPack --download-dir ./raptor_pack +``` + +You can then inspect/modify the files at `./raptor_pack` and use them as a template for your own project. + +## Code Usage + +You can alternaitvely install the package: + +`pip install llama-index-packs-raptor` + +Then, you can import and initialize the pack! This will perform clustering and summarization over your data. + +```python +from llama_index.packs.raptor import RaptorPack + +pack = RaptorPack(documents, llm=llm, embed_model=embed_model) +``` + +The `run()` function is a light wrapper around `retriever.retrieve()`. + +```python +nodes = pack.run( + "query", + mode="collapsed", # or tree_traversal +) +``` + +You can also use modules individually. + +```python +# get the retriever +retriever = pack.retriever +``` + +## Persistence + +The `RaptorPack` comes with the `RaptorRetriever`, which offers ways of saving/reloading! + +If you are using a remote vector-db, just pass it in + +```python +# Pack usage +pack = RaptorPack(..., vector_store=vector_store) + +# RaptorRetriever usage +retriever = RaptorRetriever(..., vector_store=vector_store) +``` + +Then, to re-connect, just pass in the vector store again and an empty list of documents + +```python +# Pack usage +pack = RaptorPack([], ..., vector_store=vector_store) + +# RaptorRetriever usage +retriever = RaptorRetriever([], ..., vector_store=vector_store) +``` + +Check out the [notebook here for complete details!](https://github.com/run-llama/llama_index/blob/main/llama-index-packs/llama-index-packs-raptor/examples/raptor.ipynb). diff --git a/llama-index-packs/llama-index-packs-raptor/examples/raptor.ipynb b/llama-index-packs/llama-index-packs-raptor/examples/raptor.ipynb new file mode 100644 index 0000000000000..1f550dc52375e --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/examples/raptor.ipynb @@ -0,0 +1,379 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval\n", + "\n", + "This notebook shows how to use an implementation of RAPTOR with llama-index, leveraging the RAPTOR llama-pack.\n", + "\n", + "RAPTOR works by recursively clustering and summarizing clusters in layers for retrieval.\n", + "\n", + "There two retrieval modes:\n", + "- tree_traversal -- traversing the tree of clusters, performing top-k at each level in the tree.\n", + "- collapsed -- treat the entire tree as a giant pile of nodes, perform simple top-k.\n", + "\n", + "See [the paper](https://arxiv.org/abs/2401.18059) for full algorithm details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install llama-index llama-index-packs-raptor llama-index-vector-stores-qdrant" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.packs.raptor import RaptorPack\n", + "\n", + "# optionally download the pack to inspect/modify it yourself!\n", + "# from llama_index.core.llama_pack import download_llama_pack\n", + "# RaptorPack = download_llama_pack(\"RaptorPack\", \"./raptor_pack\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Will not apply HSTS. The HSTS database must be a regular and non-world-writable file.\n", + "ERROR: could not open HSTS store at '/home/loganm/.wget-hsts'. HSTS will be disabled.\n", + "--2024-02-29 22:16:11-- https://arxiv.org/pdf/2401.18059.pdf\n", + "Resolving arxiv.org (arxiv.org)... 151.101.3.42, 151.101.195.42, 151.101.131.42, ...\n", + "Connecting to arxiv.org (arxiv.org)|151.101.3.42|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 2547113 (2.4M) [application/pdf]\n", + "Saving to: ‘./raptor_paper.pdf’\n", + "\n", + "./raptor_paper.pdf 100%[===================>] 2.43M 12.5MB/s in 0.2s \n", + "\n", + "2024-02-29 22:16:12 (12.5 MB/s) - ‘./raptor_paper.pdf’ saved [2547113/2547113]\n", + "\n" + ] + } + ], + "source": [ + "!wget https://arxiv.org/pdf/2401.18059.pdf -O ./raptor_paper.pdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = \"sk-...\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constructing the Clusters/Hierarchy Tree" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core import SimpleDirectoryReader\n", + "\n", + "documents = SimpleDirectoryReader(input_files=[\"./raptor_paper.pdf\"]).load_data()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating embeddings for level 0.\n", + "Performing clustering for level 0.\n", + "Generating summaries for level 0 with 10 clusters.\n", + "Level 0 created summaries/clusters: 10\n", + "Generating embeddings for level 1.\n", + "Performing clustering for level 1.\n", + "Generating summaries for level 1 with 1 clusters.\n", + "Level 1 created summaries/clusters: 1\n", + "Generating embeddings for level 2.\n", + "Performing clustering for level 2.\n", + "Generating summaries for level 2 with 1 clusters.\n", + "Level 2 created summaries/clusters: 1\n" + ] + } + ], + "source": [ + "from llama_index.core.node_parser import SentenceSplitter\n", + "from llama_index.llms.openai import OpenAI\n", + "from llama_index.embeddings.openai import OpenAIEmbedding\n", + "from llama_index.vector_stores.chroma import ChromaVectorStore\n", + "import chromadb\n", + "\n", + "client = chromadb.PersistentClient(path=\"./raptor_paper_db\")\n", + "collection = client.get_or_create_collection(\"raptor\")\n", + "\n", + "vector_store = ChromaVectorStore(chroma_collection=collection)\n", + "\n", + "raptor_pack = RaptorPack(\n", + " documents,\n", + " embed_model=OpenAIEmbedding(\n", + " model=\"text-embedding-3-small\"\n", + " ), # used for embedding clusters\n", + " llm=OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1), # used for generating summaries\n", + " vector_store=vector_store, # used for storage\n", + " similarity_top_k=2, # top k for each layer, or overall top-k for collapsed\n", + " mode=\"collapsed\", # sets default mode\n", + " transformations=[\n", + " SentenceSplitter(chunk_size=400, chunk_overlap=50)\n", + " ], # transformations applied for ingestion\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieval" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "Specifically, RAPTOR’s F-1 scores are at least 1.8% points higher than DPR and at least 5.3% points\n", + "higher than BM25.\n", + "Retriever GPT-3 F-1 Match GPT-4 F-1 Match UnifiedQA F-1 Match\n", + "Title + Abstract 25.2 22.2 17.5\n", + "BM25 46.6 50.2 26.4\n", + "DPR 51.3 53.0 32.1\n", + "RAPTOR 53.1 55.7 36.6\n", + "Table 4: Comparison of accuracies on the QuAL-\n", + "ITY dev dataset for two different language mod-\n", + "els (GPT-3, UnifiedQA 3B) using various retrieval\n", + "methods. RAPTOR outperforms the baselines of\n", + "BM25 and DPR by at least 2.0% in accuracy.\n", + "Model GPT-3 Acc. UnifiedQA Acc.\n", + "BM25 57.3 49.9\n", + "DPR 60.4 53.9\n", + "RAPTOR 62.4 56.6\n", + "Table 5: Results on F-1 Match scores of various\n", + "models on the QASPER dataset.\n", + "Model F-1 Match\n", + "LongT5 XL (Guo et al., 2022) 53.1\n", + "CoLT5 XL (Ainslie et al., 2023) 53.9\n", + "RAPTOR + GPT-4 55.7Comparison to State-of-the-art Systems\n", + "Building upon our controlled comparisons,\n", + "we examine RAPTOR’s performance relative\n", + "to other state-of-the-art models.\n" + ] + } + ], + "source": [ + "nodes = raptor_pack.run(\"What baselines is raptor compared against?\", mode=\"collapsed\")\n", + "print(len(nodes))\n", + "print(nodes[0].text)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Retrieved parent IDs from level 2: ['cc3b3f41-f4ca-4020-b11f-be7e0ce04c4f']\n", + "Retrieved 1 from parents at level 2.\n", + "Retrieved parent IDs from level 1: ['a4ca9426-a312-4a01-813a-c9b02aefc7e8']\n", + "Retrieved 2 from parents at level 1.\n", + "Retrieved parent IDs from level 0: ['63126782-2778-449f-99c0-1e8fd90caa36', 'd8f68d31-d878-41f1-aeb6-a7dde8ed5143']\n", + "Retrieved 4 from parents at level 0.\n", + "4\n", + "Specifically, RAPTOR’s F-1 scores are at least 1.8% points higher than DPR and at least 5.3% points\n", + "higher than BM25.\n", + "Retriever GPT-3 F-1 Match GPT-4 F-1 Match UnifiedQA F-1 Match\n", + "Title + Abstract 25.2 22.2 17.5\n", + "BM25 46.6 50.2 26.4\n", + "DPR 51.3 53.0 32.1\n", + "RAPTOR 53.1 55.7 36.6\n", + "Table 4: Comparison of accuracies on the QuAL-\n", + "ITY dev dataset for two different language mod-\n", + "els (GPT-3, UnifiedQA 3B) using various retrieval\n", + "methods. RAPTOR outperforms the baselines of\n", + "BM25 and DPR by at least 2.0% in accuracy.\n", + "Model GPT-3 Acc. UnifiedQA Acc.\n", + "BM25 57.3 49.9\n", + "DPR 60.4 53.9\n", + "RAPTOR 62.4 56.6\n", + "Table 5: Results on F-1 Match scores of various\n", + "models on the QASPER dataset.\n", + "Model F-1 Match\n", + "LongT5 XL (Guo et al., 2022) 53.1\n", + "CoLT5 XL (Ainslie et al., 2023) 53.9\n", + "RAPTOR + GPT-4 55.7Comparison to State-of-the-art Systems\n", + "Building upon our controlled comparisons,\n", + "we examine RAPTOR’s performance relative\n", + "to other state-of-the-art models.\n" + ] + } + ], + "source": [ + "nodes = raptor_pack.run(\n", + " \"What baselines is raptor compared against?\", mode=\"tree_traversal\"\n", + ")\n", + "print(len(nodes))\n", + "print(nodes[0].text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading\n", + "\n", + "Since we saved to a vector store, we can also use it again! (For local vector stores, there is a `persist` and `from_persist_dir` method on the retriever)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.packs.raptor import RaptorRetriever\n", + "\n", + "retriever = RaptorRetriever(\n", + " [],\n", + " embed_model=OpenAIEmbedding(\n", + " model=\"text-embedding-3-small\"\n", + " ), # used for embedding clusters\n", + " llm=OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1), # used for generating summaries\n", + " vector_store=vector_store, # used for storage\n", + " similarity_top_k=2, # top k for each layer, or overall top-k for collapsed\n", + " mode=\"tree_traversal\", # sets default mode\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# if using a default vector store\n", + "# retriever.persist(\"./persist\")\n", + "# retriever = RaptorRetriever.from_persist_dir(\"./persist\", ...)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Query Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llama_index.core.query_engine import RetrieverQueryEngine\n", + "\n", + "query_engine = RetrieverQueryEngine.from_args(\n", + " raptor_pack.retriever, llm=OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "response = query_engine.query(\"What baselines was RAPTOR compared against?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BM25 and DPR\n" + ] + } + ], + "source": [ + "print(str(response))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "llama-index-4aB9_5sa-py3.10", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/data_level0.bin b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/data_level0.bin new file mode 100644 index 0000000000000..ea3192e8ec511 Binary files /dev/null and b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/data_level0.bin differ diff --git a/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/header.bin b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/header.bin new file mode 100644 index 0000000000000..3e0932a7d0033 Binary files /dev/null and b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/header.bin differ diff --git a/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/length.bin b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/length.bin new file mode 100644 index 0000000000000..45bfe72ed91d6 Binary files /dev/null and b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/length.bin differ diff --git a/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/link_lists.bin b/llama-index-packs/llama-index-packs-raptor/examples/raptor/81db1dbe-a06d-43a6-ba07-875398bc33a7/link_lists.bin new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-index-packs/llama-index-packs-raptor/examples/raptor/chroma.sqlite3 b/llama-index-packs/llama-index-packs-raptor/examples/raptor/chroma.sqlite3 new file mode 100644 index 0000000000000..ca77f198baaba Binary files /dev/null and b/llama-index-packs/llama-index-packs-raptor/examples/raptor/chroma.sqlite3 differ diff --git a/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/BUILD b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/BUILD new file mode 100644 index 0000000000000..db46e8d6c978c --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/__init__.py b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/__init__.py new file mode 100644 index 0000000000000..7bc9f986b42d1 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/__init__.py @@ -0,0 +1,4 @@ +from llama_index.packs.raptor.base import RaptorPack, RaptorRetriever + + +__all__ = ["RaptorPack", "RaptorRetriever"] diff --git a/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/base.py b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/base.py new file mode 100644 index 0000000000000..89a539ee07aa8 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/base.py @@ -0,0 +1,366 @@ +from typing import Any, Dict, List, Optional + +import asyncio +from enum import Enum + +from llama_index.core import ( + StorageContext, + VectorStoreIndex, + get_response_synthesizer, + load_index_from_storage, +) +from llama_index.core.base.response.schema import Response +from llama_index.core.base.base_retriever import BaseRetriever, QueryType +from llama_index.core.bridge.pydantic import BaseModel, Field +from llama_index.core.embeddings import BaseEmbedding +from llama_index.core.ingestion import run_transformations +from llama_index.core.llama_pack.base import BaseLlamaPack +from llama_index.core.llms.llm import LLM +from llama_index.core.response_synthesizers import BaseSynthesizer +from llama_index.core.schema import ( + BaseNode, + NodeWithScore, + QueryBundle, + TextNode, + TransformComponent, +) +from llama_index.core.vector_stores.types import ( + MetadataFilter, + MetadataFilters, + VectorStore, +) +from llama_index.packs.raptor.clustering import get_clusters + + +DEFAULT_SUMMARY_PROMPT = ( + "Summarize the provided text, including as many key details as needed." +) + + +class QueryModes(str, Enum): + """Query modes.""" + + tree_traversal = "tree_traversal" + collapsed = "collapsed" + + +class SummaryModule(BaseModel): + response_synthesizer: BaseSynthesizer = Field(description="LLM") + summary_prompt: str = Field( + default=DEFAULT_SUMMARY_PROMPT, + description="Summary prompt.", + ) + num_workers: int = Field( + default=4, description="Number of workers to generate summaries." + ) + show_progress: bool = Field(default=True, description="Show progress.") + + class Config: + arbitrary_types_allowed = True + + def __init__( + self, llm: Optional[LLM] = None, summary_prompt: str = DEFAULT_SUMMARY_PROMPT + ) -> None: + response_synthesizer = get_response_synthesizer( + response_mode="tree_summarize", use_async=True, llm=llm + ) + super().__init__( + response_synthesizer=response_synthesizer, summary_prompt=summary_prompt + ) + + async def generate_summaries( + self, documents_per_cluster: List[List[BaseNode]] + ) -> List[str]: + """Generate summaries of documents per cluster. + + Args: + documents_per_cluster (List[List[BaseNode]]): List of documents per cluster + + Returns: + List[str]: List of summary for each cluster + """ + jobs = [] + for documents in documents_per_cluster: + with_scores = [NodeWithScore(node=doc, score=1.0) for doc in documents] + jobs.append( + self.response_synthesizer.asynthesize(self.summary_prompt, with_scores) + ) + + lock = asyncio.Semaphore(self.num_workers) + responses = [] + + # run the jobs while limiting the number of concurrent jobs to num_workers + for job in jobs: + async with lock: + responses.append(await job) + + return [str(response) for response in responses] + + +class RaptorRetriever(BaseRetriever): + """Raptor indexing retriever.""" + + def __init__( + self, + documents: List[BaseNode], + tree_depth: int = 3, + similarity_top_k: int = 2, + llm: Optional[LLM] = None, + embed_model: Optional[BaseEmbedding] = None, + vector_store: Optional[VectorStore] = None, + transformations: Optional[List[TransformComponent]] = None, + summary_module: Optional[SummaryModule] = None, + existing_index: Optional[VectorStoreIndex] = None, + mode: QueryModes = "collapsed", + **kwargs: Any, + ) -> None: + """Init params.""" + super().__init__( + **kwargs, + ) + + self.mode = mode + self.summary_module = summary_module or SummaryModule(llm=llm) + self.index = existing_index or VectorStoreIndex( + nodes=[], + storage_context=StorageContext.from_defaults(vector_store=vector_store), + embed_model=embed_model, + transformations=transformations, + ) + self.tree_depth = tree_depth + self.similarity_top_k = similarity_top_k + + if len(documents) > 0: + asyncio.run(self.insert(documents)) + + def _get_embeddings_per_level(self, level: int = 0) -> List[float]: + """Retrieve embeddings per level in the abstraction tree. + + Args: + level (int, optional): Target level. Defaults to 0 which stands for leaf nodes. + + Returns: + List[float]: List of embeddings + """ + filters = MetadataFilters(filters=[MetadataFilter("level", level)]) + + # kind of janky, but should work with any vector index + source_nodes = self.index.as_retriever( + similarity_top_k=10000, filters=filters + ).retrieve("retrieve") + + return [x.node for x in source_nodes] + + async def insert(self, documents: List[BaseNode]) -> None: + """Given a set of documents, this function inserts higher level of abstractions within the index. + + For later retrieval + + Args: + documents (List[BaseNode]): List of Documents + """ + embed_model = self.index._embed_model + transformations = self.index._transformations + + cur_nodes = run_transformations(documents, transformations, in_place=False) + for level in range(self.tree_depth): + # get the embeddings for the current documents + + if self._verbose: + print(f"Generating embeddings for level {level}.") + + embeddings = await embed_model.aget_text_embedding_batch( + [node.get_content(metadata_mode="embed") for node in cur_nodes] + ) + assert len(embeddings) == len(cur_nodes) + id_to_embedding = { + node.id_: embedding for node, embedding in zip(cur_nodes, embeddings) + } + + if self._verbose: + print(f"Performing clustering for level {level}.") + + # cluster the documents + nodes_per_cluster = get_clusters(cur_nodes, id_to_embedding) + + if self._verbose: + print( + f"Generating summaries for level {level} with {len(nodes_per_cluster)} clusters." + ) + summaries_per_cluster = await self.summary_module.generate_summaries( + nodes_per_cluster + ) + + if self._verbose: + print( + f"Level {level} created summaries/clusters: {len(nodes_per_cluster)}" + ) + + # replace the current nodes with their summaries + new_nodes = [ + TextNode( + text=summary, + metadata={"level": level}, + excluded_embed_metadata_keys=["level"], + excluded_llm_metadata_keys=["level"], + ) + for summary in summaries_per_cluster + ] + + # insert the nodes with their embeddings and parent_id + nodes_with_embeddings = [] + for cluster, summary_doc in zip(nodes_per_cluster, new_nodes): + for node in cluster: + node.metadata["parent_id"] = summary_doc.id_ + node.excluded_embed_metadata_keys.append("parent_id") + node.excluded_llm_metadata_keys.append("parent_id") + node.embedding = id_to_embedding[node.id_] + nodes_with_embeddings.append(node) + + self.index.insert_nodes(nodes_with_embeddings) + + # set the current nodes to the new nodes + cur_nodes = new_nodes + + self.index.insert_nodes(cur_nodes) + + async def collapsed_retrieval(self, query_str: str) -> Response: + """Query the index as a collapsed tree -- i.e. a single pool of nodes.""" + return await self.index.as_retriever( + similarity_top_k=self.similarity_top_k + ).aretrieve(query_str) + + async def tree_traversal_retrieval(self, query_str: str) -> Response: + """Query the index as a tree, traversing the tree from the top down.""" + # get top k nodes for each level, starting with the top + parent_ids = None + nodes = [] + level = self.tree_depth - 1 + while level >= 0: + # retrieve nodes at the current level + if parent_ids is None: + nodes = await self.index.as_retriever( + similarity_top_k=self.similarity_top_k, + filters=MetadataFilters( + filters=[MetadataFilter(key="level", value=level)] + ), + ).aretrieve(query_str) + + parent_ids = [node.id_ for node in nodes] + if self._verbose: + print(f"Retrieved parent IDs from level {level}: {parent_ids!s}") + # retrieve nodes that are children of the nodes at the previous level + elif parent_ids is not None and len(parent_ids) > 0: + nested_nodes = await asyncio.gather( + *[ + self.index.as_retriever( + similarity_top_k=self.similarity_top_k, + filters=MetadataFilters( + filters=[MetadataFilter(key="parent_id", value=id_)] + ), + ).aretrieve(query_str) + for id_ in parent_ids + ] + ) + + nodes = [node for nested in nested_nodes for node in nested] + + if self._verbose: + print(f"Retrieved {len(nodes)} from parents at level {level}.") + + level -= 1 + parent_ids = None + + return nodes + + def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: + """Retrieve nodes given query and mode.""" + # not used, needed for type checking + + def retrieve( + self, query_str_or_bundle: QueryType, mode: Optional[QueryModes] = None + ) -> List[NodeWithScore]: + """Retrieve nodes given query and mode.""" + if isinstance(query_str_or_bundle, QueryBundle): + query_str = query_str_or_bundle.query_str + else: + query_str = query_str_or_bundle + + return asyncio.run(self.aretrieve(query_str, mode or self.mode)) + + async def aretrieve( + self, query_str_or_bundle: QueryType, mode: Optional[QueryModes] = None + ) -> List[NodeWithScore]: + """Retrieve nodes given query and mode.""" + if isinstance(query_str_or_bundle, QueryBundle): + query_str = query_str_or_bundle.query_str + else: + query_str = query_str_or_bundle + + mode = mode or self.mode + if mode == "tree_traversal": + return await self.tree_traversal_retrieval(query_str) + elif mode == "collapsed": + return await self.collapsed_retrieval(query_str) + else: + raise ValueError(f"Invalid mode: {mode}") + + def persist(self, persist_dir: str) -> None: + self.index.storage_context.persist(persist_dir=persist_dir) + + @classmethod + def from_persist_dir( + cls: "RaptorRetriever", + persist_dir: str, + embed_model: Optional[BaseEmbedding] = None, + **kwargs: Any, + ) -> "RaptorRetriever": + storage_context = StorageContext.from_defaults(persist_dir=persist_dir) + return cls( + [], + existing_index=load_index_from_storage( + storage_context, embed_model=embed_model + ), + **kwargs, + ) + + +class RaptorPack(BaseLlamaPack): + """Raptor pack.""" + + def __init__( + self, + documents: List[BaseNode], + llm: Optional[LLM] = None, + embed_model: Optional[BaseEmbedding] = None, + vector_store: Optional[VectorStore] = None, + similarity_top_k: int = 2, + mode: QueryModes = "collapsed", + verbose: bool = True, + **kwargs: Any, + ) -> None: + """Init params.""" + self.retriever = RaptorRetriever( + documents, + embed_model=embed_model, + llm=llm, + similarity_top_k=similarity_top_k, + vector_store=vector_store, + mode=mode, + verbose=verbose, + **kwargs, + ) + + def get_modules(self) -> Dict[str, Any]: + """Get modules.""" + return { + "retriever": self.retriever, + } + + def run( + self, + query: str, + mode: Optional[QueryModes] = None, + ) -> Any: + """Run the pipeline.""" + return self.retriever.retrieve(query, mode=mode) diff --git a/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/clustering.py b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/clustering.py new file mode 100644 index 0000000000000..3969446f53ac0 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/clustering.py @@ -0,0 +1,169 @@ +""" +Minorly tweaked from https://github.com/parthsarthi03/raptor/blob/master/raptor/cluster_tree_builder.py. + +Full credits to the original authors! +""" + +import numpy as np +import random +import tiktoken +import umap +from sklearn.mixture import GaussianMixture +from typing import Dict, List, Optional + +from llama_index.core.schema import BaseNode + + +# Set a random seed for reproducibility +RANDOM_SEED = 224 +random.seed(RANDOM_SEED) + + +def global_cluster_embeddings( + embeddings: np.ndarray, + dim: int, + n_neighbors: Optional[int] = None, + metric: str = "cosine", +) -> np.ndarray: + if n_neighbors is None: + n_neighbors = int((len(embeddings) - 1) ** 0.5) + return umap.UMAP( + n_neighbors=n_neighbors, n_components=dim, metric=metric + ).fit_transform(embeddings) + + +def local_cluster_embeddings( + embeddings: np.ndarray, dim: int, num_neighbors: int = 10, metric: str = "cosine" +) -> np.ndarray: + return umap.UMAP( + n_neighbors=num_neighbors, n_components=dim, metric=metric + ).fit_transform(embeddings) + + +def get_optimal_clusters( + embeddings: np.ndarray, max_clusters: int = 50, random_state: int = RANDOM_SEED +) -> int: + max_clusters = min(max_clusters, len(embeddings)) + n_clusters = np.arange(1, max_clusters) + bics = [] + for n in n_clusters: + gm = GaussianMixture(n_components=n, random_state=random_state) + gm.fit(embeddings) + bics.append(gm.bic(embeddings)) + return n_clusters[np.argmin(bics)] + + +def GMM_cluster(embeddings: np.ndarray, threshold: float, random_state: int = 0): + n_clusters = get_optimal_clusters(embeddings) + gm = GaussianMixture(n_components=n_clusters, random_state=random_state) + gm.fit(embeddings) + probs = gm.predict_proba(embeddings) + labels = [np.where(prob > threshold)[0] for prob in probs] + return labels, n_clusters + + +def perform_clustering( + embeddings: np.ndarray, + dim: int, + threshold: float, +) -> List[np.ndarray]: + # If the number of embeddings is less than or equal to the dimension, return a list of zeros + # This means all nodes are in the same cluster. + # Otherwise, we will get an error when trying to cluster. + if len(embeddings) <= dim + 1: + return [np.array([0]) for _ in range(len(embeddings))] + + reduced_embeddings_global = global_cluster_embeddings(embeddings, dim) + global_clusters, n_global_clusters = GMM_cluster( + reduced_embeddings_global, threshold + ) + + all_local_clusters = [np.array([]) for _ in range(len(embeddings))] + total_clusters = 0 + + for i in range(n_global_clusters): + global_cluster_embeddings_ = embeddings[ + np.array([i in gc for gc in global_clusters]) + ] + + if len(global_cluster_embeddings_) == 0: + continue + if len(global_cluster_embeddings_) <= dim + 1: + local_clusters = [np.array([0]) for _ in global_cluster_embeddings_] + n_local_clusters = 1 + else: + reduced_embeddings_local = local_cluster_embeddings( + global_cluster_embeddings_, dim + ) + local_clusters, n_local_clusters = GMM_cluster( + reduced_embeddings_local, threshold + ) + + for j in range(n_local_clusters): + local_cluster_embeddings_ = global_cluster_embeddings_[ + np.array([j in lc for lc in local_clusters]) + ] + indices = np.where( + (embeddings == local_cluster_embeddings_[:, None]).all(-1) + )[1] + for idx in indices: + all_local_clusters[idx] = np.append( + all_local_clusters[idx], j + total_clusters + ) + + total_clusters += n_local_clusters + + return all_local_clusters + + +def get_clusters( + nodes: List[BaseNode], + embedding_map: Dict[str, List[List[float]]], + max_length_in_cluster: int = 10000, # 10k tokens max per cluster + tokenizer: tiktoken.Encoding = tiktoken.get_encoding("cl100k_base"), + reduction_dimension: int = 10, + threshold: float = 0.1, +) -> List[List[BaseNode]]: + # get embeddings + embeddings = np.array([np.array(embedding_map[node.id_]) for node in nodes]) + + # Perform the clustering + clusters = perform_clustering( + embeddings, dim=reduction_dimension, threshold=threshold + ) + + # Initialize an empty list to store the clusters of nodes + node_clusters = [] + + # Iterate over each unique label in the clusters + for label in np.unique(np.concatenate(clusters)): + # Get the indices of the nodes that belong to this cluster + indices = [i for i, cluster in enumerate(clusters) if label in cluster] + + # Add the corresponding nodes to the node_clusters list + cluster_nodes = [nodes[i] for i in indices] + + # Base case: if the cluster only has one node, do not attempt to recluster it + if len(cluster_nodes) == 1: + node_clusters.append(cluster_nodes) + continue + + # Calculate the total length of the text in the nodes + total_length = sum([len(tokenizer.encode(node.text)) for node in cluster_nodes]) + + # If the total length exceeds the maximum allowed length, recluster this cluster + if total_length > max_length_in_cluster: + node_clusters.extend( + get_clusters( + cluster_nodes, + embedding_map, + max_length_in_cluster=max_length_in_cluster, + tokenizer=tokenizer, + reduction_dimension=reduction_dimension, + threshold=threshold, + ) + ) + else: + node_clusters.append(cluster_nodes) + + return node_clusters diff --git a/llama-index-packs/llama-index-packs-raptor/pyproject.toml b/llama-index-packs/llama-index-packs-raptor/pyproject.toml new file mode 100644 index 0000000000000..4e0202df446b8 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.codespell] +check-filenames = true +check-hidden = true +# Feel free to un-skip examples, and experimental, you will just need to +# work through many typos (--write-changes and --interactive will help) +skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb" + +[tool.llamahub] +contains_example = true +import_path = "llama_index.packs.raptor" + +[tool.llamahub.class_authors] +RaptorPack = "logan-markewich" + +[tool.mypy] +disallow_untyped_defs = true +# Remove venv skip when integrated with pre-commit +exclude = ["_static", "build", "examples", "notebooks", "venv"] +ignore_missing_imports = true +python_version = "3.8" + +[tool.poetry] +authors = ["Logan Markewich "] +description = "llama-index packs raptor integration" +keywords = ["cluster", "raptor", "retrieval"] +license = "MIT" +name = "llama-index-packs-raptor" +packages = [{include = "llama_index/"}] +readme = "README.md" +version = "0.1.1" + +[tool.poetry.dependencies] +python = ">=3.9,<4.0" +llama-index-core = "^0.10.0" +llama-index-llms-openai = "^0.1.6" +umap-learn = ">=0.5.5" +scikit-learn = "*" + +[tool.poetry.group.dev.dependencies] +black = {extras = ["jupyter"], version = "<=23.9.1,>=23.7.0"} +codespell = {extras = ["toml"], version = ">=v2.2.6"} +ipython = "8.10.0" +jupyter = "^1.0.0" +mypy = "0.991" +pre-commit = "3.2.0" +pylint = "2.15.10" +pytest = "7.2.1" +pytest-mock = "3.11.1" +ruff = "0.0.292" +tree-sitter-languages = "^1.8.0" +types-Deprecated = ">=0.1.0" +types-PyYAML = "^6.0.12.12" +types-protobuf = "^4.24.0.4" +types-redis = "4.5.5.0" +types-requests = "2.28.11.8" # TODO: unpin when mypy>0.991 +types-setuptools = "67.1.0.0" diff --git a/llama-index-packs/llama-index-packs-raptor/tests/BUILD b/llama-index-packs/llama-index-packs-raptor/tests/BUILD new file mode 100644 index 0000000000000..dabf212d7e716 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/tests/BUILD @@ -0,0 +1 @@ +python_tests() diff --git a/llama-index-packs/llama-index-packs-raptor/tests/__init__.py b/llama-index-packs/llama-index-packs-raptor/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llama-index-packs/llama-index-packs-raptor/tests/test_packs_raptor.py b/llama-index-packs/llama-index-packs-raptor/tests/test_packs_raptor.py new file mode 100644 index 0000000000000..a8deb2a261a69 --- /dev/null +++ b/llama-index-packs/llama-index-packs-raptor/tests/test_packs_raptor.py @@ -0,0 +1,30 @@ +from llama_index.core import Document, MockEmbedding +from llama_index.core.llms import MockLLM +from llama_index.packs.raptor.base import RaptorRetriever + + +def test_raptor() -> None: + retriever = RaptorRetriever( + [ + Document(text="one"), + Document(text="two"), + Document(text="three"), + Document(text="four"), + Document(text="five"), + Document(text="six"), + Document(text="seven"), + Document(text="eight"), + Document(text="nine"), + Document(text="ten"), + ], + embed_model=MockEmbedding(embed_dim=1536), + llm=MockLLM(), + ) + + assert len(retriever.index.docstore.docs) == 13 + + nodes = retriever.retrieve("test", mode="collapsed") + assert len(nodes) == 2 + + nodes = retriever.retrieve("text", mode="tree_traversal") + assert len(nodes) == 2 diff --git a/llama-index-packs/llama-index-packs-redis-ingestion-pipeline/README.md b/llama-index-packs/llama-index-packs-redis-ingestion-pipeline/README.md index 6c4dc6de2f12f..cbe2a65e5148c 100644 --- a/llama-index-packs/llama-index-packs-redis-ingestion-pipeline/README.md +++ b/llama-index-packs/llama-index-packs-redis-ingestion-pipeline/README.md @@ -30,8 +30,8 @@ From here, you can use the pack, or inspect and modify the pack in `./redis_inge Then, you can set up the pack like so: ```python -from llama_index.core.text_splitter import SentenceSplitter -from llama_index.core.embeddings import OpenAIEmbedding +from llama_index.core.node_parser import SentenceSplitter +from llama_index.embeddings.openai import OpenAIEmbedding transformations = [SentenceSplitter(), OpenAIEmbedding()] diff --git a/llama-index-packs/llama-index-packs-retry-engine-weaviate/README.md b/llama-index-packs/llama-index-packs-retry-engine-weaviate/README.md index 345ca13440b3a..5f88ce6597dc5 100644 --- a/llama-index-packs/llama-index-packs-retry-engine-weaviate/README.md +++ b/llama-index-packs/llama-index-packs-retry-engine-weaviate/README.md @@ -31,7 +31,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo vector_store_info = VectorStoreInfo( content_info="brief biography of celebrities", diff --git a/llama-index-packs/llama-index-packs-self-discover/README.md b/llama-index-packs/llama-index-packs-self-discover/README.md index b7b923ca17238..51ce36fc26b2d 100644 --- a/llama-index-packs/llama-index-packs-self-discover/README.md +++ b/llama-index-packs/llama-index-packs-self-discover/README.md @@ -36,7 +36,7 @@ There are two ways using LlamaPack: ### Using `download_llama_pack` ```python -from llama_index.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies SelfDiscoverPack = download_llama_pack( diff --git a/llama-index-packs/llama-index-packs-self-rag/README.md b/llama-index-packs/llama-index-packs-self-rag/README.md index 56bf4cec94b54..5caf1f240c704 100644 --- a/llama-index-packs/llama-index-packs-self-rag/README.md +++ b/llama-index-packs/llama-index-packs-self-rag/README.md @@ -28,7 +28,7 @@ huggingface-cli download m4r1/selfrag_llama2_7b-GGUF selfrag_llama2_7b.q4_k_m.gg ``` ```python -from llama_index.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies SelfRAGPack = download_llama_pack("SelfRAGPack", "./self_rag_pack") diff --git a/llama-index-packs/llama-index-packs-sub-question-weaviate/README.md b/llama-index-packs/llama-index-packs-sub-question-weaviate/README.md index 0ef0cdfb83171..5d258b12f1ad7 100644 --- a/llama-index-packs/llama-index-packs-sub-question-weaviate/README.md +++ b/llama-index-packs/llama-index-packs-sub-question-weaviate/README.md @@ -31,7 +31,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo vector_store_info = VectorStoreInfo( content_info="brief biography of celebrities", diff --git a/llama-index-packs/llama-index-packs-timescale-vector-autoretrieval/README.md b/llama-index-packs/llama-index-packs-timescale-vector-autoretrieval/README.md index a9204d506b642..56a07d28ee741 100644 --- a/llama-index-packs/llama-index-packs-timescale-vector-autoretrieval/README.md +++ b/llama-index-packs/llama-index-packs-timescale-vector-autoretrieval/README.md @@ -51,7 +51,7 @@ You can then inspect the files at `./tsv_pack` and use them as a template for yo You can download the pack to a the `./tsv_pack` directory: ```python -from llama_hub.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies TimescaleVectorAutoretrievalPack = download_llama_pack( @@ -65,7 +65,7 @@ Then, you can set up the pack like so: ```python # setup pack arguments -from llama_index.core.vector_stores.types import MetadataInfo, VectorStoreInfo +from llama_index.core.vector_stores import MetadataInfo, VectorStoreInfo from timescale_vector import client from dotenv import load_dotenv, find_dotenv import os diff --git a/llama-index-packs/llama-index-packs-vanna/README.md b/llama-index-packs/llama-index-packs-vanna/README.md index d4b42ec9cc5d3..42fde06f552d0 100644 --- a/llama-index-packs/llama-index-packs-vanna/README.md +++ b/llama-index-packs/llama-index-packs-vanna/README.md @@ -24,7 +24,7 @@ You can then inspect the files at `./vanna_pack` and use them as a template for You can download the pack to a `./vanna_pack` directory: ```python -from llama_index.llama_pack import download_llama_pack +from llama_index.core.llama_pack import download_llama_pack # download and install dependencies VannaPack = download_llama_pack("VannaPack", "./vanna_pack") diff --git a/poetry.lock b/poetry.lock index cdf83badcd93f..063032b09a51d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -423,18 +423,18 @@ beautifulsoup4 = "*" [[package]] name = "build" -version = "1.0.3" +version = "1.1.1" description = "A simple, correct Python build frontend" optional = false python-versions = ">= 3.7" files = [ - {file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"}, - {file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"}, + {file = "build-1.1.1-py3-none-any.whl", hash = "sha256:8ed0851ee76e6e38adce47e4bee3b51c771d86c64cf578d0c2245567ee200e73"}, + {file = "build-1.1.1.tar.gz", hash = "sha256:8eea65bb45b1aac2e734ba2cc8dad3a6d97d97901a395bd0ed3e7b46953d2a31"}, ] [package.dependencies] colorama = {version = "*", markers = "os_name == \"nt\""} -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} packaging = ">=19.0" pyproject_hooks = "*" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} @@ -1254,13 +1254,13 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "graphlib-backport" -version = "1.0.3" +version = "1.1.0" description = "Backport of the Python 3.9 graphlib module for Python 3.6+" optional = false python-versions = ">=3.6,<4.0" files = [ - {file = "graphlib_backport-1.0.3-py3-none-any.whl", hash = "sha256:24246967b9e7e6a91550bc770e6169585d35aa32790258579a8a3899a8c18fde"}, - {file = "graphlib_backport-1.0.3.tar.gz", hash = "sha256:7bb8fc7757b8ae4e6d8000a26cd49e9232aaa9a3aa57edb478474b8424bfaae2"}, + {file = "graphlib_backport-1.1.0-py3-none-any.whl", hash = "sha256:eccacf9f2126cdf89ce32a6018c88e1ecd3e4898a07568add6e1907a439055ba"}, + {file = "graphlib_backport-1.1.0.tar.gz", hash = "sha256:00a7888b21e5393064a133209cb5d3b3ef0a2096cf023914c9d778dff5644125"}, ] [[package]] @@ -1506,13 +1506,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "huggingface-hub" -version = "0.21.2" +version = "0.21.3" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.21.2-py3-none-any.whl", hash = "sha256:16955c2b60bcff32a0778f84b9e9ae8f61d7f003da6aa1fbb7bc897a0c37b28c"}, - {file = "huggingface_hub-0.21.2.tar.gz", hash = "sha256:839f2fc69fc51797b76dcffa7edbf7fb1150176f74cb1dc2d87ca00e5e0b5611"}, + {file = "huggingface_hub-0.21.3-py3-none-any.whl", hash = "sha256:b183144336fdf2810a8c109822e0bb6ef1fd61c65da6fb60e8c3f658b7144016"}, + {file = "huggingface_hub-0.21.3.tar.gz", hash = "sha256:26a15b604e4fc7bad37c467b76456543ec849386cbca9cd7e1e135f53e500423"}, ] [package.dependencies] @@ -1969,13 +1969,13 @@ llama-index-llms-openai = ">=0.1.5,<0.2.0" [[package]] name = "llama-index-cli" -version = "0.1.5" +version = "0.1.7" description = "llama-index cli" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "llama_index_cli-0.1.5-py3-none-any.whl", hash = "sha256:a0fcfc3239d8b05158558423ca5c1a426d2a455eab44128b2b786cab566f74ad"}, - {file = "llama_index_cli-0.1.5.tar.gz", hash = "sha256:e2493ff7ecfd1983fd15c28c6c0c7bfdba66662c1d8960f6aea229db3d7fafda"}, + {file = "llama_index_cli-0.1.7-py3-none-any.whl", hash = "sha256:48a81fc33d4005dbe91b77ebe840ac69e0102e7e2a59770308f94fae1b792544"}, + {file = "llama_index_cli-0.1.7.tar.gz", hash = "sha256:55a77e3c370eb760c42cb74a0df6f650e41ec17928b72b07ff8b927cb94b15b4"}, ] [package.dependencies] @@ -1986,16 +1986,46 @@ llama-index-vector-stores-chroma = ">=0.1.1,<0.2.0" [[package]] name = "llama-index-core" -version = "0.10.14" +version = "0.10.16" description = "Interface between LLMs and your data" optional = false -python-versions = "*" -files = [] -develop = true +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "llama_index_core-0.10.16-py3-none-any.whl", hash = "sha256:189c808b9c92d871b4c27f78e71186f5edbe202c9fafa28ff4e276197c1a05ff"}, + {file = "llama_index_core-0.10.16.tar.gz", hash = "sha256:a20b246b75e2e5b2dd679953fcede921e8b9896f06e61f473a31ee96d24cd446"}, +] -[package.source] -type = "directory" -url = "llama-index-core" +[package.dependencies] +aiohttp = ">=3.8.6,<4.0.0" +dataclasses-json = "*" +deprecated = ">=1.2.9.3" +dirtyjson = ">=1.0.8,<2.0.0" +fsspec = ">=2023.5.0" +httpx = "*" +llamaindex-py-client = ">=0.1.13,<0.2.0" +nest-asyncio = ">=1.5.8,<2.0.0" +networkx = ">=3.0" +nltk = ">=3.8.1,<4.0.0" +numpy = "*" +openai = ">=1.1.0" +pandas = "*" +pillow = ">=9.0.0" +PyYAML = ">=6.0.1" +requests = ">=2.31.0" +SQLAlchemy = {version = ">=1.4.49", extras = ["asyncio"]} +tenacity = ">=8.2.0,<9.0.0" +tiktoken = ">=0.3.3" +tqdm = ">=4.66.1,<5.0.0" +typing-extensions = ">=4.5.0" +typing-inspect = ">=0.8.0" + +[package.extras] +gradientai = ["gradientai (>=1.4.0)"] +html = ["beautifulsoup4 (>=4.12.2,<5.0.0)"] +langchain = ["langchain (>=0.0.303)"] +local-models = ["optimum[onnxruntime] (>=1.13.2,<2.0.0)", "sentencepiece (>=0.1.99,<0.2.0)", "transformers[torch] (>=4.33.1,<5.0.0)"] +postgres = ["asyncpg (>=0.28.0,<0.29.0)", "pgvector (>=0.1.0,<0.2.0)", "psycopg2-binary (>=2.9.9,<3.0.0)"] +query-tools = ["guidance (>=0.0.64,<0.0.65)", "jsonpath-ng (>=1.6.0,<2.0.0)", "lm-format-enforcer (>=0.4.3,<0.5.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "scikit-learn", "spacy (>=3.7.1,<4.0.0)"] [[package]] name = "llama-index-embeddings-openai" @@ -2067,13 +2097,13 @@ query-tools = ["guidance (>=0.0.64,<0.0.65)", "jsonpath-ng (>=1.6.0,<2.0.0)", "l [[package]] name = "llama-index-llms-openai" -version = "0.1.6" +version = "0.1.7" description = "llama-index llms openai integration" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "llama_index_llms_openai-0.1.6-py3-none-any.whl", hash = "sha256:4260ad31c3444e97ec8a8d061cb6dbf1074262b82341a2b69d2b27e8a23efe62"}, - {file = "llama_index_llms_openai-0.1.6.tar.gz", hash = "sha256:15530dfa3893b15c5576ebc71e01b77acbf47abd689219436fdf7b6ca567a9fd"}, + {file = "llama_index_llms_openai-0.1.7-py3-none-any.whl", hash = "sha256:162a7f1064b389d0db6f731bcedaca80e87ceca8aa919d7425ca32107e756243"}, + {file = "llama_index_llms_openai-0.1.7.tar.gz", hash = "sha256:5ddb405c0a5847a7c2098a70ced270555f036c2793412a8992456bd32f83ff0f"}, ] [package.dependencies] @@ -2128,13 +2158,13 @@ llama-index-program-openai = ">=0.1.1,<0.2.0" [[package]] name = "llama-index-readers-file" -version = "0.1.6" +version = "0.1.7" description = "llama-index readers file integration" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "llama_index_readers_file-0.1.6-py3-none-any.whl", hash = "sha256:f583bd90353a0c0985213af02c97aa2f2f22e702d4311fe719de91382c9ad8dd"}, - {file = "llama_index_readers_file-0.1.6.tar.gz", hash = "sha256:d9fc0ca84926d04bd757c57fe87841cd9dbc2606aab5f2ce927deec14aaa1a74"}, + {file = "llama_index_readers_file-0.1.7-py3-none-any.whl", hash = "sha256:46cf03a141b3fa5fd50c81fa607e9de3060aa67ab9a79dd64bea18962776d2de"}, + {file = "llama_index_readers_file-0.1.7.tar.gz", hash = "sha256:63827ab51e8f66d97f08e1a20be67f86b92484ae120253b5f756fef2371d61bf"}, ] [package.dependencies] @@ -2178,13 +2208,13 @@ tokenizers = ">=0.15.1,<0.16.0" [[package]] name = "llama-parse" -version = "0.3.4" +version = "0.3.5" description = "Parse files into RAG-Optimized formats." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "llama_parse-0.3.4-py3-none-any.whl", hash = "sha256:b667c78d4c32fc5d0561e6e3ca6c53648a6701b436f21d0d252cd46774927660"}, - {file = "llama_parse-0.3.4.tar.gz", hash = "sha256:5a30569c390ab9089dad66cf2a8c967f8c21d77641deec0a922672df4e16cfa3"}, + {file = "llama_parse-0.3.5-py3-none-any.whl", hash = "sha256:8e6e7a0986ad30cb82c5c67a29b7e2c3892620dd2a422afc909654a9d0f1c82c"}, + {file = "llama_parse-0.3.5.tar.gz", hash = "sha256:736a80e4fc5970b9cbef1048171908021ebd26be43f07b806889f0d1bb3875fe"}, ] [package.dependencies] @@ -2315,13 +2345,13 @@ files = [ [[package]] name = "marshmallow" -version = "3.21.0" +version = "3.21.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.21.0-py3-none-any.whl", hash = "sha256:e7997f83571c7fd476042c2c188e4ee8a78900ca5e74bd9c8097afa56624e9bd"}, - {file = "marshmallow-3.21.0.tar.gz", hash = "sha256:20f53be28c6e374a711a16165fb22a8dc6003e3f7cda1285e3ca777b9193885b"}, + {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, + {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, ] [package.dependencies] @@ -2940,13 +2970,13 @@ sympy = "*" [[package]] name = "openai" -version = "1.12.0" +version = "1.13.3" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, - {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, + {file = "openai-1.13.3-py3-none-any.whl", hash = "sha256:5769b62abd02f350a8dd1a3a242d8972c947860654466171d60fb0972ae0a41c"}, + {file = "openai-1.13.3.tar.gz", hash = "sha256:ff6c6b3bc7327e715e4b3592a923a5a1c7519ff5dd764a83d69f633d49e77a7b"}, ] [package.dependencies] @@ -3324,6 +3354,91 @@ files = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] +[[package]] +name = "pillow" +version = "10.2.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "pkgutil-resolve-name" version = "1.3.10" @@ -3352,13 +3467,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest- [[package]] name = "posthog" -version = "3.4.2" +version = "3.5.0" description = "Integrate PostHog into any python application." optional = false python-versions = "*" files = [ - {file = "posthog-3.4.2-py2.py3-none-any.whl", hash = "sha256:c7e79b2e585d16e93749874bcbcdad78d857037398ce0d8d6c474a04d0bd3bbe"}, - {file = "posthog-3.4.2.tar.gz", hash = "sha256:f0eafa663fbc4a942b49b6168a62a890635407044bbc7593051dcb9cc1208873"}, + {file = "posthog-3.5.0-py2.py3-none-any.whl", hash = "sha256:3c672be7ba6f95d555ea207d4486c171d06657eb34b3ce25eb043bfe7b6b5b76"}, + {file = "posthog-3.5.0.tar.gz", hash = "sha256:8f7e3b2c6e8714d0c0c542a2109b83a7549f63b7113a133ab2763a89245ef2ef"}, ] [package.dependencies] @@ -3659,42 +3774,42 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymupdf" -version = "1.23.25" +version = "1.23.26" description = "A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents." optional = false python-versions = ">=3.8" files = [ - {file = "PyMuPDF-1.23.25-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:6be2b20fbff40602f673fc8e60fde3e5911397f8ca9ed6aa2d15be94b12cc2c4"}, - {file = "PyMuPDF-1.23.25-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:0f6923a44fbeaeefaabb2fa10955dcef3624e8826db661201951f3b3409fed32"}, - {file = "PyMuPDF-1.23.25-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:8eeb2e97347586ec293fddaf61e8dfc58d6b2763406e8f7a6e45b560bf9b15a3"}, - {file = "PyMuPDF-1.23.25-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:dca46799c152051697c5e88d66c17ba6d0244668d0c4dd8a2ba2d8d3cb745988"}, - {file = "PyMuPDF-1.23.25-cp310-none-win32.whl", hash = "sha256:88bfed1bd13ec84869489fc7b97381016cb8b99956073f4c3e8ac8c840bbb15a"}, - {file = "PyMuPDF-1.23.25-cp310-none-win_amd64.whl", hash = "sha256:98a78582c8a0c61b372e2bcd63dc61efc873e40b7d1f0b896a195e1a9ef9ffa7"}, - {file = "PyMuPDF-1.23.25-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:d7792810634036a745ea3eb3c4ccf2b6adab55ca9644e3352747d2b5aa5327f9"}, - {file = "PyMuPDF-1.23.25-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:03bd1985b0234c3d2b8e26bb3e9ab1d2641dbada1e199b838a6bf884f35224c8"}, - {file = "PyMuPDF-1.23.25-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:638fcb1f7551eb5ab582e412e204e8ded94acbbc37bc7f1e891a5dfc428881ee"}, - {file = "PyMuPDF-1.23.25-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:067c88b4e6609cb7e74d98d0b0a35c11eb8e29f4fc51dc7ed1dd448b81d347c7"}, - {file = "PyMuPDF-1.23.25-cp311-none-win32.whl", hash = "sha256:a694f160d1701285cf3152951430740878d168511cd9ea0a3adcfaf3cac00322"}, - {file = "PyMuPDF-1.23.25-cp311-none-win_amd64.whl", hash = "sha256:514bcb679926b33413637b0bd73b223c90fb0d19352caf3395d0f23b1d47e8af"}, - {file = "PyMuPDF-1.23.25-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:bba342321e1b5574631894d7d34ec046605d953a23553b7d2f9c0e4d3c27254b"}, - {file = "PyMuPDF-1.23.25-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:b2cb058c8229f9697deebe0574f7d95e4b9a5e295ceafd554346bbd464141e89"}, - {file = "PyMuPDF-1.23.25-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:2479473b533936593428ce78499a1e9901570110ac602f03f1f3174efa0fa6a8"}, - {file = "PyMuPDF-1.23.25-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:a247a4be1e43a6127ee305eae9f65767ee7519a2aa0cb1a2aa6acfd4e7fe7a9b"}, - {file = "PyMuPDF-1.23.25-cp312-none-win32.whl", hash = "sha256:b062be400bbaff6e8b17c0a8da9481e01ec935f97967e0870e9aacd7ba60a52a"}, - {file = "PyMuPDF-1.23.25-cp312-none-win_amd64.whl", hash = "sha256:b12e608761e1586a65f6e96a34417a91f814dbab29f2929b41d825ab32fab6ef"}, - {file = "PyMuPDF-1.23.25-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:ac97691c0e0e23607626d394bd660a46ea33f64921dc9288cf24daee207f9fe3"}, - {file = "PyMuPDF-1.23.25-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:c0a16cda5dc9b59d494ae23bdd9c4a3db53d04f2b6390265f5c0fe6269777975"}, - {file = "PyMuPDF-1.23.25-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:23d735db51722a889bb50636d161d2747f08fa0b82cc2e4a7eb8e228b25d1c4e"}, - {file = "PyMuPDF-1.23.25-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:cbc1407dcf01b2e3e547b2d7643b97cc44c0950d2bb4b12c74322664c5cb37d7"}, - {file = "PyMuPDF-1.23.25-cp38-none-win32.whl", hash = "sha256:c29518701d6360beb01c25cf69a77b6426db90a9e7cd11179b3bd783c7fb4cb1"}, - {file = "PyMuPDF-1.23.25-cp38-none-win_amd64.whl", hash = "sha256:c1bb6fa9e00c846e6829dec2bee8326754adaef5c80626b99233c01923f0342c"}, - {file = "PyMuPDF-1.23.25-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:514b272bfcd897f9ae29384da04167dcdea3b13ce0f2b9099b645314355d037d"}, - {file = "PyMuPDF-1.23.25-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:ef345a5b050d0869ef404845075edd5f4bd7fd99e235f4d32ce85f423779a120"}, - {file = "PyMuPDF-1.23.25-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:b3ade5b349c38ddffb24f8c266fbcd7161f488c43960ff0f03f977d40d4df967"}, - {file = "PyMuPDF-1.23.25-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:111d795a3e840aec2ad66beebd90a5327994ec85ed56fd68312f5463062dbbfa"}, - {file = "PyMuPDF-1.23.25-cp39-none-win32.whl", hash = "sha256:2237ce9897771f4af686cc0c81517ffb020fc1a011b95ccf5ccf05383492bd6d"}, - {file = "PyMuPDF-1.23.25-cp39-none-win_amd64.whl", hash = "sha256:251c9c321a2112716068d5ae11deedd1911d0387cbdd0ef19adb216a3adf882c"}, - {file = "PyMuPDF-1.23.25.tar.gz", hash = "sha256:eb414e92f08107f43576a1fedea28aa837220b15ad58c8e32015435fe96cc03e"}, + {file = "PyMuPDF-1.23.26-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:645a05321aecc8c45739f71f0eb574ce33138d19189582ffa5241fea3a8e2549"}, + {file = "PyMuPDF-1.23.26-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:2dfc9e010669ae92fade6fb72aaea49ebe3b8dcd7ee4dcbbe50115abcaa4d3fe"}, + {file = "PyMuPDF-1.23.26-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:734ee380b3abd038602be79114194a3cb74ac102b7c943bcb333104575922c50"}, + {file = "PyMuPDF-1.23.26-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:b22f8d854f8196ad5b20308c1cebad3d5189ed9f0988acbafa043947ea7e6c55"}, + {file = "PyMuPDF-1.23.26-cp310-none-win32.whl", hash = "sha256:cc0f794e3466bc96b5bf79d42fbc1551428751e3fef38ebc10ac70396b676144"}, + {file = "PyMuPDF-1.23.26-cp310-none-win_amd64.whl", hash = "sha256:2eb701247d8e685a24e45899d1175f01a3ce5fc792a4431c91fbb68633b29298"}, + {file = "PyMuPDF-1.23.26-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:e2804a64bb57da414781e312fb0561f6be67658ad57ed4a73dce008b23fc70a6"}, + {file = "PyMuPDF-1.23.26-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:97b40bb22e3056874634617a90e0ed24a5172cf71791b9e25d1d91c6743bc567"}, + {file = "PyMuPDF-1.23.26-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:fab8833559bc47ab26ce736f915b8fc1dd37c108049b90396f7cd5e1004d7593"}, + {file = "PyMuPDF-1.23.26-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:f25aafd3e7fb9d7761a22acf2b67d704f04cc36d4dc33a3773f0eb3f4ec3606f"}, + {file = "PyMuPDF-1.23.26-cp311-none-win32.whl", hash = "sha256:05e672ed3e82caca7ef02a88ace30130b1dd392a1190f03b2b58ffe7aa331400"}, + {file = "PyMuPDF-1.23.26-cp311-none-win_amd64.whl", hash = "sha256:92b3c4dd4d0491d495f333be2d41f4e1c155a409bc9d04b5ff29655dccbf4655"}, + {file = "PyMuPDF-1.23.26-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:a217689ede18cc6991b4e6a78afee8a440b3075d53b9dec4ba5ef7487d4547e9"}, + {file = "PyMuPDF-1.23.26-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:42ad2b819b90ce1947e11b90ec5085889df0a2e3aa0207bc97ecacfc6157cabc"}, + {file = "PyMuPDF-1.23.26-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:99607649f89a02bba7d8ebe96e2410664316adc95e9337f7dfeff6a154f93049"}, + {file = "PyMuPDF-1.23.26-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:bb42d4b8407b4de7cb58c28f01449f16f32a6daed88afb41108f1aeb3552bdd4"}, + {file = "PyMuPDF-1.23.26-cp312-none-win32.whl", hash = "sha256:c40d044411615e6f0baa7d3d933b3032cf97e168c7fa77d1be8a46008c109aee"}, + {file = "PyMuPDF-1.23.26-cp312-none-win_amd64.whl", hash = "sha256:3f876533aa7f9a94bcd9a0225ce72571b7808260903fec1d95c120bc842fb52d"}, + {file = "PyMuPDF-1.23.26-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:52df831d46beb9ff494f5fba3e5d069af6d81f49abf6b6e799ee01f4f8fa6799"}, + {file = "PyMuPDF-1.23.26-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:0bbb0cf6593e53524f3fc26fb5e6ead17c02c64791caec7c4afe61b677dedf80"}, + {file = "PyMuPDF-1.23.26-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:5ef4360f20015673c20cf59b7e19afc97168795188c584254ed3778cde43ce77"}, + {file = "PyMuPDF-1.23.26-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:d7cd88842b2e7f4c71eef4d87c98c35646b80b60e6375392d7ce40e519261f59"}, + {file = "PyMuPDF-1.23.26-cp38-none-win32.whl", hash = "sha256:6577e2f473625e2d0df5f5a3bf1e4519e94ae749733cc9937994d1b256687bfa"}, + {file = "PyMuPDF-1.23.26-cp38-none-win_amd64.whl", hash = "sha256:fbe1a3255b2cd0d769b2da2c4efdd0c0f30d4961a1aac02c0f75cf951b337aa4"}, + {file = "PyMuPDF-1.23.26-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:73fce034f2afea886a59ead2d0caedf27e2b2a8558b5da16d0286882e0b1eb82"}, + {file = "PyMuPDF-1.23.26-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:b3de8618b7cb5b36db611083840b3bcf09b11a893e2d8262f4e042102c7e65de"}, + {file = "PyMuPDF-1.23.26-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:879e7f5ad35709d8760ab6103c3d5dac8ab8043a856ab3653fd324af7358ee87"}, + {file = "PyMuPDF-1.23.26-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:deee96c2fd415ded7b5070d8d5b2c60679aee6ed0e28ac0d2cb998060d835c2c"}, + {file = "PyMuPDF-1.23.26-cp39-none-win32.whl", hash = "sha256:9f7f4ef99dd8ac97fb0b852efa3dcbee515798078b6c79a6a13c7b1e7c5d41a4"}, + {file = "PyMuPDF-1.23.26-cp39-none-win_amd64.whl", hash = "sha256:ba9a54552c7afb9ec85432c765e2fa9a81413acfaa7d70db7c9b528297749e5b"}, + {file = "PyMuPDF-1.23.26.tar.gz", hash = "sha256:a904261b317b761b0aa2bd2c1f6cd25d25aa4258be67a90c02a878efc5dca649"}, ] [package.dependencies] @@ -3716,13 +3831,13 @@ files = [ [[package]] name = "pypdf" -version = "4.0.2" +version = "4.1.0" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" optional = false python-versions = ">=3.6" files = [ - {file = "pypdf-4.0.2-py3-none-any.whl", hash = "sha256:a62daa2a24d5a608ba1b6284dde185317ce3644f89b9ebe5314d0c5d1c9f257d"}, - {file = "pypdf-4.0.2.tar.gz", hash = "sha256:3316d9ddfcff5df67ae3cdfe8b945c432aa43e7f970bae7c2a4ab4fe129cd937"}, + {file = "pypdf-4.1.0-py3-none-any.whl", hash = "sha256:16cac912a05200099cef3f347c4c7e0aaf0a6d027603b8f9a973c0ea500dff89"}, + {file = "pypdf-4.1.0.tar.gz", hash = "sha256:01c3257ec908676efd60a4537e525b89d48e0852bc92b4e0aa4cc646feda17cc"}, ] [package.dependencies] @@ -3772,13 +3887,13 @@ files = [ [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -4605,48 +4720,60 @@ test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.27" +version = "2.0.28" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, + {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] @@ -5058,24 +5185,24 @@ files = [ [[package]] name = "types-docutils" -version = "0.20.0.20240227" +version = "0.20.0.20240304" description = "Typing stubs for docutils" optional = false python-versions = ">=3.8" files = [ - {file = "types-docutils-0.20.0.20240227.tar.gz", hash = "sha256:7f2dbb02356024b5db3efd9df26b236da050ad2eada89872e5284b4a394b7761"}, - {file = "types_docutils-0.20.0.20240227-py3-none-any.whl", hash = "sha256:51c139502ba0add871392cbc37200a3a64096e61eeb6396727443ba6d38ae579"}, + {file = "types-docutils-0.20.0.20240304.tar.gz", hash = "sha256:c35ae35ca835a5aeead758df411cd46cfb7e7f19f2b223c413dae7e069d5b0be"}, + {file = "types_docutils-0.20.0.20240304-py3-none-any.whl", hash = "sha256:ef02f9d05f2b61500638b1358cdf3fbf975cc5dedaa825a2eb5ea71b7318a760"}, ] [[package]] name = "types-protobuf" -version = "4.24.0.20240129" +version = "4.24.0.20240302" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-4.24.0.20240129.tar.gz", hash = "sha256:8a83dd3b9b76a33e08d8636c5daa212ace1396418ed91837635fcd564a624891"}, - {file = "types_protobuf-4.24.0.20240129-py3-none-any.whl", hash = "sha256:23be68cc29f3f5213b5c5878ac0151706182874040e220cfb11336f9ee642ead"}, + {file = "types-protobuf-4.24.0.20240302.tar.gz", hash = "sha256:f22c00cc0cea9722e71e14d389bba429af9e35a74a949719c167203a5abbe2e4"}, + {file = "types_protobuf-4.24.0.20240302-py3-none-any.whl", hash = "sha256:5c607990f50f14606c2edaf379f8acc7418fef1451b227aa3c6a8a2cbc6ff14a"}, ] [[package]] @@ -5696,4 +5823,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "7521296bc392c8bb14845d4f764faaaef668863f9442fd44a1bb5539d261d77c" +content-hash = "b3cad5844057965a829ca5df97dfcce50bf430eef20b0b69e7be04a0288acef9" diff --git a/pyproject.toml b/pyproject.toml index 466a97043e605..7ef492487c9b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ name = "llama-index" packages = [{from = "_llama-index", include = "llama_index"}] readme = "README.md" repository = "https://github.com/run-llama/llama_index" -version = "0.10.14" +version = "0.10.16" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" @@ -57,7 +57,7 @@ llama-index-agent-openai = "^0.1.4" llama-index-readers-file = "^0.1.4" llama-index-readers-llama-parse = "^0.1.2" llama-index-indices-managed-llama-cloud = "^0.1.2" -llama-index-core = "^0.10.14" +llama-index-core = "^0.10.16" llama-index-multi-modal-llms-openai = "^0.1.3" llama-index-cli = "^0.1.2"