diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..817826de84d615125248d0ed0130cacaf738f0e2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +.env +.git +.gitignore +notebooks/ +.ipynb_checkpoints/ +wandb/ +.cache/ +*.log +*.tmp +.DS_Store diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..2d544f8bf9ad87257d1d9a08100383ef94e7abb8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.png filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.db filter=lfs diff=lfs merge=lfs -text +*.sqlite3 filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fe866ef3d1d36ee9bfd81b3708724873b5bf1167 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +querymind/ +venv/ +.venv/ +__pycache__/ +*.pyc +.env \ No newline at end of file diff --git a/.here b/.here new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..98e1ea7de167536c3104af51a013dd494c577468 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install -r requirements.txt + +COPY . . + +EXPOSE 7860 + +CMD ["python", "src\app.py"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..c7e97f67e4f5b37a0bb33af30024ecf81724c426 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Beshoy Arnest + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Notebooks/Tools/RAG_tool_step_by_step/rag_tool.ipynb b/Notebooks/Tools/RAG_tool_step_by_step/rag_tool.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..2e434ec977db7a56b556ee8b7f98c97336d5b789 --- /dev/null +++ b/Notebooks/Tools/RAG_tool_step_by_step/rag_tool.ipynb @@ -0,0 +1,474 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_chroma import Chroma\n", + "import os\n", + "from pyprojroot import here\n", + "from langchain_huggingface import HuggingFaceEmbeddings\n", + "from groq import Groq\n", + "from dotenv import load_dotenv\n", + "from pprint import pprint\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Load environment variables and configs**" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['GROQ_API_KEY'] = os.getenv(\"GROQ_API_KEY\")\n", + "\n", + "EMBEDDING_MODEL = \"all-MiniLM-L6-v2\"\n", + "VECTORDB_DIR = \"data/airline_policy_vectordb\"\n", + "K=2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Load the vectorDB**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "f:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\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", + "Loading weights: 100%|██████████| 103/103 [00:00<00:00, 6866.51it/s]\n", + "Failed to send telemetry event ClientStartEvent: capture() takes 1 positional argument but 3 were given\n", + "Failed to send telemetry event ClientCreateCollectionEvent: capture() takes 1 positional argument but 3 were given\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of vectors in vectordb: 22 \n", + "\n", + "\n" + ] + } + ], + "source": [ + "vectordb = Chroma(\n", + " collection_name=\"rag-chroma\",\n", + " persist_directory=str(here(VECTORDB_DIR)),\n", + " embedding_function=HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)\n", + ")\n", + "print(\"Number of vectors in vectordb:\",\n", + " vectordb._collection.count(), \"\\n\\n\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Sample Query**" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "message = \"What is the cancelation rule for a flight ticket at swiss airline policy?\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Perform the vector Search**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Failed to send telemetry event CollectionQueryEvent: capture() takes 1 positional argument but 3 were given\n" + ] + } + ], + "source": [ + "docs = vectordb.similarity_search(message, k=K)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(metadata={'page': 8, 'source': 'F:\\\\end_to_end_AI_Projects\\\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\\\data\\\\unstructured_docs\\\\swiss_airline_policy\\\\swiss_faq.pdf'}, page_content=\"How to Cancel a Swiss Air Flight: 877-\\n5O7-7341 Step-by-Step Guide\\nSwiss Air is a premium airline based in Switzerland that of fers a range of domestic and international flights to\\npassengers. However , sometimes situations arise where passengers may need to cancel their flights. In such cases, it is\\nimportant to understand the Swiss Air Cancellation Policy to avoid any confusion or additional charges.\\nSwiss International Airlines Cancellation Policy In this article, we will provide you with everything you need to know about\\nthe Swiss Air Cancellation Policy , including how to cancel a Swiss Air flight, the fees associated with cancelling a flight,\\nand the refund policy .\\nIf you have booked a flight with Swiss Airlines but need to cancel it, it's important to understand their cancellation policy\\nto avoid any unnecessary fees or charges. Swiss Airlines of fers dif ferent fare types, each with their own specific\\ncancellation terms and conditions. The most flexible fare types such as Flex and Business Flex allow you to cancel your\\nflight up to 24 hours before departure without any penalty . For other fare types, cancellation fees may apply . If you cancel\\nyour Swiss Airlines flight outside of the 24-hour window , cancellation fees will be charged depending on your fare type\\nand the time of cancellation. For example, if you cancel a non-flexible economy class ticket, a cancellation fee will be\\ncharged. The closer you cancel to the departure date, the higher the cancellation fee. In some cases, Swiss Airlines may\\nallow you to make changes to your flight instead of cancelling it outright. However , these changes may also come with\\nfees or penalties depending on your fare type and the type of change requested. If Swiss Airlines cancels your flight, you\\nmay be entitled to a full refund or rebooking on another flight. However , if the cancellation is due to extraordinary\\ncircumstances such as bad weather or political unrest, Swiss Airlines may not be obligated to of fer any compensation. In\\nsummary , Swiss Airlines' cancellation policy varies depending on your fare type and the time of cancellation. T o avoid any\\nunnecessary fees or charges, it's important to familiarise yourself with the terms and conditions of your ticket and to\\ncontact Swiss Airlines as soon as possible if you need to make changes or cancel your flight.\"),\n", + " Document(metadata={'page': 9, 'source': 'F:\\\\end_to_end_AI_Projects\\\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\\\data\\\\unstructured_docs\\\\swiss_airline_policy\\\\swiss_faq.pdf'}, page_content=\"for a refund or may only be able to receive a partial refund. If you booked your flight through a third-party website or\\ntravel agent, you may need to contact them directly to cancel your flight. Always check the terms and conditions of your\\nticket to make sure you understand the cancellation policy and any associated fees or penalties. If you're cancelling your\\nflight due to unforeseen circumstances such as a medical emergency or a natural disaster , Swiss Air may of fer you\\nspecial exemptions or accommodations. What is Swiss Airlines 24 Hour Cancellation Policy? Swiss Airlines has a 24\")]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Prepare the prompt for the Groq model**" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "question = \"# User new question:\\n\" + message\n", + "retrieved_content = \"\"\n", + "for doc in docs:\n", + " retrieved_content += f\"{doc.page_content}\\n\\n\"\n", + "prompt = f\"# Content:\\n{retrieved_content}\\n\\n{question}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Prepared prompt" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('# Content:\\n'\n", + " 'How to Cancel a Swiss Air Flight: 877-\\n'\n", + " '5O7-7341 Step-by-Step Guide\\n'\n", + " 'Swiss Air is a premium airline based in Switzerland that of fers a range of '\n", + " 'domestic and international flights to\\n'\n", + " 'passengers. However , sometimes situations arise where passengers may need '\n", + " 'to cancel their flights. In such cases, it is\\n'\n", + " 'important to understand the Swiss Air Cancellation Policy to avoid any '\n", + " 'confusion or additional charges.\\n'\n", + " 'Swiss International Airlines Cancellation Policy In this article, we will '\n", + " 'provide you with everything you need to know about\\n'\n", + " 'the Swiss Air Cancellation Policy , including how to cancel a Swiss Air '\n", + " 'flight, the fees associated with cancelling a flight,\\n'\n", + " 'and the refund policy .\\n'\n", + " \"If you have booked a flight with Swiss Airlines but need to cancel it, it's \"\n", + " 'important to understand their cancellation policy\\n'\n", + " 'to avoid any unnecessary fees or charges. Swiss Airlines of fers dif ferent '\n", + " 'fare types, each with their own specific\\n'\n", + " 'cancellation terms and conditions. The most flexible fare types such as Flex '\n", + " 'and Business Flex allow you to cancel your\\n'\n", + " 'flight up to 24 hours before departure without any penalty . For other fare '\n", + " 'types, cancellation fees may apply . If you cancel\\n'\n", + " 'your Swiss Airlines flight outside of the 24-hour window , cancellation fees '\n", + " 'will be charged depending on your fare type\\n'\n", + " 'and the time of cancellation. For example, if you cancel a non-flexible '\n", + " 'economy class ticket, a cancellation fee will be\\n'\n", + " 'charged. The closer you cancel to the departure date, the higher the '\n", + " 'cancellation fee. In some cases, Swiss Airlines may\\n'\n", + " 'allow you to make changes to your flight instead of cancelling it outright. '\n", + " 'However , these changes may also come with\\n'\n", + " 'fees or penalties depending on your fare type and the type of change '\n", + " 'requested. If Swiss Airlines cancels your flight, you\\n'\n", + " 'may be entitled to a full refund or rebooking on another flight. However , '\n", + " 'if the cancellation is due to extraordinary\\n'\n", + " 'circumstances such as bad weather or political unrest, Swiss Airlines may '\n", + " 'not be obligated to of fer any compensation. In\\n'\n", + " \"summary , Swiss Airlines' cancellation policy varies depending on your fare \"\n", + " 'type and the time of cancellation. T o avoid any\\n'\n", + " \"unnecessary fees or charges, it's important to familiarise yourself with the \"\n", + " 'terms and conditions of your ticket and to\\n'\n", + " 'contact Swiss Airlines as soon as possible if you need to make changes or '\n", + " 'cancel your flight.\\n'\n", + " '\\n'\n", + " 'for a refund or may only be able to receive a partial refund. If you booked '\n", + " 'your flight through a third-party website or\\n'\n", + " 'travel agent, you may need to contact them directly to cancel your flight. '\n", + " 'Always check the terms and conditions of your\\n'\n", + " 'ticket to make sure you understand the cancellation policy and any '\n", + " \"associated fees or penalties. If you're cancelling your\\n\"\n", + " 'flight due to unforeseen circumstances such as a medical emergency or a '\n", + " 'natural disaster , Swiss Air may of fer you\\n'\n", + " 'special exemptions or accommodations. What is Swiss Airlines 24 Hour '\n", + " 'Cancellation Policy? Swiss Airlines has a 24\\n'\n", + " '\\n'\n", + " '\\n'\n", + " '\\n'\n", + " '# User new question:\\n'\n", + " 'What is the cancelation rule for a flight ticket at swiss airline policy?')\n" + ] + } + ], + "source": [ + "pprint(prompt)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pass the prompt to the GPT model and get the response**" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "client = Groq()\n", + "response = client.chat.completions.create(\n", + " model=\"llama-3.3-70b-versatile\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You will receive a user's query and possible content where the answer might be. If the answer is found, provide it, if not, state that the answer does not exist.\"},\n", + " {\"role\": \"user\", \"content\": prompt}\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Printing the response" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('Swiss Airlines allows passengers to cancel their flights based on the type '\n", + " 'of ticket purchased. Here are the cancellation rules:\\n'\n", + " '\\n'\n", + " '1. **Flex Ticket**: You can cancel your flight without incurring any fees '\n", + " 'and receive a full refund.\\n'\n", + " '\\n'\n", + " '2. **Standard Ticket**: \\n'\n", + " ' - If you cancel within 24 hours of booking, you can receive a full '\n", + " 'refund.\\n'\n", + " ' - If you cancel after 24 hours, you may be charged a cancellation fee '\n", + " '(ranging from 100 to 250 CHF) and receive a partial refund.\\n'\n", + " '\\n'\n", + " '3. **Economy Ticket**: \\n'\n", + " ' - If you cancel within 24 hours of booking, you can receive a full '\n", + " 'refund.\\n'\n", + " ' - If you cancel after 24 hours, you may be charged a cancellation fee '\n", + " '(ranging from 150 to 350 CHF) and receive a partial refund.\\n'\n", + " '\\n'\n", + " \"It's important to check the terms and conditions of your specific ticket, as \"\n", + " 'fees and refund eligibility may vary. Additionally, if you cancel your '\n", + " 'flight outside of the 24-hour window, cancellation fees apply.')\n" + ] + } + ], + "source": [ + "pprint(response.choices[0].message.content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**RAG Tool design using LangChain**" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "@tool\n", + "def lookup_swiss_airline_policy(query: str)->str:\n", + " \"\"\"Search within the Swiss Airline's company policies to check whether certain options are permitted. Input should be a search query.\"\"\"\n", + " vectordb = Chroma(\n", + " collection_name=\"rag-chroma\",\n", + " persist_directory=str(here(VECTORDB_DIR)),\n", + " embedding_function=HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)\n", + " )\n", + " docs = vectordb.similarity_search(query, k=K)\n", + " return \"\\n\\n\".join([doc.page_content for doc in docs])" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lookup_swiss_airline_policy\n", + "{'query': {'title': 'Query', 'type': 'string'}}\n", + "Search within the Swiss Airline's company policies to check whether certain options are permitted. Input should be a search query.\n" + ] + } + ], + "source": [ + "print(lookup_swiss_airline_policy.name)\n", + "print(lookup_swiss_airline_policy.args)\n", + "print(lookup_swiss_airline_policy.description)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading weights: 100%|██████████| 103/103 [00:00<00:00, 2156.99it/s]\n", + "Failed to send telemetry event ClientStartEvent: capture() takes 1 positional argument but 3 were given\n", + "Failed to send telemetry event ClientCreateCollectionEvent: capture() takes 1 positional argument but 3 were given\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('hour cancellation policy that allows passengers to cancel their flights '\n", + " 'within 24 hours of booking at +1-877-507-7341\\n'\n", + " 'without penalty . This policy applies to all fare types, including '\n", + " 'non-refundable tickets. If you cancel your Swiss Airlines\\n'\n", + " \"flight within 24 hours of booking, you'll receive a full refund of your \"\n", + " 'ticket price.\\n'\n", + " 'How to Cancel Swiss Airlines Flight within 24 Hours? If you need to cancel '\n", + " 'your Swiss Airlines flight within 24 hours of\\n'\n", + " 'booking, you can do so easily online. Here are the steps to follow:\\n'\n", + " 'Go to Swiss Airlines\\' website and click on the \"Manage your bookings\" tab. '\n", + " 'Enter your booking reference number and last\\n'\n", + " 'name to access your booking. Select the flight you want to cancel and click '\n", + " 'on \"Cancel flight.\" Confirm your cancellation\\n'\n", + " \"and you'll receive a full refund of your ticket price. If you booked your \"\n", + " \"Swiss Airlines flight through a travel agent, you'll\\n\"\n", + " 'need to contact them directly to cancel your flight within 24 hours.\\n'\n", + " 'Important Things to Keep in Mind for Swiss Airlines 24 Hour Cancellation '\n", + " 'Here are some important things to keep in mind\\n'\n", + " 'when cancelling your Swiss Airlines flight within 24 hours:\\n'\n", + " \"Swiss Airlines' 24 hour cancellation policy only applies to flights booked \"\n", + " 'directly through Swiss Airlines. If you booked\\n'\n", + " \"your flight through a travel agent or third-party website, you'll need to \"\n", + " 'check their cancellation policy . If you cancel your\\n'\n", + " 'Swiss Airlines flight after the 24 hour window , you may be subject to '\n", + " 'cancellation fees or penalties. If you have a non-\\n'\n", + " \"refundable ticket and cancel your flight within 24 hours of booking, you'll \"\n", + " 'receive a full refund of your ticket price.\\n'\n", + " 'However , if you cancel your flight after the 24 hour window , you may not '\n", + " \"be eligible for a refund. Swiss Airlines' 24 hour\\n\"\n", + " 'cancellation policy allows passengers to cancel their flights within 24 '\n", + " 'hours of booking without penalty . If you need to\\n'\n", + " 'cancel your Swiss Airlines flight within 24 hours, you can do so easily '\n", + " 'online. Just remember to check the terms and\\n'\n", + " \"conditions of your ticket to make sure you're eligible for a refund.\\n\"\n", + " 'Swiss Air Cancellation Fees The cancellation fees for Swiss Air flights may '\n", + " 'vary depending on the type of ticket you have\\n'\n", + " 'purchased. The airline of fers three dif ferent types of tickets, which '\n", + " 'are:\\n'\n", + " '\\n'\n", + " 'for a refund or may only be able to receive a partial refund. If you booked '\n", + " 'your flight through a third-party website or\\n'\n", + " 'travel agent, you may need to contact them directly to cancel your flight. '\n", + " 'Always check the terms and conditions of your\\n'\n", + " 'ticket to make sure you understand the cancellation policy and any '\n", + " \"associated fees or penalties. If you're cancelling your\\n\"\n", + " 'flight due to unforeseen circumstances such as a medical emergency or a '\n", + " 'natural disaster , Swiss Air may of fer you\\n'\n", + " 'special exemptions or accommodations. What is Swiss Airlines 24 Hour '\n", + " 'Cancellation Policy? Swiss Airlines has a 24')\n" + ] + } + ], + "source": [ + "pprint(lookup_swiss_airline_policy.invoke(\"can I cancel my ticket?\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "querymind (3.12.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", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/Tools/sql_agents/sql_agent_chain_for_large_db.ipynb b/Notebooks/Tools/sql_agents/sql_agent_chain_for_large_db.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ebd4dba772984ed014a710c525ca56825cc400e4 --- /dev/null +++ b/Notebooks/Tools/sql_agents/sql_agent_chain_for_large_db.ipynb @@ -0,0 +1,680 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Reference: https://python.langchain.com/v0.1/docs/use_cases/sql/large_db/**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What happens in this notebook:\n", + "\n", + "### **Table Model Definition**\n", + " - **`Table` Class**: This is a simple Pydantic model representing a SQL table. It has one attribute, `name`, which is a string and is described as \"Name of table in SQL database.\"\n", + " - This model is used in the extraction process to match relevant SQL tables based on the user's query.\n", + "\n", + "### **Helper Function - `get_tables`**\n", + " - **`get_tables`**: This function takes a list of `Table` objects (i.e., categories such as \"Music\" or \"Business\") and returns a list of corresponding SQL table names based on the category.\n", + " - For example, if the category is `\"Music\"`, the tables `\"Album\"`, `\"Artist\"`, `\"Genre\"`, etc., are added to the result.\n", + " - Similarly, for `\"Business\"`, the corresponding tables like `\"Customer\"`, `\"Employee\"`, etc., are included.\n", + "\n", + "### **Designing the agent for the large DB**\n", + "\n", + "- **Step 1: Initialize LLM (`sql_agent_llm`)**: The LLM is instantiated with a given model (e.g., `\"gpt-3.5-turbo\"`) and temperature. The temperature controls how creative/random the model's responses are.\n", + "- **Step 2: Connect to the SQL Database (`db`)**: The connection to the Chinook SQLite database is established. The database URI is constructed using the `sqldb_directory` provided.\n", + "- **Step 3: Define Category Chain (`category_chain`)**: The `category_chain_system` is defined, which is a string explaining the categories available (like \"Music\" and \"Business\"). This chain determines which SQL tables are relevant to the user query based on the category.\n", + "- **Step 4: Chain Creation**:\n", + "- **`category_chain`**: This uses the `create_extraction_chain_pydantic` function, which creates an extraction chain that identifies relevant SQL tables from the user's question using the `Table` Pydantic model and the LLM.\n", + "- **`table_chain`**: A chain is formed by combining the output from `category_chain` with the `get_tables` function, so it maps categories to the actual SQL tables.\n", + "- **Step 5: Query Chain (`query_chain`)**: This creates a SQL query chain using the LLM and the database (`self.db`). It takes the SQL tables and constructs a query.\n", + "- **Step 6: Table Chain Input Handling**: The `\"question\"` key from the user input is mapped to the `\"input\"` key expected by the `table_chain`. This enables the chain to process user queries correctly.\n", + "- **Step 7: Full Chain Construction**: Finally, the full chain (`full_chain`) is created by combining:\n", + "1. **`RunnablePassthrough.assign`**: This sets up a step that assigns the `table_names_to_use` using the result of the `table_chain`.\n", + "2. **`query_chain`**: Executes the SQL query once the relevant tables are identified." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from dotenv import load_dotenv\n", + "from pyprojroot import here\n", + "from typing import List\n", + "from langchain_community.utilities import SQLDatabase\n", + "from langchain_groq import ChatGroq\n", + "from pprint import pprint\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from pydantic import BaseModel, Field\n", + "from typing import List\n", + "\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Set the environment variables and load the LLM**" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['GROQ_API_KEY'] = os.getenv(\"GROQ_API_KEY\")\n", + "\n", + "\n", + "sql_agent_llm = ChatGroq(model=\"openai/gpt-oss-120b\", temperature=0)\n", + "table_extractor_llm = ChatGroq(model=\"openai/gpt-oss-120b\", temperature=0)\n", + "# llm = ChatGroq(model=\"llama3-70b-8192\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\"" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sqldb_directory = here(\"data/Chinook.db\")\n", + "db = SQLDatabase.from_uri(f\"sqlite:///{sqldb_directory}\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM Artist LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Prepare the `Table` class**" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "# class Table(BaseModel):\n", + "# \"\"\"\n", + "# Represents a table in the SQL database.\n", + "\n", + "# Attributes:\n", + "# name (str): The name of the table in the SQL database.\n", + "# \"\"\"\n", + "# name: str = Field(description=\"Name of table in SQL database.\")\n", + "class Table(BaseModel):\n", + " \"\"\"Table in SQL database.\"\"\"\n", + " name: str = Field(description=\"Name of table in SQL database.\")\n", + "\n", + "class Tables(BaseModel):\n", + " \"\"\"Extract all relevant tables.\"\"\"\n", + " tables: List[Table] = Field(description=\"List of relevant tables.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Strategy A:**" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('Album\\n'\n", + " 'Artist\\n'\n", + " 'Customer\\n'\n", + " 'Employee\\n'\n", + " 'Genre\\n'\n", + " 'Invoice\\n'\n", + " 'InvoiceLine\\n'\n", + " 'MediaType\\n'\n", + " 'Playlist\\n'\n", + " 'PlaylistTrack\\n'\n", + " 'Track')\n" + ] + } + ], + "source": [ + "table_names = \"\\n\".join(db.get_usable_table_names())\n", + "pprint(table_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Table(name='Artist'), Table(name='Track'), Table(name='Genre'), Table(name='Album')]\n" + ] + } + ], + "source": [ + "system = f\"\"\"Return the names of ALL the SQL tables that MIGHT be relevant to the user question. \\\n", + "The tables are:\n", + "\n", + "{table_names}\n", + "\n", + "Remember to include ALL POTENTIALLY RELEVANT tables, even if you're not sure that they're needed.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", system),\n", + " (\"human\", \"{input}\")\n", + "])\n", + "\n", + "# This works with Groq, OpenAI, Anthropic, etc.\n", + "table_chain = prompt | table_extractor_llm.with_structured_output(Tables)\n", + "\n", + "result = table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})\n", + "print(result.tables)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Strategy B:**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Music:\n", + "\n", + "- \"Album\"\n", + "- \"Artist\"\n", + "- \"Genre\"\n", + "- \"MediaType\"\n", + "- \"Playlist\"\n", + "- \"PlaylistTrack\"\n", + "- \"Track\"\n", + "\n", + "Business:\n", + "\n", + "- \"Customer\"\n", + "- \"Employee\"\n", + "- \"Invoice\"\n", + "- \"InvoiceLine\"" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Table(name='Album'), Table(name='Artist'), Table(name='Genre'), Table(name='MediaType'), Table(name='Playlist'), Table(name='PlaylistTrack'), Table(name='Track')]\n", + "['Album', 'Artist', 'Genre', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + } + ], + "source": [ + "system = \"\"\"You will receive a question.\n", + "\n", + "If the question is about **Music**, return **ALL** these tables:\n", + " - \"Album\"\n", + " - \"Artist\"\n", + " - \"Genre\"\n", + " - \"MediaType\"\n", + " - \"Playlist\"\n", + " - \"PlaylistTrack\"\n", + " - \"Track\"\n", + "\n", + "If the question is about **Business**, return **ALL** these tables:\n", + " - \"Customer\"\n", + " - \"Employee\"\n", + " - \"Invoice\"\n", + " - \"InvoiceLine\"\n", + "\n", + "If you are unsure, return the full list of all available tables for both Music and Business categories.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", system),\n", + " (\"human\", \"{input}\")\n", + "])\n", + "\n", + "table_chain = prompt | table_extractor_llm.with_structured_output(Tables)\n", + "\n", + "# Test it\n", + "result = table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})\n", + "print(result.tables)\n", + "\n", + "# To get just the table names as a list\n", + "table_names = [t.name for t in result.tables]\n", + "print(table_names)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Strategy C:**\n", + "\n", + "- **Step 1: Define the category**" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Table(name='Music')]\n", + "['Music']\n" + ] + } + ], + "source": [ + "system = \"\"\"Return the names of the SQL tables that are relevant to the user question. \\\n", + "The tables are:\n", + "\n", + "Music\n", + "Business\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", system),\n", + " (\"human\", \"{input}\")\n", + "])\n", + "\n", + "category_chain = prompt | table_extractor_llm.with_structured_output(Tables)\n", + "\n", + "# Test it\n", + "result = category_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})\n", + "print(result.tables)\n", + "\n", + "# To get just the category names as a list\n", + "category_names = [t.name for t in result.tables]\n", + "print(category_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Tables(tables=[Table(name='Music')])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "category_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Step 2: Execute the python function**" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Album', 'Artist', 'Genre', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + } + ], + "source": [ + "from langchain_core.runnables import RunnableLambda\n", + "\n", + "def get_tables(result: Tables) -> List[str]:\n", + " \"\"\"Maps category names to corresponding SQL table names.\n", + "\n", + " Args:\n", + " result (Tables): A `Tables` object containing a list of `Table` objects\n", + " representing the relevant categories (e.g., Music, Business).\n", + "\n", + " Returns:\n", + " List[str]: A list of SQL table names corresponding to the provided categories.\n", + " \"\"\"\n", + " tables = []\n", + " for category in result.tables: # ← extract .tables from the Tables object\n", + " if category.name == \"Music\":\n", + " tables.extend([\n", + " \"Album\",\n", + " \"Artist\", \n", + " \"Genre\",\n", + " \"MediaType\",\n", + " \"Playlist\",\n", + " \"PlaylistTrack\",\n", + " \"Track\",\n", + " ])\n", + " elif category.name == \"Business\":\n", + " tables.extend([\"Customer\", \"Employee\", \"Invoice\", \"InvoiceLine\"])\n", + " return tables\n", + "\n", + "table_chain = category_chain | RunnableLambda(get_tables)\n", + "\n", + "# Test\n", + "result = table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Final step:**\n", + "\n", + "**Attach the desired strategy to your SQL agent**" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables import RunnablePassthrough, RunnableLambda\n", + "from langchain.chains import create_sql_query_chain\n", + "from operator import itemgetter\n", + "import re\n", + "\n", + "def extract_sql(llm_response: str) -> str:\n", + " \"\"\"Extracts clean SQL query from LLM response.\n", + "\n", + " Args:\n", + " llm_response (str): The full LLM response containing the SQL query\n", + " possibly wrapped in markdown code blocks and prefixed\n", + " with 'Question:' and 'SQLQuery:' labels.\n", + "\n", + " Returns:\n", + " str: The clean SQL query string ready to be executed.\n", + " \"\"\"\n", + " match = re.search(r\"```sql\\s*(.*?)\\s*```\", llm_response, re.DOTALL)\n", + " if match:\n", + " return match.group(1).strip()\n", + " match = re.search(r\"SQLQuery:\\s*(.*)\", llm_response, re.DOTALL)\n", + " if match:\n", + " return match.group(1).strip()\n", + " return llm_response.strip()\n", + "\n", + "\n", + "# table_chain: categories → table names\n", + "_table_mapper = category_chain | RunnableLambda(get_tables)\n", + "\n", + "# Wire \"question\" → \"input\" expected by category_chain\n", + "_table_chain_with_key = {\"input\": itemgetter(\"question\")} | _table_mapper\n", + "\n", + "# Full chain\n", + "query_chain = create_sql_query_chain(sql_agent_llm, db)\n", + "\n", + "full_chain = (\n", + " RunnablePassthrough.assign(table_names_to_use=_table_chain_with_key)\n", + " | query_chain\n", + " | RunnableLambda(extract_sql)\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Test the agent**" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "ename": "APITimeoutError", + "evalue": "Request timed out.", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mConnectTimeout\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_transports\\default.py:101\u001b[39m, in \u001b[36mmap_httpcore_exceptions\u001b[39m\u001b[34m()\u001b[39m\n\u001b[32m 100\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m101\u001b[39m \u001b[38;5;28;01myield\u001b[39;00m\n\u001b[32m 102\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_transports\\default.py:250\u001b[39m, in \u001b[36mHTTPTransport.handle_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 249\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m map_httpcore_exceptions():\n\u001b[32m--> \u001b[39m\u001b[32m250\u001b[39m resp = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_pool\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mhandle_request\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mreq\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 252\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(resp.stream, typing.Iterable)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_sync\\connection_pool.py:256\u001b[39m, in \u001b[36mConnectionPool.handle_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 255\u001b[39m \u001b[38;5;28mself\u001b[39m._close_connections(closing)\n\u001b[32m--> \u001b[39m\u001b[32m256\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m exc \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 258\u001b[39m \u001b[38;5;66;03m# Return the response. Note that in this case we still have to manage\u001b[39;00m\n\u001b[32m 259\u001b[39m \u001b[38;5;66;03m# the point at which the response is closed.\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_sync\\connection_pool.py:236\u001b[39m, in \u001b[36mConnectionPool.handle_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 234\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 235\u001b[39m \u001b[38;5;66;03m# Send the request on the assigned connection.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m236\u001b[39m response = \u001b[30;43mconnection\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mhandle_request\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 237\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mpool_request\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\n\u001b[32m 238\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 239\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ConnectionNotAvailable:\n\u001b[32m 240\u001b[39m \u001b[38;5;66;03m# In some cases a connection may initially be available to\u001b[39;00m\n\u001b[32m 241\u001b[39m \u001b[38;5;66;03m# handle a request, but then become unavailable.\u001b[39;00m\n\u001b[32m 242\u001b[39m \u001b[38;5;66;03m#\u001b[39;00m\n\u001b[32m 243\u001b[39m \u001b[38;5;66;03m# In this case we clear the connection and try again.\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_sync\\connection.py:101\u001b[39m, in \u001b[36mHTTPConnection.handle_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 100\u001b[39m \u001b[38;5;28mself\u001b[39m._connect_failed = \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m101\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m exc\n\u001b[32m 103\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._connection.handle_request(request)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_sync\\connection.py:78\u001b[39m, in \u001b[36mHTTPConnection.handle_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 77\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._connection \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m78\u001b[39m stream = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_connect\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 80\u001b[39m ssl_object = stream.get_extra_info(\u001b[33m\"\u001b[39m\u001b[33mssl_object\u001b[39m\u001b[33m\"\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_sync\\connection.py:156\u001b[39m, in \u001b[36mHTTPConnection._connect\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 155\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m Trace(\u001b[33m\"\u001b[39m\u001b[33mstart_tls\u001b[39m\u001b[33m\"\u001b[39m, logger, request, kwargs) \u001b[38;5;28;01mas\u001b[39;00m trace:\n\u001b[32m--> \u001b[39m\u001b[32m156\u001b[39m stream = \u001b[30;43mstream\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mstart_tls\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 157\u001b[39m trace.return_value = stream\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_backends\\sync.py:154\u001b[39m, in \u001b[36mSyncStream.start_tls\u001b[39m\u001b[34m(self, ssl_context, server_hostname, timeout)\u001b[39m\n\u001b[32m 150\u001b[39m exc_map: ExceptionMapping = {\n\u001b[32m 151\u001b[39m socket.timeout: ConnectTimeout,\n\u001b[32m 152\u001b[39m \u001b[38;5;167;01mOSError\u001b[39;00m: ConnectError,\n\u001b[32m 153\u001b[39m }\n\u001b[32m--> \u001b[39m\u001b[32m154\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m map_exceptions(exc_map):\n\u001b[32m 155\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\Python312\\Lib\\contextlib.py:158\u001b[39m, in \u001b[36m_GeneratorContextManager.__exit__\u001b[39m\u001b[34m(self, typ, value, traceback)\u001b[39m\n\u001b[32m 157\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m158\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mgen\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mthrow\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mvalue\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 159\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 160\u001b[39m \u001b[38;5;66;03m# Suppress StopIteration *unless* it's the same exception that\u001b[39;00m\n\u001b[32m 161\u001b[39m \u001b[38;5;66;03m# was passed to throw(). This prevents a StopIteration\u001b[39;00m\n\u001b[32m 162\u001b[39m \u001b[38;5;66;03m# raised inside the \"with\" statement from being suppressed.\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpcore\\_exceptions.py:14\u001b[39m, in \u001b[36mmap_exceptions\u001b[39m\u001b[34m(map)\u001b[39m\n\u001b[32m 13\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(exc, from_exc):\n\u001b[32m---> \u001b[39m\u001b[32m14\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m to_exc(exc) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mexc\u001b[39;00m\n\u001b[32m 15\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m\n", + "\u001b[31mConnectTimeout\u001b[39m: [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[31mConnectTimeout\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\groq\\_base_client.py:980\u001b[39m, in \u001b[36mSyncAPIClient.request\u001b[39m\u001b[34m(self, cast_to, options, stream, stream_cls)\u001b[39m\n\u001b[32m 979\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m980\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_client\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43msend\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 981\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 982\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mor\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_should_stream_response_body\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 983\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 984\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 985\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m httpx.TimeoutException \u001b[38;5;28;01mas\u001b[39;00m err:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_client.py:914\u001b[39m, in \u001b[36mClient.send\u001b[39m\u001b[34m(self, request, stream, auth, follow_redirects)\u001b[39m\n\u001b[32m 912\u001b[39m auth = \u001b[38;5;28mself\u001b[39m._build_request_auth(request, auth)\n\u001b[32m--> \u001b[39m\u001b[32m914\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_send_handling_auth\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 915\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 916\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mauth\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mauth\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 917\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mfollow_redirects\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mfollow_redirects\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 918\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mhistory\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43m[\u001b[39;49m\u001b[30;43m]\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 919\u001b[39m \u001b[30;43m\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 920\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_client.py:942\u001b[39m, in \u001b[36mClient._send_handling_auth\u001b[39m\u001b[34m(self, request, auth, follow_redirects, history)\u001b[39m\n\u001b[32m 941\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m942\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_send_handling_redirects\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 943\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 944\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mfollow_redirects\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mfollow_redirects\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 945\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mhistory\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mhistory\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 946\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 947\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_client.py:979\u001b[39m, in \u001b[36mClient._send_handling_redirects\u001b[39m\u001b[34m(self, request, follow_redirects, history)\u001b[39m\n\u001b[32m 977\u001b[39m hook(request)\n\u001b[32m--> \u001b[39m\u001b[32m979\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_send_single_request\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 980\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_client.py:1014\u001b[39m, in \u001b[36mClient._send_single_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 1013\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m request_context(request=request):\n\u001b[32m-> \u001b[39m\u001b[32m1014\u001b[39m response = \u001b[30;43mtransport\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mhandle_request\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 1016\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(response.stream, SyncByteStream)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_transports\\default.py:249\u001b[39m, in \u001b[36mHTTPTransport.handle_request\u001b[39m\u001b[34m(self, request)\u001b[39m\n\u001b[32m 237\u001b[39m req = httpcore.Request(\n\u001b[32m 238\u001b[39m method=request.method,\n\u001b[32m 239\u001b[39m url=httpcore.URL(\n\u001b[32m (...)\u001b[39m\u001b[32m 247\u001b[39m extensions=request.extensions,\n\u001b[32m 248\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m249\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m map_httpcore_exceptions():\n\u001b[32m 250\u001b[39m resp = \u001b[38;5;28mself\u001b[39m._pool.handle_request(req)\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\Python312\\Lib\\contextlib.py:158\u001b[39m, in \u001b[36m_GeneratorContextManager.__exit__\u001b[39m\u001b[34m(self, typ, value, traceback)\u001b[39m\n\u001b[32m 157\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m158\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mgen\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mthrow\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mvalue\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 159\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 160\u001b[39m \u001b[38;5;66;03m# Suppress StopIteration *unless* it's the same exception that\u001b[39;00m\n\u001b[32m 161\u001b[39m \u001b[38;5;66;03m# was passed to throw(). This prevents a StopIteration\u001b[39;00m\n\u001b[32m 162\u001b[39m \u001b[38;5;66;03m# raised inside the \"with\" statement from being suppressed.\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\httpx\\_transports\\default.py:118\u001b[39m, in \u001b[36mmap_httpcore_exceptions\u001b[39m\u001b[34m()\u001b[39m\n\u001b[32m 117\u001b[39m message = \u001b[38;5;28mstr\u001b[39m(exc)\n\u001b[32m--> \u001b[39m\u001b[32m118\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m mapped_exc(message) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mexc\u001b[39;00m\n", + "\u001b[31mConnectTimeout\u001b[39m: [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[31mAPITimeoutError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[54]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# ── Test ─────────────────────────────────────────────────────────────────────\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m query = full_chain.invoke({\u001b[33m\"question\"\u001b[39m: \u001b[33m\"What is the most popular genre by number of tracks?\"\u001b[39m})\n\u001b[32m 3\u001b[39m print(\u001b[33m\"Clean SQL:\\n\"\u001b[39m, query)\n\u001b[32m 4\u001b[39m \n\u001b[32m 5\u001b[39m result = db.run(query)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\base.py:2876\u001b[39m, in \u001b[36mRunnableSequence.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 2874\u001b[39m context.run(_set_config_context, config)\n\u001b[32m 2875\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m i == \u001b[32m0\u001b[39m:\n\u001b[32m-> \u001b[39m\u001b[32m2876\u001b[39m \u001b[38;5;28minput\u001b[39m = \u001b[30;43mcontext\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrun\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mstep\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43minvoke\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 2877\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 2878\u001b[39m \u001b[38;5;28minput\u001b[39m = context.run(step.invoke, \u001b[38;5;28minput\u001b[39m, config)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\passthrough.py:495\u001b[39m, in \u001b[36mRunnableAssign.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 489\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34minvoke\u001b[39m(\n\u001b[32m 490\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 491\u001b[39m \u001b[38;5;28minput\u001b[39m: Dict[\u001b[38;5;28mstr\u001b[39m, Any],\n\u001b[32m 492\u001b[39m config: Optional[RunnableConfig] = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 493\u001b[39m **kwargs: Any,\n\u001b[32m 494\u001b[39m ) -> Dict[\u001b[38;5;28mstr\u001b[39m, Any]:\n\u001b[32m--> \u001b[39m\u001b[32m495\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_call_with_config\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_invoke\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\base.py:1785\u001b[39m, in \u001b[36mRunnable._call_with_config\u001b[39m\u001b[34m(self, func, input, config, run_type, **kwargs)\u001b[39m\n\u001b[32m 1781\u001b[39m context = copy_context()\n\u001b[32m 1782\u001b[39m context.run(_set_config_context, child_config)\n\u001b[32m 1783\u001b[39m output = cast(\n\u001b[32m 1784\u001b[39m Output,\n\u001b[32m-> \u001b[39m\u001b[32m1785\u001b[39m \u001b[30;43mcontext\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrun\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 1786\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mcall_func_with_variable_args\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[32m 1787\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mfunc\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[32m 1788\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[32m 1789\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1790\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrun_manager\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1791\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1792\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m,\n\u001b[32m 1793\u001b[39m )\n\u001b[32m 1794\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 1795\u001b[39m run_manager.on_chain_error(e)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\config.py:398\u001b[39m, in \u001b[36mcall_func_with_variable_args\u001b[39m\u001b[34m(func, input, config, run_manager, **kwargs)\u001b[39m\n\u001b[32m 396\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m accepts_run_manager(func):\n\u001b[32m 397\u001b[39m kwargs[\u001b[33m\"\u001b[39m\u001b[33mrun_manager\u001b[39m\u001b[33m\"\u001b[39m] = run_manager\n\u001b[32m--> \u001b[39m\u001b[32m398\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mfunc\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\passthrough.py:482\u001b[39m, in \u001b[36mRunnableAssign._invoke\u001b[39m\u001b[34m(self, input, run_manager, config, **kwargs)\u001b[39m\n\u001b[32m 469\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_invoke\u001b[39m(\n\u001b[32m 470\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 471\u001b[39m \u001b[38;5;28minput\u001b[39m: Dict[\u001b[38;5;28mstr\u001b[39m, Any],\n\u001b[32m (...)\u001b[39m\u001b[32m 474\u001b[39m **kwargs: Any,\n\u001b[32m 475\u001b[39m ) -> Dict[\u001b[38;5;28mstr\u001b[39m, Any]:\n\u001b[32m 476\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(\n\u001b[32m 477\u001b[39m \u001b[38;5;28minput\u001b[39m, \u001b[38;5;28mdict\u001b[39m\n\u001b[32m 478\u001b[39m ), \u001b[33m\"\u001b[39m\u001b[33mThe input to RunnablePassthrough.assign() must be a dict.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 480\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m {\n\u001b[32m 481\u001b[39m **\u001b[38;5;28minput\u001b[39m,\n\u001b[32m--> \u001b[39m\u001b[32m482\u001b[39m **\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mmapper\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43minvoke\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 483\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 484\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mpatch_config\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mcallbacks\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrun_manager\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mget_child\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 485\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 486\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m,\n\u001b[32m 487\u001b[39m }\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\base.py:3579\u001b[39m, in \u001b[36mRunnableParallel.invoke\u001b[39m\u001b[34m(self, input, config)\u001b[39m\n\u001b[32m 3574\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m get_executor_for_config(config) \u001b[38;5;28;01mas\u001b[39;00m executor:\n\u001b[32m 3575\u001b[39m futures = [\n\u001b[32m 3576\u001b[39m executor.submit(_invoke_step, step, \u001b[38;5;28minput\u001b[39m, config, key)\n\u001b[32m 3577\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m key, step \u001b[38;5;129;01min\u001b[39;00m steps.items()\n\u001b[32m 3578\u001b[39m ]\n\u001b[32m-> \u001b[39m\u001b[32m3579\u001b[39m output = {key: \u001b[30;43mfuture\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mresult\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m \u001b[38;5;28;01mfor\u001b[39;00m key, future \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(steps, futures)}\n\u001b[32m 3580\u001b[39m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[32m 3581\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\Python312\\Lib\\concurrent\\futures\\_base.py:456\u001b[39m, in \u001b[36mFuture.result\u001b[39m\u001b[34m(self, timeout)\u001b[39m\n\u001b[32m 454\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m CancelledError()\n\u001b[32m 455\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._state == FINISHED:\n\u001b[32m--> \u001b[39m\u001b[32m456\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m__get_result\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 457\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 458\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTimeoutError\u001b[39;00m()\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\Python312\\Lib\\concurrent\\futures\\_base.py:401\u001b[39m, in \u001b[36mFuture.__get_result\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 399\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._exception:\n\u001b[32m 400\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m401\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m._exception\n\u001b[32m 402\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m 403\u001b[39m \u001b[38;5;66;03m# Break a reference cycle with the exception in self._exception\u001b[39;00m\n\u001b[32m 404\u001b[39m \u001b[38;5;28mself\u001b[39m = \u001b[38;5;28;01mNone\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\Python312\\Lib\\concurrent\\futures\\thread.py:59\u001b[39m, in \u001b[36m_WorkItem.run\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 56\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[32m 58\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m59\u001b[39m result = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mfn\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43margs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 60\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 61\u001b[39m \u001b[38;5;28mself\u001b[39m.future.set_exception(exc)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\base.py:3563\u001b[39m, in \u001b[36mRunnableParallel.invoke.._invoke_step\u001b[39m\u001b[34m(step, input, config, key)\u001b[39m\n\u001b[32m 3561\u001b[39m context = copy_context()\n\u001b[32m 3562\u001b[39m context.run(_set_config_context, child_config)\n\u001b[32m-> \u001b[39m\u001b[32m3563\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mcontext\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrun\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 3564\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mstep\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43minvoke\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 3565\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 3566\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mchild_config\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 3567\u001b[39m \u001b[30;43m\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\base.py:2878\u001b[39m, in \u001b[36mRunnableSequence.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 2876\u001b[39m \u001b[38;5;28minput\u001b[39m = context.run(step.invoke, \u001b[38;5;28minput\u001b[39m, config, **kwargs)\n\u001b[32m 2877\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m2878\u001b[39m \u001b[38;5;28minput\u001b[39m = \u001b[30;43mcontext\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrun\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mstep\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43minvoke\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 2879\u001b[39m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[32m 2880\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\runnables\\base.py:5092\u001b[39m, in \u001b[36mRunnableBindingBase.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 5086\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34minvoke\u001b[39m(\n\u001b[32m 5087\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 5088\u001b[39m \u001b[38;5;28minput\u001b[39m: Input,\n\u001b[32m 5089\u001b[39m config: Optional[RunnableConfig] = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 5090\u001b[39m **kwargs: Optional[Any],\n\u001b[32m 5091\u001b[39m ) -> Output:\n\u001b[32m-> \u001b[39m\u001b[32m5092\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mbound\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43minvoke\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 5093\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 5094\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_merge_configs\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 5095\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m{\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m}\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 5096\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\language_models\\chat_models.py:277\u001b[39m, in \u001b[36mBaseChatModel.invoke\u001b[39m\u001b[34m(self, input, config, stop, **kwargs)\u001b[39m\n\u001b[32m 266\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34minvoke\u001b[39m(\n\u001b[32m 267\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 268\u001b[39m \u001b[38;5;28minput\u001b[39m: LanguageModelInput,\n\u001b[32m (...)\u001b[39m\u001b[32m 272\u001b[39m **kwargs: Any,\n\u001b[32m 273\u001b[39m ) -> BaseMessage:\n\u001b[32m 274\u001b[39m config = ensure_config(config)\n\u001b[32m 275\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m cast(\n\u001b[32m 276\u001b[39m ChatGeneration,\n\u001b[32m--> \u001b[39m\u001b[32m277\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mgenerate_prompt\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 278\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m[\u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_convert_input\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43minput\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m]\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 279\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 280\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mcallbacks\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mget\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mcallbacks\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 281\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtags\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mget\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtags\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 282\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mmetadata\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mget\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mmetadata\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 283\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrun_name\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mget\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mrun_name\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 284\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrun_id\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mpop\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mrun_id\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mNone\u001b[39;49;00m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 285\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 286\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m.generations[\u001b[32m0\u001b[39m][\u001b[32m0\u001b[39m],\n\u001b[32m 287\u001b[39m ).message\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\language_models\\chat_models.py:777\u001b[39m, in \u001b[36mBaseChatModel.generate_prompt\u001b[39m\u001b[34m(self, prompts, stop, callbacks, **kwargs)\u001b[39m\n\u001b[32m 769\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mgenerate_prompt\u001b[39m(\n\u001b[32m 770\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 771\u001b[39m prompts: List[PromptValue],\n\u001b[32m (...)\u001b[39m\u001b[32m 774\u001b[39m **kwargs: Any,\n\u001b[32m 775\u001b[39m ) -> LLMResult:\n\u001b[32m 776\u001b[39m prompt_messages = [p.to_messages() \u001b[38;5;28;01mfor\u001b[39;00m p \u001b[38;5;129;01min\u001b[39;00m prompts]\n\u001b[32m--> \u001b[39m\u001b[32m777\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mgenerate\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mprompt_messages\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mcallbacks\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mcallbacks\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\language_models\\chat_models.py:634\u001b[39m, in \u001b[36mBaseChatModel.generate\u001b[39m\u001b[34m(self, messages, stop, callbacks, tags, metadata, run_name, run_id, **kwargs)\u001b[39m\n\u001b[32m 632\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m run_managers:\n\u001b[32m 633\u001b[39m run_managers[i].on_llm_error(e, response=LLMResult(generations=[]))\n\u001b[32m--> \u001b[39m\u001b[32m634\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[32m 635\u001b[39m flattened_outputs = [\n\u001b[32m 636\u001b[39m LLMResult(generations=[res.generations], llm_output=res.llm_output) \u001b[38;5;66;03m# type: ignore[list-item]\u001b[39;00m\n\u001b[32m 637\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m res \u001b[38;5;129;01min\u001b[39;00m results\n\u001b[32m 638\u001b[39m ]\n\u001b[32m 639\u001b[39m llm_output = \u001b[38;5;28mself\u001b[39m._combine_llm_outputs([res.llm_output \u001b[38;5;28;01mfor\u001b[39;00m res \u001b[38;5;129;01min\u001b[39;00m results])\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\language_models\\chat_models.py:624\u001b[39m, in \u001b[36mBaseChatModel.generate\u001b[39m\u001b[34m(self, messages, stop, callbacks, tags, metadata, run_name, run_id, **kwargs)\u001b[39m\n\u001b[32m 621\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m i, m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(messages):\n\u001b[32m 622\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 623\u001b[39m results.append(\n\u001b[32m--> \u001b[39m\u001b[32m624\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_generate_with_cache\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 625\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mm\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 626\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 627\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrun_manager\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrun_managers\u001b[39;49m\u001b[30;43m[\u001b[39;49m\u001b[30;43mi\u001b[39;49m\u001b[30;43m]\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mif\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mrun_managers\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01melse\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mNone\u001b[39;49;00m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 628\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 629\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 630\u001b[39m )\n\u001b[32m 631\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 632\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m run_managers:\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_core\\language_models\\chat_models.py:846\u001b[39m, in \u001b[36mBaseChatModel._generate_with_cache\u001b[39m\u001b[34m(self, messages, stop, run_manager, **kwargs)\u001b[39m\n\u001b[32m 844\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 845\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m inspect.signature(\u001b[38;5;28mself\u001b[39m._generate).parameters.get(\u001b[33m\"\u001b[39m\u001b[33mrun_manager\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m--> \u001b[39m\u001b[32m846\u001b[39m result = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_generate\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 847\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mmessages\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mrun_manager\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrun_manager\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\n\u001b[32m 848\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 849\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 850\u001b[39m result = \u001b[38;5;28mself\u001b[39m._generate(messages, stop=stop, **kwargs)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\langchain_groq\\chat_models.py:472\u001b[39m, in \u001b[36mChatGroq._generate\u001b[39m\u001b[34m(self, messages, stop, run_manager, **kwargs)\u001b[39m\n\u001b[32m 467\u001b[39m message_dicts, params = \u001b[38;5;28mself\u001b[39m._create_message_dicts(messages, stop)\n\u001b[32m 468\u001b[39m params = {\n\u001b[32m 469\u001b[39m **params,\n\u001b[32m 470\u001b[39m **kwargs,\n\u001b[32m 471\u001b[39m }\n\u001b[32m--> \u001b[39m\u001b[32m472\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mclient\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mcreate\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mmessages\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mmessage_dicts\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mparams\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 473\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._create_chat_result(response)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\groq\\resources\\chat\\completions.py:461\u001b[39m, in \u001b[36mCompletions.create\u001b[39m\u001b[34m(self, messages, model, citation_options, compound_custom, disable_tool_validation, documents, exclude_domains, frequency_penalty, function_call, functions, include_domains, include_reasoning, logit_bias, logprobs, max_completion_tokens, max_tokens, metadata, n, parallel_tool_calls, presence_penalty, reasoning_effort, reasoning_format, response_format, search_settings, seed, service_tier, stop, store, stream, temperature, tool_choice, tools, top_logprobs, top_p, user, extra_headers, extra_query, extra_body, timeout)\u001b[39m\n\u001b[32m 241\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mcreate\u001b[39m(\n\u001b[32m 242\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 243\u001b[39m *,\n\u001b[32m (...)\u001b[39m\u001b[32m 300\u001b[39m timeout: \u001b[38;5;28mfloat\u001b[39m | httpx.Timeout | \u001b[38;5;28;01mNone\u001b[39;00m | NotGiven = not_given,\n\u001b[32m 301\u001b[39m ) -> ChatCompletion | Stream[ChatCompletionChunk]:\n\u001b[32m 302\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 303\u001b[39m \u001b[33;03m Creates a model response for the given chat conversation.\u001b[39;00m\n\u001b[32m 304\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 459\u001b[39m \u001b[33;03m timeout: Override the client-level default timeout for this request, in seconds\u001b[39;00m\n\u001b[32m 460\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m461\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_post\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 462\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m/openai/v1/chat/completions\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 463\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mbody\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mmaybe_transform\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 464\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m{\u001b[39;49m\n\u001b[32m 465\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mmessages\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mmessages\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 466\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mmodel\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mmodel\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 467\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mcitation_options\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mcitation_options\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 468\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mcompound_custom\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mcompound_custom\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 469\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mdisable_tool_validation\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mdisable_tool_validation\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 470\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mdocuments\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mdocuments\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 471\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mexclude_domains\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mexclude_domains\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 472\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mfrequency_penalty\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mfrequency_penalty\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 473\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mfunction_call\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mfunction_call\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 474\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mfunctions\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mfunctions\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 475\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43minclude_domains\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43minclude_domains\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 476\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43minclude_reasoning\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43minclude_reasoning\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 477\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mlogit_bias\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mlogit_bias\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 478\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mlogprobs\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mlogprobs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 479\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mmax_completion_tokens\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mmax_completion_tokens\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 480\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mmax_tokens\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mmax_tokens\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 481\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mmetadata\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mmetadata\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 482\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mn\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mn\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 483\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mparallel_tool_calls\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mparallel_tool_calls\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 484\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mpresence_penalty\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mpresence_penalty\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 485\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mreasoning_effort\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mreasoning_effort\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 486\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mreasoning_format\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mreasoning_format\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 487\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mresponse_format\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mresponse_format\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 488\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43msearch_settings\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43msearch_settings\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 489\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mseed\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mseed\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 490\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mservice_tier\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mservice_tier\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 491\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstop\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 492\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mstore\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstore\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 493\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 494\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtemperature\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtemperature\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 495\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtool_choice\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtool_choice\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 496\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtools\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtools\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 497\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtop_logprobs\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtop_logprobs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 498\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtop_p\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtop_p\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 499\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43muser\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m:\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43muser\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 500\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m}\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 501\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mcompletion_create_params\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mCompletionCreateParams\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 502\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 503\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43moptions\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mmake_request_options\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 504\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mextra_headers\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mextra_headers\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mextra_query\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mextra_query\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mextra_body\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mextra_body\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtimeout\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtimeout\u001b[39;49m\n\u001b[32m 505\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 506\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mcast_to\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mChatCompletion\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 507\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mor\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mFalse\u001b[39;49;00m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 508\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mstream_cls\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mStream\u001b[39;49m\u001b[30;43m[\u001b[39;49m\u001b[30;43mChatCompletionChunk\u001b[39;49m\u001b[30;43m]\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 509\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\groq\\_base_client.py:1242\u001b[39m, in \u001b[36mSyncAPIClient.post\u001b[39m\u001b[34m(self, path, cast_to, body, options, files, stream, stream_cls)\u001b[39m\n\u001b[32m 1228\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mpost\u001b[39m(\n\u001b[32m 1229\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 1230\u001b[39m path: \u001b[38;5;28mstr\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 1237\u001b[39m stream_cls: \u001b[38;5;28mtype\u001b[39m[_StreamT] | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 1238\u001b[39m ) -> ResponseT | _StreamT:\n\u001b[32m 1239\u001b[39m opts = FinalRequestOptions.construct(\n\u001b[32m 1240\u001b[39m method=\u001b[33m\"\u001b[39m\u001b[33mpost\u001b[39m\u001b[33m\"\u001b[39m, url=path, json_data=body, files=to_httpx_files(files), **options\n\u001b[32m 1241\u001b[39m )\n\u001b[32m-> \u001b[39m\u001b[32m1242\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m cast(ResponseT, \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrequest\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mcast_to\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mopts\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstream\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mstream_cls\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mstream_cls\u001b[39;49m\u001b[30;43m)\u001b[39;49m)\n", + "\u001b[36mFile \u001b[39m\u001b[32mf:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\site-packages\\groq\\_base_client.py:998\u001b[39m, in \u001b[36mSyncAPIClient.request\u001b[39m\u001b[34m(self, cast_to, options, stream, stream_cls)\u001b[39m\n\u001b[32m 995\u001b[39m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[32m 997\u001b[39m log.debug(\u001b[33m\"\u001b[39m\u001b[33mRaising timeout error\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m998\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m APITimeoutError(request=request) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01merr\u001b[39;00m\n\u001b[32m 999\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m 1000\u001b[39m log.debug(\u001b[33m\"\u001b[39m\u001b[33mEncountered Exception\u001b[39m\u001b[33m\"\u001b[39m, exc_info=\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "\u001b[31mAPITimeoutError\u001b[39m: Request timed out." + ] + } + ], + "source": [ + "# ── Test ─────────────────────────────────────────────────────────────────────\n", + "query = full_chain.invoke({\"question\": \"What is the most popular genre by number of tracks?\"})\n", + "print(\"Clean SQL:\\n\", query)\n", + "\n", + "result = db.run(query)\n", + "print(\"Result:\\n\", result)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(18,)]'" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Prepare the tool (Don't run the following cell)**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class ChinookSQLAgent:\n", + " \"\"\"\n", + " A specialized SQL agent that interacts with the Chinook SQL database using an LLM (Large Language Model).\n", + "\n", + " The agent handles SQL queries by mapping user questions to relevant SQL tables based on categories like \"Music\"\n", + " and \"Business\". It uses an extraction chain to determine relevant tables based on the question and then\n", + " executes queries against the database using the appropriate tables.\n", + "\n", + " Attributes:\n", + " sql_agent_llm (ChatOpenAI): The language model used for interpreting and interacting with the database.\n", + " db (SQLDatabase): The SQL database object, representing the Chinook database.\n", + " full_chain (Runnable): A chain of operations that maps user questions to SQL tables and executes queries.\n", + "\n", + " Methods:\n", + " __init__: Initializes the agent by setting up the LLM, connecting to the SQL database, and creating query chains.\n", + "\n", + " Args:\n", + " sqldb_directory (str): The directory where the Chinook SQLite database file is located.\n", + " llm (str): The name of the LLM model to use (e.g., \"gpt-3.5-turbo\").\n", + " llm_temperature (float): The temperature setting for the LLM, controlling the randomness of responses.\n", + " \"\"\"\n", + "\n", + " def __init__(self, sqldb_directory: str, llm: str, llm_temerature: float) -> None:\n", + " \"\"\"Initializes the ChinookSQLAgent with the LLM and database connection.\n", + "\n", + " Args:\n", + " sqldb_directory (str): The directory path to the SQLite database file.\n", + " llm (str): The LLM model identifier (e.g., \"gpt-3.5-turbo\").\n", + " llm_temerature (float): The temperature value for the LLM, determining the randomness of the model's output.\n", + " \"\"\"\n", + " self.sql_agent_llm = ChatGroq(\n", + " model=llm, temperature=llm_temerature)\n", + "\n", + " self.db = SQLDatabase.from_uri(f\"sqlite:///{sqldb_directory}\")\n", + " print(self.db.get_usable_table_names())\n", + " category_chain_system = \"\"\"Return the names of the SQL tables that are relevant to the user question. \\\n", + " The tables are:\n", + "\n", + " Music\n", + " Business\"\"\"\n", + " category_chain = create_extraction_chain_pydantic(\n", + " Table, self.sql_agent_llm, system_message=category_chain_system)\n", + " table_chain = category_chain | get_tables # noqa\n", + " query_chain = create_sql_query_chain(self.sql_agent_llm, self.db)\n", + " # Convert \"question\" key to the \"input\" key expected by current table_chain.\n", + " table_chain = {\"input\": itemgetter(\"question\")} | table_chain\n", + " # Set table_names_to_use using table_chain.\n", + " self.full_chain = RunnablePassthrough.assign(\n", + " table_names_to_use=table_chain) | query_chain\n", + "\n", + "\n", + "@tool\n", + "def query_chinook_sqldb(query: str) -> str:\n", + " \"\"\"Query the Chinook SQL Database. Input should be a search query.\"\"\"\n", + " # Create an instance of ChinookSQLAgent\n", + " agent = ChinookSQLAgent(\n", + " sqldb_directory=TOOLS_CFG.chinook_sqldb_directory,\n", + " llm=TOOLS_CFG.chinook_sqlagent_llm,\n", + " llm_temerature=TOOLS_CFG.chinook_sqlagent_llm_temperature\n", + " )\n", + "\n", + " query = agent.full_chain.invoke({\"question\": query})\n", + "\n", + " return agent.db.run(query)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "querymind (3.12.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", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/Tools/sql_agents/sql_agent_chain_steps.ipynb b/Notebooks/Tools/sql_agents/sql_agent_chain_steps.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..c004472bc4ceaaf20693fd37fa358840cd19643f --- /dev/null +++ b/Notebooks/Tools/sql_agents/sql_agent_chain_steps.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from pyprojroot import here\n", + "from langchain_community.utilities import SQLDatabase\n", + "from langchain.chains import create_sql_query_chain\n", + "from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool\n", + "from langchain_core.prompts import PromptTemplate\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from operator import itemgetter\n", + "from langchain_groq import ChatGroq\n", + "import os\n", + "from dotenv import load_dotenv\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Set the environment variables and load the LLM**" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['GROQ_API_KEY'] = os.getenv(\"GROQ_API_KEY\")\n", + "\n", + "llm = ChatGroq(model=\"openai/gpt-oss-120b\")\n", + "# llm = ChatGroq(model=\"llama3-8b-8192\")\n", + "# llm = ChatGroq(model=\"mixtral-8x7b-32768\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Load and test the sqlite db**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[('Album',), ('Artist',), ('Customer',), ('Employee',), ('Genre',), ('Invoice',), ('InvoiceLine',), ('MediaType',), ('Playlist',), ('PlaylistTrack',), ('Track',)]\"" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sqldb_directory = here(\"data/Chinook.db\")\n", + "db = SQLDatabase.from_uri(\n", + " f\"sqlite:///{sqldb_directory}\")\n", + "\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"\"\" SELECT name\n", + "FROM sqlite_master\n", + "WHERE type='table'\n", + "AND name NOT LIKE 'sqlite_%'; \"\"\")\n", + "\n", + "# from sqlalchemy import create_engine, inspect\n", + "# from sqlalchemy.orm import sessionmaker\n", + "# engine = create_engine(db_path)\n", + "\n", + "# # Create a session\n", + "# Session = sessionmaker(bind=engine)\n", + "# session = Session()\n", + "\n", + "# # Use SQLAlchemy's Inspector to get database information\n", + "# inspector = inspect(engine)\n", + "\n", + "# # Get table names\n", + "# tables = inspector.get_table_names()\n", + "# print(\"Tables in the database:\", tables)\n", + "# print(len(tables))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Create the SQL agent chain and run a test query**" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "system_role = \"\"\"Given the following user question, corresponding SQL query, and SQL result, answer the user question.\\n\n", + " Question: {question}\\n\n", + " SQL Query: {query}\\n\n", + " SQL Result: {result}\\n\n", + " Answer:\n", + " \"\"\"\n", + "\n", + "execute_query = QuerySQLDataBaseTool(db=db)\n", + "write_query = create_sql_query_chain(\n", + " llm, db)\n", + "answer_prompt = PromptTemplate.from_template(\n", + " system_role)\n", + "answer = answer_prompt | llm | StrOutputParser()\n", + "chain = (\n", + " RunnablePassthrough.assign(query=write_query).assign(\n", + " result=itemgetter(\"query\") | execute_query\n", + " )\n", + " | answer\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'I’m sorry, but there’s no SQL result provided, so I can’t determine how many tables are in your database or what their names are. If you can share the query output (e.g., a list of table names), I’ll be happy to give you the answer.'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "message = \"How many tables do I have in the database? and what are their names?\"\n", + "response = chain.invoke({\"question\": message})\n", + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Travel SQL-agent Tool Design**" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "from langchain_community.utilities import SQLDatabase\n", + "from langchain.chains import create_sql_query_chain\n", + "from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool\n", + "from langchain_core.prompts import PromptTemplate\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from operator import itemgetter\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "\n", + "class TravelSQLAgentTool:\n", + " \"\"\"\n", + " A tool for interacting with a travel-related SQL database using an LLM (Language Model) to generate and execute SQL queries.\n", + "\n", + " This tool enables users to ask travel-related questions, which are transformed into SQL queries by a language model.\n", + " The SQL queries are executed on the provided SQLite database, and the results are processed by the language model to\n", + " generate a final answer for the user.\n", + "\n", + " Attributes:\n", + " sql_agent_llm (ChatOpenAI): An instance of a ChatOpenAI language model used to generate and process SQL queries.\n", + " system_role (str): A system prompt template that guides the language model in answering user questions based on SQL query results.\n", + " db (SQLDatabase): An instance of the SQL database used to execute queries.\n", + " chain (RunnablePassthrough): A chain of operations that creates SQL queries, executes them, and generates a response.\n", + "\n", + " Methods:\n", + " __init__: Initializes the TravelSQLAgentTool by setting up the language model, SQL database, and query-answering pipeline.\n", + " \"\"\"\n", + "\n", + " def __init__(self, llm: str, sqldb_directory: str, llm_temerature: float) -> None:\n", + " \"\"\"\n", + " Initializes the TravelSQLAgentTool with the necessary configurations.\n", + "\n", + " Args:\n", + " llm (str): The name of the language model to be used for generating and interpreting SQL queries.\n", + " sqldb_directory (str): The directory path where the SQLite database is stored.\n", + " llm_temerature (float): The temperature setting for the language model, controlling response randomness.\n", + " \"\"\"\n", + " self.sql_agent_llm = ChatGroq(\n", + " model=llm, temperature=llm_temerature)\n", + " self.system_role = \"\"\"Given the following user question, corresponding SQL query, and SQL result, answer the user question.\\n\n", + " Question: {question}\\n\n", + " SQL Query: {query}\\n\n", + " SQL Result: {result}\\n\n", + " Answer:\n", + " \"\"\"\n", + " self.db = SQLDatabase.from_uri(\n", + " f\"sqlite:///{sqldb_directory}\")\n", + " print(self.db.get_usable_table_names())\n", + "\n", + " execute_query = QuerySQLDataBaseTool(db=self.db)\n", + " write_query = create_sql_query_chain(\n", + " self.sql_agent_llm, self.db)\n", + " answer_prompt = PromptTemplate.from_template(\n", + " self.system_role)\n", + "\n", + " answer = answer_prompt | self.sql_agent_llm | StrOutputParser()\n", + " self.chain = (\n", + " RunnablePassthrough.assign(query=write_query).assign(\n", + " result=itemgetter(\"query\") | execute_query\n", + " )\n", + " | answer\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "<>:3: SyntaxWarning: invalid escape sequence '\\e'\n", + "<>:3: SyntaxWarning: invalid escape sequence '\\e'\n", + "C:\\Users\\AL-MASA\\AppData\\Local\\Temp\\ipykernel_13496\\1650904972.py:3: SyntaxWarning: invalid escape sequence '\\e'\n", + " sys.path.insert(0, os.path.abspath('F:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases')) # or the full path to your project root\n" + ] + } + ], + "source": [ + "import sys\n", + "import os\n", + "sys.path.insert(0, os.path.abspath('F:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases')) # or the full path to your project root\n", + "\n", + "from src.agent_graph.load_tools_config import LoadToolsConfig" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from src.agent_graph.load_tools_config import LoadToolsConfig\n", + "\n", + "TOOLS_CFG = LoadToolsConfig()\n", + "\n", + "@tool\n", + "def query_travel_sqldb(query: str) -> str:\n", + " \"\"\"Query the Swiss Airline SQL Database and access all the company's information. Input should be a search query.\"\"\"\n", + " agent = TravelSQLAgentTool(\n", + " llm=TOOLS_CFG.travel_sqlagent_llm,\n", + " sqldb_directory=TOOLS_CFG.travel_sqldb_directory,\n", + " llm_temperature=TOOLS_CFG.travel_sqlagent_llm_temperature\n", + " )\n", + " response = agent.chain.invoke({\"question\": query})\n", + " return response" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "querymind (3.12.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", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/Tools/sql_agents/sql_agent_steps.ipynb b/Notebooks/Tools/sql_agents/sql_agent_steps.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..711238f2459367b07cdc547e1b3545d0c773aea7 --- /dev/null +++ b/Notebooks/Tools/sql_agents/sql_agent_steps.ipynb @@ -0,0 +1,163 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from dotenv import load_dotenv\n", + "from pyprojroot import here\n", + "from langchain.chains import create_sql_query_chain\n", + "from langchain_community.agent_toolkits import create_sql_agent\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit\n", + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Set the environment variable and load the LLM**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['OPENAI_API_KEY'] = os.getenv(\"OPEN_AI_API_KEY\")\n", + "\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "# llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Load and test the sqlite db**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['aircrafts_data', 'airports_data', 'boarding_passes', 'bookings', 'car_rentals', 'flights', 'hotels', 'seats', 'ticket_flights', 'tickets', 'trip_recommendations']\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[('773', 'Boeing 777-300', 11100), ('763', 'Boeing 767-300', 7900), ('SU9', 'Sukhoi Superjet-100', 3000), ('320', 'Airbus A320-200', 5700), ('321', 'Airbus A321-200', 5600), ('319', 'Airbus A319-100', 6700), ('733', 'Boeing 737-300', 4200), ('CN1', 'Cessna 208 Caravan', 1200), ('CR2', 'Bombardier CRJ-200', 2700)]\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sqldb_directory = here(\"data/travel.sqlite\")\n", + "db = SQLDatabase.from_uri(f\"sqlite:///{sqldb_directory}\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "db.run(\"SELECT * FROM aircrafts_data LIMIT 10;\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Create the SQL agent and run a test query**" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'SELECT COUNT(*) AS total_rows FROM aircrafts_data;'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = create_sql_query_chain(llm, db)\n", + "response = chain.invoke({\"question\": \"How many rows are there in the aircrafts_data table?\"})\n", + "response" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(9,)]'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(response)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "rag-sqlagent", + "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", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/Tools/tavily/tavily_search.ipynb b/Notebooks/Tools/tavily/tavily_search.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..50d91d806e60b5b1d7afca58d7b17605b52e190c --- /dev/null +++ b/Notebooks/Tools/tavily/tavily_search.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from dotenv import load_dotenv\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", + "load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['TAVILY_API_KEY'] = os.getenv(\"TAVILY_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "search_tool = TavilySearchResults(max_results=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'A search engine optimized for comprehensive, accurate, and trusted results. Useful for when you need to answer questions about current events. Input should be a search query.'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search_tool.description" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'url': 'https://www.ibm.com/think/topics/langgraph',\n", + " 'content': 'Nodes: In LangGraph, nodes represent individual components or agents within an AI workflow. Nodes can be thought of as “actors” that interact with each other in a specific way. For example,to add nodes for tool calling, one can use the ToolNode. Another example, the next node, refers to the node that will be executed following the current one.\\n\\nEdges: Edges are a function within Python that determines which node to execute next based on the current state. Edges can be conditional branches or fixed transitions.\\n\\n#### Tools\\n\\nRAG: Retrieval-augmented generation (RAG) combines the power of LLMs with contextual information from external sources by retrieving relevant documents, which are then used as input for answer generation. [...] Workflows: Workflows are the sequences of node interactions that define an AI workflow. By arranging nodes into a workflow, users can create more complex and dynamic workflows that use the strengths of individual components.\\n\\nAPIs: LangGraph provides a set of APIs that enable users to interact with its components in a programmatic way. Users can use an API key, add new nodes, modify existing workflows and retrieve data from an AI workflow.\\n\\nLangSmith: LangSmith is a specialized API for building and managing LLMs within LangGraph. It provides tools for initializing LLMs, adding conditional edges and optimizing performance. By combining these components in innovative ways, users can build more sophisticated AI workflows that use the strengths of individual components. [...] #### Graph architecture\\n\\nStateful graphs: A concept where each node in the graph represents a step in the computation, essentially devising a state graph. This stateful approach allows the graph to retain information about the previous steps, enabling continuous and contextual processing of information as the computation unfolds. Users can manage all LangGraph’s stateful graphs with its APIs.\\n\\nCyclical graph: A cyclical graph is any graph that contains at least one cycle and is essential for agent runtimes. This means that there exists a path that starts and ends at the same node, forming a loop within the graph. Complex workflows often involve cyclic dependencies, where the outcome of one step depends on previous steps in the loop.'},\n", + " {'url': 'https://dev.to/raunaklallala/understanding-core-concepts-of-langgraph-deep-dive-1d7h',\n", + " 'content': '### 1. Nodes: The Execution Units\\n\\nA Node is basically “a single action.” Imagine breaking your workday into steps: checking email, making coffee, writing code, or scheduling a meeting. Each of those is a Node.\\n\\nIn LangGraph, a Node can be many things:\\n\\nEach Node is like a worker with a simple contract: it takes an input, does its piece of the job, and pushes out an output.\\n\\nEveryday example: \\n \\nThink about ordering food on a delivery app.\\n\\nAnalogy: Nodes are like “stations” on a metro map. The passenger (your data) steps off at every station, something happens to them, and then they move along.\\n\\n### 2.Edges: The Flow of Control\\n\\nNodes mean nothing without connections. That’s where Edges come in—they define how data flows between steps. [...] DEV Community\\n\\n## DEV Community\\n\\nCover image for Understanding Core Concepts of LangGraph (Deep Dive)\\nRaunak ALI\\n\\nPosted on Sep 16, 2025\\n\\n# Understanding Core Concepts of LangGraph (Deep Dive)\\n\\n## Single Agent Workflow — From LLMs to LangGraph (2 Part Series)\\n\\n# Understanding Core Concepts of LangGraph (Deep Dive)\\n\\nIn the last chapter, we talked about why LangGraph feels like a shift compared to traditional “linear chains.” Now, let’s slow down and zoom into its DNA. At the core, LangGraph has three simple but powerful building blocks: Nodes, Edges, and State.\\n\\nIf those names sound abstract, don’t worry, by the end of this chapter, you’ll see them the same way you see apps on your phone or stops on a subway map. They’re pieces you already know, just arranged in a smarter way.'}]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search_tool.invoke(\"What's a 'node' in LangGraph?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "querymind (3.12.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", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/custom_agent/groq_function_calling.ipynb b/Notebooks/custom_agent/groq_function_calling.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..acbc0fff8dcb19f261f93c447da1b8d48bdf7dd0 --- /dev/null +++ b/Notebooks/custom_agent/groq_function_calling.ipynb @@ -0,0 +1,401 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "import os\n", + "from dotenv import load_dotenv\n", + "from groq import Groq\n", + "from pydantic import create_model\n", + "import inspect, json\n", + "from inspect import Parameter\n", + "\n", + "print(load_dotenv())\n", + "\n", + "os.environ['GROQ_API_KEY'] = os.getenv(\"GROQ_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **Custom agent**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Define the functions**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def abc(num1:int, num2:int)->int:\n", + " \"Compute abc between two numbers\"\n", + " return 2*(num1) - 2*(num2)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-2" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abc(2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def jsonschema(f):\n", + " \"\"\"\n", + " Generate a JSON schema for the input parameters of the given function.\n", + "\n", + " Parameters:\n", + " f (FunctionType): The function for which to generate the JSON schema.\n", + "\n", + " Returns:\n", + " Dict: A dictionary containing the function name, description, and parameters schema.\n", + " \"\"\"\n", + " kw = {n: (o.annotation, ... if o.default == Parameter.empty else o.default)\n", + " for n, o in inspect.signature(f).parameters.items()}\n", + " s = create_model(f'Input for `{f.__name__}`', **kw).schema()\n", + " return dict(name=f.__name__, description=f.__doc__, parameters=s)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'abc',\n", + " 'description': 'Compute abc between two numbers',\n", + " 'parameters': {'properties': {'num1': {'title': 'Num1', 'type': 'integer'},\n", + " 'num2': {'title': 'Num2', 'type': 'integer'}},\n", + " 'required': ['num1', 'num2'],\n", + " 'title': 'Input for `abc`',\n", + " 'type': 'object'}}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abc_json = jsonschema(abc)\n", + "abc_json" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model_name = \"llama-3.3-70b-versatile\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Ask Groq**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "client = Groq()\n", + "\n", + "response = client.chat.completions.create(\n", + " model= model_name,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", + " {\"role\": \"user\", \"content\": \"compute abc between 2 and 3\"},\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'To compute the absolute difference, also known as the absolute value of the difference, between 2 and 3:\\n\\n|2 - 3| = |-1| = 1\\n\\nSo the absolute difference between 2 and 3 is 1.'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response.choices[0].message.content" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "messages= [\n", + " {\"role\": \"user\", \"content\": \"Compute abc between 2 and 3\"}\n", + "]\n", + "\n", + "# Pass th function to groq model\n", + "response = client.chat.completions.create(\n", + " model=model_name,\n", + " messages=messages,\n", + " functions=[abc_json],\n", + " function_call=\"auto\",\n", + " temperature=0\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ChatCompletion(id='chatcmpl-e37ef9c8-5015-4811-91c8-6373640bb737', choices=[Choice(finish_reason='function_call', index=0, logprobs=None, message=ChatCompletionMessage(content=None, role='assistant', annotations=None, executed_tools=None, function_call=FunctionCall(arguments='{\"num1\":2,\"num2\":3}', name='abc'), reasoning=None, tool_calls=None))], created=1778583530, model='llama-3.3-70b-versatile', object='chat.completion', mcp_list_tools=None, service_tier='on_demand', system_fingerprint='fp_ce7bc1685b', usage=CompletionUsage(completion_tokens=21, prompt_tokens=237, total_tokens=258, completion_time=0.047009754, completion_tokens_details=None, prompt_time=0.013427763, prompt_tokens_details=None, queue_time=0.048241546, total_time=0.060437517), usage_breakdown=None, x_groq=XGroq(id='req_01krdxdtkmeghva26epfsabw0m', debug=None, seed=1357225914, usage=None))" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Executing the function by extracting the info from the output of the model**" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FunctionCall(arguments='{\"num1\":2,\"num2\":3}', name='abc')\n", + "{\"num1\":2,\"num2\":3}\n", + "\n" + ] + } + ], + "source": [ + "print(response.choices[0].message.function_call)\n", + "print(response.choices[0].message.function_call.arguments)\n", + "print(type(response.choices[0].message.function_call.arguments))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Function name: abc\n", + "Function arguments: {'num1': 2, 'num2': 3}\n", + "\n" + ] + } + ], + "source": [ + "func_name = response.choices[0].message.function_call.name\n", + "func_args = json.loads(response.choices[0].message.function_call.arguments)\n", + "print(\"Function name:\", func_name)\n", + "print(\"Function arguments:\", func_args)\n", + "print(type(func_args))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-2\n" + ] + } + ], + "source": [ + "if func_name == 'abc':\n", + " result = abc(**func_args)\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **Using Langchain**" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "@tool\n", + "def abc(num1:int, num2:int)->int:\n", + " \"Compute abc between two numbers\"\n", + " return 2*(num1) - 2*(num2)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Compute abc between two numbers'" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abc.description" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_groq import ChatGroq \n", + "llm = ChatGroq(model=\"llama-3.3-70b-versatile\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "tools = [abc]\n", + "\n", + "llm_with_tools = llm.bind_tools(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "response = llm_with_tools.invoke(\"Compute abc between 2 and 3\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tool_calls': [{'id': 'rjae3mfkg',\n", + " 'function': {'arguments': '{\"num1\":2,\"num2\":3}', 'name': 'abc'},\n", + " 'type': 'function'}]}" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response.additional_kwargs" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "querymind (3.12.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", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/explore_databases/explore_chinook.ipynb b/Notebooks/explore_databases/explore_chinook.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..fc0801748dfa0ea20834953ff1559482919d302b --- /dev/null +++ b/Notebooks/explore_databases/explore_chinook.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sqlite3\n", + "import pandas as pd\n", + "from pyprojroot import here" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Connect to SQLite database\n", + "db_path = here('data/Chinook.db')\n", + "conn = sqlite3.connect(db_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
name
0Album
1Artist
2Customer
3Employee
4Genre
5Invoice
6InvoiceLine
7MediaType
8Playlist
9PlaylistTrack
10Track
\n", + "
" + ], + "text/plain": [ + " name\n", + "0 Album\n", + "1 Artist\n", + "2 Customer\n", + "3 Employee\n", + "4 Genre\n", + "5 Invoice\n", + "6 InvoiceLine\n", + "7 MediaType\n", + "8 Playlist\n", + "9 PlaylistTrack\n", + "10 Track" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get list of all tables\n", + "query = \"SELECT name FROM sqlite_master WHERE type='table';\"\n", + "tables = pd.read_sql(query, conn)\n", + "tables" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Title
0Jagged Little Pill
\n", + "
" + ], + "text/plain": [ + " Title\n", + "0 Jagged Little Pill" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query = \"\"\"SELECT Album.Title\n", + "FROM Album\n", + "JOIN Artist ON Album.ArtistId = Artist.ArtistId\n", + "WHERE Artist.Name = 'Alanis Morissette';\"\"\"\n", + "tables = pd.read_sql(query, conn)\n", + "tables" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "rag-sqlagent", + "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", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/explore_databases/explore_traveldb.ipynb b/Notebooks/explore_databases/explore_traveldb.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1d357f5f78f4e43c9f46d943030e3905e1a8b19b --- /dev/null +++ b/Notebooks/explore_databases/explore_traveldb.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import sqlite3\n", + "import pandas as pd\n", + "from pyprojroot import here" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Connect to SQLite database\n", + "db_path = here('data/travel.sqlite')\n", + "conn = sqlite3.connect(db_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
name
0aircrafts_data
1airports_data
2boarding_passes
3bookings
4flights
5seats
6ticket_flights
7tickets
8car_rentals
9hotels
10trip_recommendations
\n", + "
" + ], + "text/plain": [ + " name\n", + "0 aircrafts_data\n", + "1 airports_data\n", + "2 boarding_passes\n", + "3 bookings\n", + "4 flights\n", + "5 seats\n", + "6 ticket_flights\n", + "7 tickets\n", + "8 car_rentals\n", + "9 hotels\n", + "10 trip_recommendations" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get list of all tables\n", + "query = \"SELECT name FROM sqlite_master WHERE type='table';\"\n", + "tables = pd.read_sql(query, conn)\n", + "tables" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "rag-sqlagent", + "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", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/full_graph.ipynb b/Notebooks/full_graph.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..719dc1655c5dada76fb543322e3ef95ebca2a698 --- /dev/null +++ b/Notebooks/full_graph.ipynb @@ -0,0 +1,828 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**In this Notebook we will design the full graph using 3 tools: search_tool, RAG tool, and SQL-agent for travel database**" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from dotenv import load_dotenv\n", + "from pyprojroot import here\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set the environment variables" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['GROQ_API_KEY'] = os.getenv(\"GROQ_API_KEY\")\n", + "os.environ['TAVILY_API_KEY'] = os.getenv(\"TAVILY_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **1. initialize the Tools**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1.1 RAG tool design**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name='lookup_policy' description='Consult the company policies to check whether certain options are permitted.' args_schema= func=\n" + ] + } + ], + "source": [ + "from langchain_chroma import Chroma\n", + "from langchain_huggingface import HuggingFaceEmbeddings\n", + "from langchain_core.tools import tool\n", + "\n", + "EMBEDDING_MODEL = \"all-MiniLM-L6-v2\"\n", + "VECTORDB_DIR = \"data/airline_policy_vectordb\"\n", + "K = 2\n", + "\n", + "@tool\n", + "def lookup_policy(query: str)->str:\n", + " \"\"\"Consult the company policies to check whether certain options are permitted.\"\"\"\n", + " vectordb = Chroma(\n", + " collection_name=\"rag-chroma\",\n", + " persist_directory=str(here(VECTORDB_DIR)),\n", + " embedding_function=HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)\n", + " )\n", + " docs = vectordb.similarity_search(query, k=K)\n", + " return \"\\n\\n\".join([doc.page_content for doc in docs])\n", + "\n", + "print(lookup_policy)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test the RAG tool" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "f:\\end_to_end_AI_Projects\\QueryMind _ AI_Powered_Natural_Language_Interface_for_SQL_&_Vector_Databases\\querymind\\Lib\\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", + "Loading weights: 100%|██████████| 103/103 [00:00<00:00, 2527.99it/s]\n", + "Failed to send telemetry event ClientStartEvent: capture() takes 1 positional argument but 3 were given\n", + "Failed to send telemetry event ClientCreateCollectionEvent: capture() takes 1 positional argument but 3 were given\n", + "Failed to send telemetry event CollectionQueryEvent: capture() takes 1 positional argument but 3 were given\n" + ] + }, + { + "data": { + "text/plain": [ + "'hour cancellation policy that allows passengers to cancel their flights within 24 hours of booking at +1-877-507-7341\\nwithout penalty . This policy applies to all fare types, including non-refundable tickets. If you cancel your Swiss Airlines\\nflight within 24 hours of booking, you\\'ll receive a full refund of your ticket price.\\nHow to Cancel Swiss Airlines Flight within 24 Hours? If you need to cancel your Swiss Airlines flight within 24 hours of\\nbooking, you can do so easily online. Here are the steps to follow:\\nGo to Swiss Airlines\\' website and click on the \"Manage your bookings\" tab. Enter your booking reference number and last\\nname to access your booking. Select the flight you want to cancel and click on \"Cancel flight.\" Confirm your cancellation\\nand you\\'ll receive a full refund of your ticket price. If you booked your Swiss Airlines flight through a travel agent, you\\'ll\\nneed to contact them directly to cancel your flight within 24 hours.\\nImportant Things to Keep in Mind for Swiss Airlines 24 Hour Cancellation Here are some important things to keep in mind\\nwhen cancelling your Swiss Airlines flight within 24 hours:\\nSwiss Airlines\\' 24 hour cancellation policy only applies to flights booked directly through Swiss Airlines. If you booked\\nyour flight through a travel agent or third-party website, you\\'ll need to check their cancellation policy . If you cancel your\\nSwiss Airlines flight after the 24 hour window , you may be subject to cancellation fees or penalties. If you have a non-\\nrefundable ticket and cancel your flight within 24 hours of booking, you\\'ll receive a full refund of your ticket price.\\nHowever , if you cancel your flight after the 24 hour window , you may not be eligible for a refund. Swiss Airlines\\' 24 hour\\ncancellation policy allows passengers to cancel their flights within 24 hours of booking without penalty . If you need to\\ncancel your Swiss Airlines flight within 24 hours, you can do so easily online. Just remember to check the terms and\\nconditions of your ticket to make sure you\\'re eligible for a refund.\\nSwiss Air Cancellation Fees The cancellation fees for Swiss Air flights may vary depending on the type of ticket you have\\npurchased. The airline of fers three dif ferent types of tickets, which are:\\n\\nfor a refund or may only be able to receive a partial refund. If you booked your flight through a third-party website or\\ntravel agent, you may need to contact them directly to cancel your flight. Always check the terms and conditions of your\\nticket to make sure you understand the cancellation policy and any associated fees or penalties. If you\\'re cancelling your\\nflight due to unforeseen circumstances such as a medical emergency or a natural disaster , Swiss Air may of fer you\\nspecial exemptions or accommodations. What is Swiss Airlines 24 Hour Cancellation Policy? Swiss Airlines has a 24'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lookup_policy.invoke(\"can I cancel my ticket?\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1.2 Search tool design**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", + "search_tool = TavilySearchResults(max_results=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test the Search Tool" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "search_tool.invoke(\"What's a 'node' in LangGraph?\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1.3 SQL agent tool design**" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "from langchain.chains import create_sql_query_chain\n", + "from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool\n", + "from langchain_core.prompts import PromptTemplate\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from operator import itemgetter\n", + "from langchain_groq import ChatGroq" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**SQL agent chain**" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "sqldb_directory = here(\"data/travel.sqlite\")\n", + "\n", + "sql_llm = ChatGroq(model=\"openai/gpt-oss-120b\", temperature=0)\n", + "# llm = ChatGroq(model=\"llama-3.3-70b-versatile\")\n", + "system_role = \"\"\"Given the following user question, corresponding SQL query, and SQL result, answer the user question.\\n\n", + " Question: {question}\\n\n", + " SQL Query: {query}\\n\n", + " SQL Result: {result}\\n\n", + " Answer:\n", + " \"\"\"\n", + "db = SQLDatabase.from_uri(\n", + " f\"sqlite:///{sqldb_directory}\")\n", + "\n", + "execute_query = QuerySQLDataBaseTool(db=db)\n", + "write_query = create_sql_query_chain(\n", + " sql_llm, db)\n", + "answer_prompt = PromptTemplate.from_template(\n", + " system_role)\n", + "\n", + "\n", + "answer = answer_prompt | sql_llm | StrOutputParser()\n", + "chain = (\n", + " RunnablePassthrough.assign(query=write_query).assign(\n", + " result=itemgetter(\"query\") | execute_query\n", + " )\n", + " | answer\n", + ")\n", + "# Test the chain\n", + "# message = \"How many tables do I have in the database? and what are their names?\"\n", + "# response = chain.invoke({\"question\": message})\n", + "\n", + "@tool\n", + "def query_sqldb(query):\n", + " \"\"\"Query the Swiss Airline SQL Database and access all the company's information. Input should be a search query.\"\"\"\n", + " response = chain.invoke({\"question\": query})\n", + " return response" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "message = \"How many tables do I have in the database? and what are their names?\"\n", + "response = query_sqldb.invoke(message)\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Wrap up the tools into a list**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "tools = [search_tool, lookup_policy, query_sqldb]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### **Load the LLM for the primary agent and bind it with the tools**" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "llm = ChatGroq(model=\"openai/gpt-oss-120b\", temperature=0)\n", + "# Tell the LLM which tools it can call\n", + "llm_with_tools = llm.bind_tools(tools)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **2. Initialize the Graph State**\n", + "\n", + "Define our StateGraph's state as a typed dictionary containing an append-only list of messages. These messages form the chat history, which is all the state our chatbot needs." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated\n", + "from typing_extensions import TypedDict\n", + "from langgraph.graph import StateGraph, START\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "\n", + "class State(TypedDict):\n", + " messages: Annotated[list, add_messages]\n", + "\n", + "\n", + "graph_builder = StateGraph(State)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **3. Define the Graph Nodes**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**3.1 First node: chatbot**" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def chatbot(state: State):\n", + " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", + "\n", + "\n", + "graph_builder.add_node(\"chatbot\", chatbot)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to create a function that will run the tools when they are needed. To do this, we'll add the tools to a new node.\n", + "\n", + "In the example below, we'll build a BasicToolNode. This node will check the latest message and, if it contains a request to use a tool, it will run the appropriate tool. This works because many language models (like Anthropic, OpenAI, and Google Gemini) support tool usage." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**3.2 Second node: BasicToolNode that runs the appropriate tool based on the primary agent's output**" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from langchain_core.messages import ToolMessage\n", + "\n", + "\n", + "class BasicToolNode:\n", + " \"\"\"A node that runs the tools requested in the last AIMessage.\"\"\"\n", + "\n", + " def __init__(self, tools: list) -> None:\n", + " self.tools_by_name = {tool.name: tool for tool in tools}\n", + "\n", + " def __call__(self, inputs: dict):\n", + " if messages := inputs.get(\"messages\", []):\n", + " message = messages[-1]\n", + " else:\n", + " raise ValueError(\"No message found in input\")\n", + " outputs = []\n", + " for tool_call in message.tool_calls:\n", + " tool_result = self.tools_by_name[tool_call[\"name\"]].invoke(\n", + " tool_call[\"args\"]\n", + " )\n", + " outputs.append(\n", + " ToolMessage(\n", + " content=json.dumps(tool_result),\n", + " name=tool_call[\"name\"],\n", + " tool_call_id=tool_call[\"id\"],\n", + " )\n", + " )\n", + " return {\"messages\": outputs}\n", + "\n", + "\n", + "tool_node = BasicToolNode(tools=[search_tool, lookup_policy, query_sqldb])\n", + "graph_builder.add_node(\"tools\", tool_node)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **4. Define the entry point and graph edges**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Aproach 1**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Literal\n", + "\n", + "\n", + "def route_tools(\n", + " state: State,\n", + ") -> Literal[\"tools\", \"__end__\"]:\n", + " \"\"\"\n", + " Use in the conditional_edge to route to the ToolNode if the last message\n", + " has tool calls. Otherwise, route to the end.\n", + " \"\"\"\n", + " if isinstance(state, list):\n", + " ai_message = state[-1]\n", + " elif messages := state.get(\"messages\", []):\n", + " ai_message = messages[-1]\n", + " else:\n", + " raise ValueError(f\"No messages found in input state to tool_edge: {state}\")\n", + " if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n", + " return \"tools\"\n", + " return \"__end__\"\n", + "\n", + "\n", + "\n", + "graph_builder.add_conditional_edges(\n", + " \"chatbot\",\n", + " route_tools,\n", + " \n", + " {\"tools\": \"tools\", \"__end__\": \"__end__\"},\n", + ")\n", + "# Any time a tool is called, we return to the chatbot to decide the next step\n", + "graph_builder.add_edge(\"tools\", \"chatbot\")\n", + "graph_builder.add_edge(START, \"chatbot\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Approach 2**" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import END, MessagesState\n", + "from typing import Literal\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state: MessagesState) -> Literal[\"tools\", END]:\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If the LLM makes a tool call, then we route to the \"tools\" node\n", + " if last_message.tool_calls:\n", + " return \"tools\"\n", + " # Otherwise, we stop (reply to the user)\n", + " return END\n", + "\n", + "graph_builder.add_conditional_edges(\n", + " \"chatbot\",\n", + " should_continue,\n", + " [\"tools\", END],\n", + ")\n", + "# Any time a tool is called, we return to the chatbot to decide the next step\n", + "graph_builder.add_edge(\"tools\", \"chatbot\")\n", + "graph_builder.add_edge(START, \"chatbot\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **5. Compile the graph**\n", + "\n", + "- In this step, we can add a memory to our graph as well." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.checkpoint.memory import MemorySaver\n", + "\n", + "memory = MemorySaver()\n", + "graph = graph_builder.compile(checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**5.1 Plot the compiled graph**" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAD5ALYDASIAAhEBAxEB/8QAHQABAAMAAwEBAQAAAAAAAAAAAAUGBwMECAIBCf/EAE0QAAEDBAADAwUKCgcHBQAAAAECAwQABQYRBxIhEyIxCBQVQbMXMjdRVmFxdZXSFiMzNkJSVJGTsiVydIGhwtMJGCZXZYOxJ2OClKP/xAAbAQEAAgMBAQAAAAAAAAAAAAAAAQIDBAUGB//EADsRAAIBAgEGCgcJAQEAAAAAAAABAgMRBBIhMVFhkRMUQUJScYGhsdEFIjNTksHwFSMyNHKCstLhJGL/2gAMAwEAAhEDEQA/AP6p0pSgFKUoBSlcM2Yxbob8uU6hiMw2p111w6ShCRtSifUAATUpNuyBzVEXTLrFZHyxcb1boDwAJblS221aPh0URVcFon8SGWpdylTLTjjqedi0x1FiRJbUkaVJcSrmAO1aaSR0I5yTtKZKLwtw6GwhlrFrPyJATtcFtajr41EEk/OTutvg6NPNVk76l5vy7S1ktJy+6TiPypsv2iz96vz3ScR+VVl+0WfvV++5viXyWsv2ez92nub4l8lrL9ns/dq3/J/67ifVA4kYko6GU2Un4hcGfvVPRpLMxhD8d1D7Lg5kONqCkqHxgjxqB9zbEev/AAtZev8A09n7tRcvhLZo7y5mOc+JXTXdk2gBtpRAOg4x+TcTs7IKd9OhHjUOOGlmjJrrSa7s/j1EeqXelVzGsndnTH7NdkNRMhiNpceZaJ7OQ0ToPs76lBIIIPVCu6d91SrHWrOEqbyZENWFKUqhApSlAKUpQClKUApSlAKUpQClKUAqh8SHBd75iOLODcW6TVSZaShK0uMRk9r2agR4Kc7IH5tj11fKoWcbt/EXALm4tDcMvy7ctazrTjzIU2P71M6+kj463MJ7W/KlJrrUW13lo6S+1jOReVPjdiye/WeNj+VZA3YHkR7vcrJaTJiwnVAEIWQoLURsb5Eq1WzV4w428Cs5yLiHlN2xPAZlkyWY+hdszbGcsTAZWkJACpsdauZSh1CuzQeYHW/XWmVNS/3mJ6/KgPDRvFLiuxt2QXB25iOkLStTqQJBJdATFCCUklPPz9OUAbMhiPlb4dl+WWKytWzIraxkDz8ezXi5W7sYVycZ3zpaXzFQPTpzpTvw8elUm9cNuKdg8odjL7Va2b76SwNONyL43LYZRBuAcC/OFtOEKWjmSFaQk+++asus3AXjSbhwvym92W83u/4veTJurF3zBMwzQtR27GbW52LCEJA2kEKVzAa7vUDZpnlsWGfjWaXHHMOy28PYw3MExabc35sw4wdHtHe15Qk9V6BKuRCjyg8oOjeT1xPuPGHhLYMou1lkWS4TI7anm3Wg208otpUXWB2iz2Kio8pUebodiso4TcDcts3k98Z8Rultbtl6yi5X1y3ockNLS43KYDbLilNqUEgn1HqAOoFaX5MNsybHuCeM2DLcccxm72SI1bDHXMZk9uhptADwU0pSQFHY5Sdjl+cUBN8Uj6GRYsnQ4lly0XBpD6zvvRX1pZeRodD75C+vrbFXmqFxqaRccNZspUpL95uUOCyUp3ol9C1E/MENrV/8avtblTPh6bem8l2Zmu9ss9CFKUrTKilKUApSlAKUpQClKUApSlAKUpQConKsdZyuwyra845HLgCmpLKilxh1JCm3UkEd5KwlQ+jr0qWpVoycJKUdKGgquNZiX5ibFfQ1b8mbT1YBIamAJ2XY5V79Ot7T75BBB8Ao2qo2/wCOWvKrcqBd4LFwiKPN2b6N8qtEcyT4pUATpQ0Rvoaqb/CFrnIh5bldtY0AiPHupWhsDwCe0Ssgf31tWoVM7eS+q67OXst2lszL9Ss89yB75e5l9ot/6VVXB8LuOTXPMGJOc5Yhu0XpVujlqegFTYjR3Nr22dq5nVjY0NAdPEmeBoe97mLLWbbXRvd8t+OW164XSYzBhtDa3n1BKR8w+Mn1AdT6qpfuQOka/DzMvtFv/SqVsvC+xWmbHnvplXu6R08rdwvMpct1He2CjnJSg79aAn/zUcHh453NvqXn/vULLWfNrtL+TZPHya4NPRosNpTdpgvo5HEc405IdSRtK1DSUoPVKd7AUspTb6UrBUqOo1qWZbF9d5DdxSlKxEClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKzvhB3p/ENf6+UyP8GI6f8ALWiVnfBrvIzZf6+Uz/8AApT/AJaA0SlKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKzvgvrzHLhogjKLnvZ/98n/AMarRKzvg33fw5R+plM7/HkV/moDRKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKVnEnO8lyNKpGIQLS5a0rUhuddpLgEnlPKpTaG0khIUFAFR72tga0T1vTXFL9lxD+PK+5XQWCqc5pPU2rlsk1ClZf6a4pfsmIfx5X3KemuKX7LiH8eV9yp4jPpR3oZO0t/EHM2eHeFXjJZFun3aNa46pT0S2IQuQptPvylK1oSeVO1Hah0SdbPSvM/kq+WDiXFXiNkOLWWx5GmXebpKvDUmTFYDEZjsWx+OUl5RSSpBSNAjak9ep1szt14nvtLbchYc42sFKkKelEKB8QRydaxvgP5NuQ+T3kGT3fHI2NPSb44OkuRIV5oyFFXYtkNg8uyN7JJ5U/FTiM+lHeMnaet6Vl/pril+y4h/Hlfcp6a4pfsmIfx5X3KcRn0o70MnaahSsv9NcUv2XEP48r7lfTN74nh1Jdh4kpvfeSiRKBI+Y8h1+6nEp9KO9DJ2mnUqCxPK2soivhTCoNzhrDM6A4oKVHc5QodR0UlQIUlQ8QfUQQJ2tGcJU5OMlnIeYUpSqEClKUApSlAKUpQClKUApSlAZfwkO+GOMH/p7X8oq21UuEnwY4x9Xs/yirbXbxPt59b8S0tLFKVEYrldqzWzIutlleewFvPRw92a29radW04NKAPRbaxvWjrY2CDWsVJelKhMkzWx4lbrvOutyZjM2mF6RnJBLjrEbv8A40tpBXyns3ANDqUKA2QaAm6Vxx325Udp5pXM04kLQrWtgjYNclSBSuvCuMS5JeVElMyksuqYdLLgWEOJOlIVrwUD0IPUV2KAqfDhRPFbiON9Am2aH/ZXWn1l/Df4WOJH9W2exXWoVTH+2/bD+ES0tIpSlc8qKUpQClKUApSlAKUpQClKUBl/CP4McX+r2f5RVtqpcJPgxxj6vZ/lFW2u3ifbz634lpfiZ584RR5+aYzjfEa6Z5dbVd7hdXRIguzv6OcQJTrKYKYqlBtKtJSgKSO05hvZNZ5jUC44rwKt+cW7I71GuETMHWGoLU1aIJju5Ath1pyODyOcwccVzqBUCoAEAAV6Ki8C8HhZZ+EjNjCLmJSp6U+dPmMiSrfM+mMV9ilw7J5wgK2Sd7O67/uUYr+BwxX0X/QImekBE84d/L+ded8/Pzc35fv63r1a5elaeSyp5z405Zkq4PEzM8Wn3lmJi0lyOmfNyVcKKxIYQ2FtMQGmVIkJ5zo9uUlSlKAUBo1xcYLLHl3/AMoC6qeuHnCeH8R5LRuUgsAuNzgoFrn5CkcoISU6SSpSQCpRO/3jgNgt/n3iVPsZf9Mc5nxfPJCYshakcinVRw4Gu15ena8vOPEKB6183PgJhF6A8+tcqSr0SqxuLcuksrfhkLHZOq7Xbug4vSllSk8xIINRksGYTJl24WZTw6mW/IbvkAv1ouBuEGdMU9HfWxBEht1lrfKz30BOmwBp0D4q6Nnm3ixYRwcz1vKr1db3lNztbV1jyri45CkonI26hEYns2uy5ipPZpSQG+u+tbXjHB3EsPvaLta7Y43OaYVFjqkTX5CIjKiCpthDi1JYQSB3WwkdPCurj3ArB8VvUW6WyyliREccehsrmSHY0NawQtTEdbhaZJClDbaU9CfjqclgqXksWRiz41mQZenPby+8s7mz35R5W5jiE9XVq66A5leKj1USetbVUFjeEWXEZt5lWiGYbt3lKnTQHnFIcfV75YQpRSgqPU8oGz1OzU7VkrKwKlw3+FjiR/VtnsV1qFZfw3+FjiR/VtnsV1qFRj/bfth/CJaWkUpSueVFKUoBSlKAUpSgFKUoBSlKAy/hJ8GOL/V7P8oq21ToFpyfh/GRZIFgcya0xgRCmMTGWXUtE91p1Lik95I6cyTogA6B2K+blmWT2mL5xJ4f3QNc7bf4qZFdUVLWEJHKlwn3yh1108ToAmu/VgqtSVSEo2bb/FFadjdy7V3cudKqqshy1IJPD+4AAbJNyhdP/wBa6lozXJb5bWJ8Ph/dFRX08zanZkVoqG/HlW4Do+IOuo0R0IrFwL6Ufij5kZLLrSqr+EGXf8vrh9pQv9WofMOJ92wDGLjkN/wudbrNbmi9KlLnxFBtA6b5UuEnqQNAEndOBfSj8UfMZLNCpVMteZ5He7ZEuNvwaZMgy2USI8hm5wlIdbUkKSpJ7XqCCCPprjtWcZHem5C4mA3NYjyHIrqXJsVtSXEKKVApU4DrpsHWlJKVJJSoEuBfSj8UfMZLLvSqt+EGXf8AL64faUL/AFa4JuVZZBhSJK+HlzWlltThS3OiuKIAJ0EocUpR6eCQSfUCacC+lH4o+YyWffDf4WOJH9W2exXWoVS+G9kWwi5X+Wto3O9qZfeaY5uWO2lpIaa73UqCTskhOys9ANVdK0sZONSteL0KK3RSfehLSKUpWkVFKUoBSlKAUpSgFKUoBSlRV5ursVxuDCZLt0lMvLjFxtZjoKEjq6sDup5lIGvfHm6AgKIA7FyurVsVDQ42+6uXITHaSw0V94gqJUR0SkJSolRIHTXUkA9Ky2V1t5m6XXsnr8qMI7rkdbnYNp5ysoaSo6T1IBWAFL5Ec3vUhPYtNkatr0iWo9vcpaWvO5RKvxhQgJASkqPZo8SEJ7oK1q98pRPBkd5VBbZgQn2Gr3cQ41b0yGXHWw4lBUVuJR1DaQOpJSCSlHMFLTsDhu6Xr7ck2psSmILfK9KuEOYlpSXEqQpMfSdr7yTtR7vdIAJ5iBP10rTaI1miqZjMNMlxxT7ymmwjtXVHa3FAeKlHZJNd2gFefvLiwTOuJ3Ax/F8CtXpe4XGewmYx50yxqMnmcJ26tKT30NjW99fDxI9A0oDyr/s+sT4lYZwlZhZc7bXsad53bSyiWtybBUHVJcZcRycnISCocrhKTsEd48vo24oftV/j3FoXKczM7OC9FZcSpiMAVqTIKD3h1VyKKD4FJKSE7TWODH9HxswsJ6Gz5LPQE/qokqTOQPoCZiQPmHzVernbYt6tsu3zmESoUtlbD7Dg2lxtSSlSSPiIJFAdqlQeOTVsOP2aYuK3Nh9WWW5hfdXEJKWXlhffBPKpJKt7UhXeNTlAQlwxlC5Uifa3UWm7SVsGRMaZSsyENE6bcB98ClS07BCgD0I0K+YuUIansW+7tIs9wlyX2ILLr6Vialsc4U0R6y3tRQQFDkc0ClBWZ2uN9huSy4y6kLbcSUKSfAgjRFAclKq7guGFw1KZRJvVihQm224qO0k3MLSvSlF1xwl8dmQdH8ZttXV1TgCbFGmx5od83faf7JwtOdksK5FjxSdeBHrFAc1KUoBSlKAUpSgFKUoCEvVzlKleiLYQzdHWO3TJkMqUw22HEJWdjXMvSlFKd+Ke9oEb71ss8OzpkiI12ZkyFynlqWpa3HFnZUpSiSemkgb0lKUpTpKQBGZelyJHhXdoXV9Vsf7dUG1KBVKQpKm1IW2ejiUhfaco0rmbTyn1KsFAfK1BCFKOyAN9Bs/uqHxtiU+hy7Tkzokq4ttOKtcx9DggAI/JAN9zm2VFZCl7USAtSEo12Mliidjt1jGIqeHojrZiIc7MvbQRyBf6JO9b9W91+46yY+P2xoxFQCiK0kxFudopnSAOQr/SI8N+vW6AkaUpQClKqebZTMhOxrDYEtP5RcUkx+2SVswmgdLlPAEEoRvogEFxZSgFIKloAiMWSYnGrPGWNOw5MG2S3VtnYbl6fbcbXr3quxbiqAPUhQPhreh1D4pi0PELOiBDU68StT0iXJUFPynlHa3nVAAFaj1OgAOgACQAJigIXIYTyVxbpDU0zKhKJdWYfnDj0Y9XWU674KtJUOX9JtGwodDJwJzFzhMS4y+0jvIC0K0RsH5j1B+Y9RXPUBOS5jk5+5NlTtukuB24mXOKW4aENkds0lQIA7qOdIUhOgpYBUVc4E/SuOPIalx2n2HUPMOpC23G1BSVpI2CCOhBHrrkoBURLx1pya3MhPrtckykSZK4qEDzzlR2fI9tJ5hy6G+ihyI0dDVS9KAgbDkapcv0RdEx4mRMxxKfhxnVOoDSnHG0OJUUp2FdmTojY8D8Znqrrdx/9Qn4HpdCv6LbfFp820pP45aS/wBr6weieT1cu/XVioBSlKAUpSgFKVWr/wAScVxaYYl2yC3wZY1zMOvp7ROwCNp8RsEEb8ayQpzqPJgm3sJSvoLGtCXEKQtIWhQ0UqGwR8RqtYzIbxiwJt9xQq0w7bIbtcN+4z0vmU2ShuOrtVHmK3CtCNL75X07xIUqO93DAvlXbf41fze4tcM72PKlxnI5meHiNjki8RnheH5AU7AYD/OppxsaS2hBUojswG9HYCSSkZ+J4n3ctzJyZaj+pl6jiXZ5zBYMoOsOILCV8hc2kjlCvVvw36t11sSjmJitmYMFdrLUJlBguPdsqPpsDsyv9Mp8Ob163Xljy3bZgfH3g7IZtuQ2yRlNlUZ1q06OZw606yCf10gfSpCKg/8AZ/2vEOB3CiXNv9+g2/KMifS/Miuu6Wwy1zJZbUPUe84r/uAeIpxPE+7luYyZaj2zSqP7uGBfKu2/xq69w4+YBboEiUrJob4ZbU4Wo6i44vQ3pKR1JPqFOJ4n3ctzGTLUWDMstRikBktxlXG6zXfNrdbW1cq5T5BITvR5UgBSlL0QlKVK660eLCcScxyPKl3GSm5ZFclh+5XAJ5Q4ob5Wm0knkZbB5UI2dDalFS1rWqocLcjsuXXx6/zL3bZuVzGSyxbmJSHPRsTYX5u0N95RKUqdcTvnUlI3yNthOqVgnTnTeTNNPaQ1bSKUpWMgUpSgIFCX8duIQlMmZa5ryUtttNNBu2ab6+HKotqUkepakrWdkI1yT1cUqKzOjPRpLLciO8gtuNOpCkLSRopUD0II6EGoOE8cYmM22QptNsfcQxawxHd/EAN9Wnl7UkdUq5VnkB5ko1zAFYFhpSlAV0XHXEMwPS6OtrD4tPm3e/Lcpf7X4uoTyf31Yqyp3j3w9azVCVcU8WRD9HqJgmfH5eftE/jO359A6Ouz3s73rpWq0ApSlAKUpQERmF0eseJXu4x9dvDgvyG+YbHMhtShsfSKpvDaCxFwmzvtpJfmxWpkl9ZKnH3nEBS3FqPVSiSep+jwAqzcSfg7yn6qlexVUBw+/MLGvqyN7JNdahmwza5ZfItzSfpSlUKilKUApSlAVbibEadwi8zCkpl2+I9MiSGzyuMPNoKkrQodUnaR4eI2D0Jq+2C4Lu9hts5xKUOSozb6kp8AVJBIH76pHEn4O8p+qpXsVVbMK/M2w/2CP7NNWr58NFvkk/BFuaTVKUrlFRSlKAVU+K8rIofDm/uYnZmsgyLzUohW96WYqHFqITsuAgjlBK9JUknl0FJJChbKoHFTiOrEWo9rtpbcv09tam+cgiK0OhfUn194gJSdcx310DWehQniKip01dslK54a8kTifxGwDykb7H4xSMhjG62pTTz9+LpaZLS1Os8vNtIT3nwgJ6bdOvfV7bV5RfD1J16fUfogySPZ1jSYKFTnZ8lbk65Onbs6UrtHl9Ne+PgNAAJGgPUBXYr19P0Hh1H72Tb2WS70xeJ5YvfAPCZXlds3diaj3K5Er0vIUIroDa+q1ROz5AvSnBoEAgJV47Ff0FT5RXD1Z0L+R86oMkD95brIaVl+xMHrlvX9ReOr63HpKwZRZ8pimRZ7nEubI1zKivJc5NjYCgD3T8x0alK8oCCY1xbudveXbLs0dtzovdcHh0V6lpOtFKgQRW98M+IQzmBIZlMCJeoHKmWygHs1BW+R1sn9BXKroTtJSpJ3oE8LH+i5YSPC03ePLrX+bRmedF0pSlcEgrnEn4O8p+qpXsVVAcPvzCxr6sjeyTU/xJ+DvKfqqV7FVQHD78wsa+rI3sk11qP5V/q+RbmkzNXIRDfVEaaflBtRZaecLaFr13QpYSopBOtkJOvHR8KwThrx4yq5cNcMkXOyQ7xl+Uy5LVsix7h2TTjTRWtx19fYAMpbSnl0lLhV3PEqIT6BrzzjXA7N8TsWFGG/YH7zhU6Ym3B2S+li4wZKVBaXlBoqYd7yCCkOAFHr5tDC73zFSyOeUI5BiXG3z8aWzm0S9R7EiwsTQ40+/Ia7ZlaZBQkdkWgtZUUBSQ2scpIANX4u8Xr6eHfEGyTYS8PzCzRoM1tdruan0OxnpKUJdZfCGle+Q4hQKUkfODXduHAjKbs/Py1+fZ2c8cyKHfo0Zsuqt7SI0cxkRlOFIcUFNOPEucg7ywQjSev5mHA/LuIkDObneH7JCyS+2+DaYUOJIediRI0eQXzzvKaStalqWs9GwBpI+M1X1gWORxvuk3NMhsmPYo3fG8fltxJ6Dd2o89ZU224pbEVSfxiEpdHeUtHMQQneq1usB4t8Ecq4mXS5suQMQKXHQq05YS/HvFnRoe8S22e1UkjaT2yAem09Ou+oSUoSCoqIGio+J+erq/KCu8Sfg7yn6qlexVVswr8zbD/YI/s01U+JPwd5T9VSvYqq2YV+Zth/sEf2aayV/wAsv1PwRbmk1SlK5RUUpSgFeX79dXb/AJ9lk94r23PVbmkKUSlDTA5AEj1bUXFfSs16grzBkFpcx/P8rgOhe3ZxuLS1JIC23xzbT8elhxO/jSa9N6CyeFqX02+av8ieRldzfLoOBYndMguPOYcBkurS2AVL9QSN9NkkAfTVHx7jbKmXFq333GHbBNm2td2tqTNQ+iU0hPMpClJSChYBBIIPTfWrZxOwhviPgV6xtx/zYT2ORD3LzdmsEKQoj1gKSNj4qzLh7wMu2PSVPXG1Ybb3GLc5DZfssRzt33VIKO1W4pI5NgnaUhW9n6K9HWlXVVKn+H6v3aNHaUPqyeUxJuNqxC9TMMkwLBkM5NtTcPPkOdk+pSkjSOUKUjaVd48vvVaB6bjuKPHK73LHuIkPG7BNNusSHYEjIo09LTjEkDRLbYAUQhXioK2B11XcTwHv44PYBinnlt9I4/eWbjKd7VzsVtocdUQg8myrTiehAHQ9a6t74G5swjiHZ8fuljbx3LH3ZpXO7bzlh1wbWgBKSnlUdDm2SB1AJrSk8W4Wd861K98nR1X7doNc4Yy37hw1xOVKeckyX7REcdeeWVrcWWUFSlKPUkkkkmrtg1wVZOJ+NykuBpuap22SByAlxK0FaBv5nGk/vPxmqng9kfxrCsftEpbbkm32+PEdUySUFbbaUqKSQCRsHWwPoq24Jb1XvifjkVLYdagl25yDzAFCUILaDr53HE/uPxGuhVSWFmqnRd9312lo6T0pSlK+ZgrnEj4O8p+qpXsVVAcPvzCxr6sjeyTVsy61O33FL1bWCkPzIT8dsqOgFLbUkb/vNUvhvcI8rDbTFbc1LgRGYsuMscrsd1CAlSFpPVJBSfEdfEbBBrrUM+GaXJL5FuaWelKVQqKUpQClKUBXOJPwd5T9VSvYqq2YV+Zth/sEf2aapnEyayjDLvb+btJ9xhvRIkRvvOvuLQUhKE+J6qBOvAbJ6A1frBb12ixW6C4oLXFjNsKUnwJSkAkfuq1fNhop8rfgi3NO/SlK5RUUpSgFUPihw5GYMsXGB2bN+goUGVrGhIbI2WFn1AnRCuvKeuiCoG+UrPRrTw9RVKbs0ToPKKpyY9yetsxtdvujKilyDKAQ6D8YG9KBHUKSSCOoNdivSV+xez5THSxeLXDubSd8iZbKXOTfQlJI7p+cdapa/J14eLUVHHEgn9WW+B+4OV66n6coOP3sWnss/FoWiZDStd/3c+HfydH/ANyR/qV9N+Tvw8bVzDHEH5lyn1D9xXqsv23hNUty/sLR1/W8xduYqfcBbbWwu7XZXvYUTvKHUDaz4NpBI2pRAFb3wy4e/gLbZDkqQJl6nlK5j6NhscoPI02D+gnmVonqSpROt6FjsmO2rGopjWi2xLZHJ5i3EZS0lR1rZCQNn5zUjXCx/pSWLjwVNZMe9/WoZlmQpSlcIgVA3jAsZyGWqVdMetdwlK0FPyYbbjh0NDaiN+FT1KvCcqbvB2ewm9ip+5JhHyRsn2e192nuSYR8kbJ9ntfdq2UrNxmv03vZOU9ZU/ckwj5I2T7Pa+7T3JMI+SNk+z2vu1bKU4zX6b3sZT1lT9yTCPkjZPs9r7tPckwj5I2T7Pa+7VspTjNfpvexlPWQVmwTG8dlCTa7BbLdJAID0WG22sA+I5gN6qdpSsMpym7zd2Re4pSlUIFKUoBSlKAUpSgFKUoBSlKAUpSgP//Z", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", + "except Exception:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Save the graph image**" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "save = False\n", + "\n", + "if save:\n", + " from PIL import Image as PILImage\n", + " import io\n", + " # Assuming graph.get_graph().draw_mermaid_png() returns PNG binary data\n", + " try:\n", + " # Generate the PNG image from the graph\n", + " png_data = graph.get_graph().draw_mermaid_png()\n", + " \n", + " # Convert the binary data into an image\n", + " img = PILImage.open(io.BytesIO(png_data))\n", + " \n", + " # Save the image locally with 300 DPI\n", + " img.save('output_image.png', 'PNG', dpi=(300, 300))\n", + " \n", + " print(\"Image saved successfully with 300 DPI.\")\n", + " except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **6. Execute the graph**" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"1\"}}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**First query**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Approach 1: Print all the steps the the system goes through it to get the final output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Hi there! My name is Farzad.\n" + ] + } + ], + "source": [ + "user_input = \"Hi there! My name is Farzad.\"\n", + "\n", + "# The config is the **second positional argument** to stream() or invoke()!\n", + "events = graph.stream(\n", + " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n", + ")\n", + "for event in events:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Approach 2: Just print the final output" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello again, Farzad! How can I help you today?'" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "user_input = \"Hi there! My name is Farzad.\"\n", + "\n", + "# Use the Runnable\n", + "final_state = graph.invoke(\n", + " {\"messages\": [HumanMessage(content=user_input)]},\n", + " config=config\n", + ")\n", + "final_state[\"messages\"][-1].content" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Second query**" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Can I cancel my ticket 10 hours before the flight?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " lookup_policy (call_PnEdkdZICkTDuytncfiYhJo2)\n", + " Call ID: call_PnEdkdZICkTDuytncfiYhJo2\n", + " Args:\n", + " query: ticket cancellation policy\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: lookup_policy\n", + "\n", + "\"for a refund or may only be able to receive a partial refund. If you booked your flight through a third-party website or\\ntravel agent, you may need to contact them directly to cancel your flight. Always check the terms and conditions of your\\nticket to make sure you understand the cancellation policy and any associated fees or penalties. If you're cancelling your\\nflight due to unforeseen circumstances such as a medical emergency or a natural disaster , Swiss Air may of fer you\\nspecial exemptions or accommodations. What is Swiss Airlines 24 Hour Cancellation Policy? Swiss Airlines has a 24\\n\\nhour cancellation policy that allows passengers to cancel their flights within 24 hours of booking at +1-877-507-7341\\nwithout penalty . This policy applies to all fare types, including non-refundable tickets. If you cancel your Swiss Airlines\\nflight within 24 hours of booking, you'll receive a full refund of your ticket price.\\nHow to Cancel Swiss Airlines Flight within 24 Hours? If you need to cancel your Swiss Airlines flight within 24 hours of\\nbooking, you can do so easily online. Here are the steps to follow:\\nGo to Swiss Airlines' website and click on the \\\"Manage your bookings\\\" tab. Enter your booking reference number and last\\nname to access your booking. Select the flight you want to cancel and click on \\\"Cancel flight.\\\" Confirm your cancellation\\nand you'll receive a full refund of your ticket price. If you booked your Swiss Airlines flight through a travel agent, you'll\\nneed to contact them directly to cancel your flight within 24 hours.\\nImportant Things to Keep in Mind for Swiss Airlines 24 Hour Cancellation Here are some important things to keep in mind\\nwhen cancelling your Swiss Airlines flight within 24 hours:\\nSwiss Airlines' 24 hour cancellation policy only applies to flights booked directly through Swiss Airlines. If you booked\\nyour flight through a travel agent or third-party website, you'll need to check their cancellation policy . If you cancel your\\nSwiss Airlines flight after the 24 hour window , you may be subject to cancellation fees or penalties. If you have a non-\\nrefundable ticket and cancel your flight within 24 hours of booking, you'll receive a full refund of your ticket price.\\nHowever , if you cancel your flight after the 24 hour window , you may not be eligible for a refund. Swiss Airlines' 24 hour\\ncancellation policy allows passengers to cancel their flights within 24 hours of booking without penalty . If you need to\\ncancel your Swiss Airlines flight within 24 hours, you can do so easily online. Just remember to check the terms and\\nconditions of your ticket to make sure you're eligible for a refund.\\nSwiss Air Cancellation Fees The cancellation fees for Swiss Air flights may vary depending on the type of ticket you have\\npurchased. The airline of fers three dif ferent types of tickets, which are:\"\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "You can cancel your ticket with Swiss Airlines, but the ability to receive a refund or avoid penalties depends on the timing and the type of ticket you purchased. Here are some key points regarding ticket cancellation:\n", + "\n", + "1. **24-Hour Cancellation Policy**: If you booked your flight directly through Swiss Airlines, you can cancel your flight within 24 hours of booking without penalty and receive a full refund.\n", + "\n", + "2. **Cancellation 10 Hours Before Flight**: If you are looking to cancel your ticket 10 hours before the flight, you may be subject to cancellation fees or penalties, especially if you have a non-refundable ticket. The specific fees can vary based on the type of ticket you purchased.\n", + "\n", + "3. **Contacting Third Parties**: If you booked your flight through a third-party website or travel agent, you will need to contact them directly to cancel your flight.\n", + "\n", + "4. **Special Exemptions**: In cases of unforeseen circumstances (like medical emergencies), Swiss Airlines may offer special exemptions or accommodations.\n", + "\n", + "It's always best to check the terms and conditions of your specific ticket for detailed information on cancellation policies and any associated fees.\n" + ] + } + ], + "source": [ + "user_input = \"Can I cancel my ticket 10 hours before the flight?\"\n", + "\n", + "events = graph.stream(\n", + " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n", + ")\n", + "for event in events:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Third query**" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Right now Harris vs. Trump Presidential Debate is being boradcasted. I want the youtube link to this debate\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " tavily_search_results_json (call_1jy65jCBXDpiaiDrn9n93HBC)\n", + " Call ID: call_1jy65jCBXDpiaiDrn9n93HBC\n", + " Args:\n", + " query: Harris Trump Presidential Debate YouTube link\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.pbs.org/newshour/politics/watch-live-harris-and-trump-debate-pbs-news-simulcast-of-abcs-2024-presidential-debate\", \"content\": \"WATCH: Harris and Trump debate \\u2014 PBS News simulcast of ABC\\u2019s 2024 Presidential Debate Vice President Kamala Harris and former President Donald Trump faced off Tuesday night for their first and possibly only debate before Election Day. The state of the race as they meet in Philadelphia is starkly different than it was just more than two months ago, when Trump debated President Joe Biden in a performance that accelerated calls for Biden to leave the race. WATCH: What to watch in the ABC Harris-Trump debate Watch PBS News\\u2019 special coverage here and the\\u00a0ABC Presidential Debate in the player above. This year\\u2019s presidential race is a genuine contest of ideas between Harris and Trump \\u2014 with clear differences on taxes, abortion, immigration, global alliances, climate change and democracy itself. LIVE FACT CHECK: Trump and Harris meet for presidential debate\"}, {\"url\": \"https://abcnews.go.com/Politics/watch-full-abc-news-presidential-debate/story?id=113470583\", \"content\": \"MORE: READ: Harris-Trump presidential debate transcript Democratic presidential nominee Vice President Kamala Harris shakes hands with former President Donald Trump, during a presidential debate ...\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "You can watch the Harris vs. Trump Presidential Debate through the following links:\n", + "\n", + "1. [PBS News Simulcast of the Debate](https://www.pbs.org/newshour/politics/watch-live-harris-and-trump-debate-pbs-news-simulcast-of-abcs-2024-presidential-debate)\n", + "2. [ABC News Full Debate Coverage](https://abcnews.go.com/Politics/watch-full-abc-news-presidential-debate/story?id=113470583)\n", + "\n", + "Feel free to check them out!\n" + ] + } + ], + "source": [ + "user_input = \"Right now Harris vs. Trump Presidential Debate is being boradcasted. I want the youtube link to this debate\"\n", + "\n", + "events = graph.stream(\n", + " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n", + ")\n", + "for event in events:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'messages': [HumanMessage(content='Hi there! My name is Farzad.', id='5aa78bed-a259-4d29-91c0-0e00d4113895'),\n", + " AIMessage(content='Hello Farzad! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 12, 'prompt_tokens': 149, 'total_tokens': 161, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_f85bea6784', 'finish_reason': 'stop', 'logprobs': None}, id='run-6a8c7f6f-0647-4569-a1ff-58747953ad59-0', usage_metadata={'input_tokens': 149, 'output_tokens': 12, 'total_tokens': 161}),\n", + " HumanMessage(content='Hi there! My name is Farzad.', id='45b30b4e-7aca-45f7-945d-48f4f3f738f0'),\n", + " AIMessage(content='Hello again, Farzad! How can I help you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 177, 'total_tokens': 191, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_f85bea6784', 'finish_reason': 'stop', 'logprobs': None}, id='run-632913bf-ab5b-4ddb-8806-aa609e10086c-0', usage_metadata={'input_tokens': 177, 'output_tokens': 14, 'total_tokens': 191}),\n", + " HumanMessage(content='Can I cancel my ticket 10 hours before the flight?', id='5a4cd448-96ea-4d6a-b865-ba9aa7661136'),\n", + " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_PnEdkdZICkTDuytncfiYhJo2', 'function': {'arguments': '{\"query\":\"ticket cancellation policy\"}', 'name': 'lookup_policy'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 210, 'total_tokens': 226, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_f85bea6784', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-dc4bc2d4-19a5-4ee6-b163-a7fc7e533489-0', tool_calls=[{'name': 'lookup_policy', 'args': {'query': 'ticket cancellation policy'}, 'id': 'call_PnEdkdZICkTDuytncfiYhJo2', 'type': 'tool_call'}], usage_metadata={'input_tokens': 210, 'output_tokens': 16, 'total_tokens': 226}),\n", + " ToolMessage(content='\"for a refund or may only be able to receive a partial refund. If you booked your flight through a third-party website or\\\\ntravel agent, you may need to contact them directly to cancel your flight. Always check the terms and conditions of your\\\\nticket to make sure you understand the cancellation policy and any associated fees or penalties. If you\\'re cancelling your\\\\nflight due to unforeseen circumstances such as a medical emergency or a natural disaster , Swiss Air may of fer you\\\\nspecial exemptions or accommodations. What is Swiss Airlines 24 Hour Cancellation Policy? Swiss Airlines has a 24\\\\n\\\\nhour cancellation policy that allows passengers to cancel their flights within 24 hours of booking at +1-877-507-7341\\\\nwithout penalty . This policy applies to all fare types, including non-refundable tickets. If you cancel your Swiss Airlines\\\\nflight within 24 hours of booking, you\\'ll receive a full refund of your ticket price.\\\\nHow to Cancel Swiss Airlines Flight within 24 Hours? If you need to cancel your Swiss Airlines flight within 24 hours of\\\\nbooking, you can do so easily online. Here are the steps to follow:\\\\nGo to Swiss Airlines\\' website and click on the \\\\\"Manage your bookings\\\\\" tab. Enter your booking reference number and last\\\\nname to access your booking. Select the flight you want to cancel and click on \\\\\"Cancel flight.\\\\\" Confirm your cancellation\\\\nand you\\'ll receive a full refund of your ticket price. If you booked your Swiss Airlines flight through a travel agent, you\\'ll\\\\nneed to contact them directly to cancel your flight within 24 hours.\\\\nImportant Things to Keep in Mind for Swiss Airlines 24 Hour Cancellation Here are some important things to keep in mind\\\\nwhen cancelling your Swiss Airlines flight within 24 hours:\\\\nSwiss Airlines\\' 24 hour cancellation policy only applies to flights booked directly through Swiss Airlines. If you booked\\\\nyour flight through a travel agent or third-party website, you\\'ll need to check their cancellation policy . If you cancel your\\\\nSwiss Airlines flight after the 24 hour window , you may be subject to cancellation fees or penalties. If you have a non-\\\\nrefundable ticket and cancel your flight within 24 hours of booking, you\\'ll receive a full refund of your ticket price.\\\\nHowever , if you cancel your flight after the 24 hour window , you may not be eligible for a refund. Swiss Airlines\\' 24 hour\\\\ncancellation policy allows passengers to cancel their flights within 24 hours of booking without penalty . If you need to\\\\ncancel your Swiss Airlines flight within 24 hours, you can do so easily online. Just remember to check the terms and\\\\nconditions of your ticket to make sure you\\'re eligible for a refund.\\\\nSwiss Air Cancellation Fees The cancellation fees for Swiss Air flights may vary depending on the type of ticket you have\\\\npurchased. The airline of fers three dif ferent types of tickets, which are:\"', name='lookup_policy', id='306a3eb2-1d47-48cb-baa3-1a721072a1df', tool_call_id='call_PnEdkdZICkTDuytncfiYhJo2'),\n", + " AIMessage(content=\"You can cancel your ticket with Swiss Airlines, but the ability to receive a refund or avoid penalties depends on the timing and the type of ticket you purchased. Here are some key points regarding ticket cancellation:\\n\\n1. **24-Hour Cancellation Policy**: If you booked your flight directly through Swiss Airlines, you can cancel your flight within 24 hours of booking without penalty and receive a full refund.\\n\\n2. **Cancellation 10 Hours Before Flight**: If you are looking to cancel your ticket 10 hours before the flight, you may be subject to cancellation fees or penalties, especially if you have a non-refundable ticket. The specific fees can vary based on the type of ticket you purchased.\\n\\n3. **Contacting Third Parties**: If you booked your flight through a third-party website or travel agent, you will need to contact them directly to cancel your flight.\\n\\n4. **Special Exemptions**: In cases of unforeseen circumstances (like medical emergencies), Swiss Airlines may offer special exemptions or accommodations.\\n\\nIt's always best to check the terms and conditions of your specific ticket for detailed information on cancellation policies and any associated fees.\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 226, 'prompt_tokens': 819, 'total_tokens': 1045, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_f85bea6784', 'finish_reason': 'stop', 'logprobs': None}, id='run-c83ed911-1952-4ece-b16f-41c9d515b6f3-0', usage_metadata={'input_tokens': 819, 'output_tokens': 226, 'total_tokens': 1045}),\n", + " HumanMessage(content='Right now Harris vs. Trump Presidential Debate is being boradcasted. I want the youtube link to this debate', id='4706cd31-61e5-4a67-bb17-c4eb3e17b0e1'),\n", + " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_1jy65jCBXDpiaiDrn9n93HBC', 'function': {'arguments': '{\"query\":\"Harris Trump Presidential Debate YouTube link\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 1075, 'total_tokens': 1100, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_f85bea6784', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-c25bda9e-c897-4e44-b7c6-94c9f32933e9-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'Harris Trump Presidential Debate YouTube link'}, 'id': 'call_1jy65jCBXDpiaiDrn9n93HBC', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1075, 'output_tokens': 25, 'total_tokens': 1100}),\n", + " ToolMessage(content='[{\"url\": \"https://www.pbs.org/newshour/politics/watch-live-harris-and-trump-debate-pbs-news-simulcast-of-abcs-2024-presidential-debate\", \"content\": \"WATCH: Harris and Trump debate \\\\u2014 PBS News simulcast of ABC\\\\u2019s 2024 Presidential Debate Vice President Kamala Harris and former President Donald Trump faced off Tuesday night for their first and possibly only debate before Election Day. The state of the race as they meet in Philadelphia is starkly different than it was just more than two months ago, when Trump debated President Joe Biden in a performance that accelerated calls for Biden to leave the race. WATCH: What to watch in the ABC Harris-Trump debate Watch PBS News\\\\u2019 special coverage here and the\\\\u00a0ABC Presidential Debate in the player above. This year\\\\u2019s presidential race is a genuine contest of ideas between Harris and Trump \\\\u2014 with clear differences on taxes, abortion, immigration, global alliances, climate change and democracy itself. LIVE FACT CHECK: Trump and Harris meet for presidential debate\"}, {\"url\": \"https://abcnews.go.com/Politics/watch-full-abc-news-presidential-debate/story?id=113470583\", \"content\": \"MORE: READ: Harris-Trump presidential debate transcript Democratic presidential nominee Vice President Kamala Harris shakes hands with former President Donald Trump, during a presidential debate ...\"}]', name='tavily_search_results_json', id='45c00f98-cf0e-4e5b-b19a-c3da9ec63434', tool_call_id='call_1jy65jCBXDpiaiDrn9n93HBC'),\n", + " AIMessage(content='You can watch the Harris vs. Trump Presidential Debate through the following links:\\n\\n1. [PBS News Simulcast of the Debate](https://www.pbs.org/newshour/politics/watch-live-harris-and-trump-debate-pbs-news-simulcast-of-abcs-2024-presidential-debate)\\n2. [ABC News Full Debate Coverage](https://abcnews.go.com/Politics/watch-full-abc-news-presidential-debate/story?id=113470583)\\n\\nFeel free to check them out!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 106, 'prompt_tokens': 1405, 'total_tokens': 1511, 'prompt_tokens_details': {'cached_tokens': 1024}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_f85bea6784', 'finish_reason': 'stop', 'logprobs': None}, id='run-c011a865-0f6e-47f3-aae6-f139aeba11ca-0', usage_metadata={'input_tokens': 1405, 'output_tokens': 106, 'total_tokens': 1511})]}" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "event" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "querymind (3.12.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", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/python_tip_automatic_docstring/automatic_docstring.ipynb b/Notebooks/python_tip_automatic_docstring/automatic_docstring.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d51c1bcfc40e3fa4eb8e5a67d02f977ca4657ee9 --- /dev/null +++ b/Notebooks/python_tip_automatic_docstring/automatic_docstring.ipynb @@ -0,0 +1,76 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old docstring:\n", + "Tool that can operate on any number of inputs.\n", + "\n", + "Updated docstring:\n", + "This function now queries a different database and returns updated results.\n" + ] + } + ], + "source": [ + "class YourClass:\n", + " @tool\n", + " def query_sqldb(self, query):\n", + " \"\"\"Query the Swiss Airline SQL Database and access all the company's information. Input should be a search query.\"\"\"\n", + " response = self.chain.invoke({\"question\": query})\n", + " return response\n", + "\n", + " def update_description(self, new_description):\n", + " \"\"\"Updates the description (docstring) of the query_sqldb function.\"\"\"\n", + " self.query_sqldb.__doc__ = new_description\n", + "\n", + "# Usage examples\n", + "your_object = YourClass()\n", + "print(\"Old docstring:\")\n", + "print(your_object.query_sqldb.__doc__)\n", + "\n", + "# Update the description\n", + "new_description = \"This function now queries a different database and returns updated results.\"\n", + "your_object.update_description(new_description)\n", + "\n", + "print(\"\\nUpdated docstring:\")\n", + "print(your_object.query_sqldb.__doc__)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "rag-sqlagent", + "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", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/test_groq_models/Llama.ipynb b/Notebooks/test_groq_models/Llama.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4b3271b7b223451fe3a4c899bf40e47725f563a3 --- /dev/null +++ b/Notebooks/test_groq_models/Llama.ipynb @@ -0,0 +1,178 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from dotenv import load_dotenv\n", + "import base64\n", + "from openai import OpenAI\n", + "load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['OPENAI_API_KEY'] = os.getenv(\"OPEN_AI_API_KEY\")\n", + "client = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Define the model name\n", + "model = \"gpt-4o\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Function to perform text-based inference\n", + "def text_inference(prompt:str)->str:\n", + " \"\"\"\n", + " Sends a text prompt to the AI model for inference and returns the model's response.\n", + "\n", + " Args:\n", + " prompt (str): The text prompt or question to be sent to the AI model.\n", + "\n", + " Returns:\n", + " str: The model's response based on the given text prompt.\n", + " \"\"\"\n", + " response = client.chat.completions.create(\n", + " model=model,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", + " {\"role\": \"user\", \"content\": prompt}\n", + " ]\n", + " )\n", + " return response.choices[0].message.content\n", + "\n", + "# Function to perform image-based inference\n", + "def image_inference(image_path:str, prompt:str)->str:\n", + " \"\"\"\n", + " Sends a text prompt and an image to the AI model for inference, returning the model's response.\n", + "\n", + " Args:\n", + " image_path (str): The file path to the image to be sent to the AI model.\n", + " prompt (str): The text prompt or question accompanying the image.\n", + "\n", + " Returns:\n", + " str: The model's response based on the given text prompt and image.\n", + " \"\"\"\n", + " with open(image_path, \"rb\") as image_file:\n", + " base64_image = base64.b64encode(image_file.read()).decode('utf-8')\n", + " \n", + " response = client.chat.completions.create(\n", + " model=model,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": [\n", + " {\"type\": \"text\", \"text\": prompt},\n", + " {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{base64_image}\"}}\n", + " ]\n", + " }\n", + " ]\n", + " )\n", + " return response.choices[0].message.content" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Model input: Only text**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello! I'm here and ready to help you. How can I assist you today?\n" + ] + } + ], + "source": [ + "# Example usage\n", + "text_prompt = \"\"\"Hello there. How are you today?\"\"\"\n", + "print(text_inference(text_prompt))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Model input: Text and Image**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The image features a stack of ice cubes with a translucent, green hue. Each cube is glistening with water droplets that enhance their icy appearance. Surrounding the cubes are fresh green leaves, adding a natural touch to the composition. The background is a soft gradient of green, creating a refreshing and cool atmosphere. The overall effect is vibrant and inviting, evoking a sense of chill and freshness.\n" + ] + } + ], + "source": [ + "image_path = \"image.png\"\n", + "image_prompt = \"You are an AI expert. Answer this question.\"\n", + "print(image_inference(image_path, image_prompt))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "huge-env", + "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", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/test_groq_models/Mixtral.ipynb b/Notebooks/test_groq_models/Mixtral.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..9d1e0ad50b23ca012649c7f0932fbbab6d794dd3 --- /dev/null +++ b/Notebooks/test_groq_models/Mixtral.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Test your GPT models before integrating them into the project to ensure they can be called successfully.**" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from dotenv import load_dotenv\n", + "import os\n", + "load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['OPENAI_API_KEY'] = os.getenv(\"OPEN_AI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from openai import OpenAI\n", + "client = OpenAI()\n", + "\n", + "response = client.chat.completions.create(\n", + " model=\"Mixtral 8x7B\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", + " {\"role\": \"user\", \"content\": \"Who won the world series in 2020?\"},\n", + " {\"role\": \"assistant\", \"content\": \"The Los Angeles Dodgers won the World Series in 2020.\"},\n", + " {\"role\": \"user\", \"content\": \"Where was it played?\"}\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The 2020 World Series was played at Globe Life Field in Arlington, Texas.'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response.choices[0].message.content" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "playaround", + "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", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Notebooks/test_groq_models/image.png b/Notebooks/test_groq_models/image.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf4bec8cbe663e07ba1ff2973cb12a1ddfa402e --- /dev/null +++ b/Notebooks/test_groq_models/image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5df88019acc85c44b9f08682b791f12c07c57c8fb6de7d6576e1258624907b6d +size 1717777 diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..375793ec03b594bc596a8d9170ce83473ff1a1e7 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ + +--- + +# AgentGraph: Intelligent SQL-agent Q&A and RAG System for Chatting with Multiple Databases + +This project demonstrates how to build an agentic system using Large Language Models (LLMs) that can interact with multiple databases and utilize various tools. It highlights the use of SQL agents to efficiently query large databases. The key frameworks used in this project include OpenAI, LangChain, LangGraph, LangSmith, and Gradio. The end product is an end-to-end chatbot, designed to perform these tasks, with LangSmith used to monitor the performance of the agents. + +--- + +## Video Explanation: +A detailed explanation of the project is available in the following YouTube video: + +Automating LLM Agents to Chat with Multiple/Large Databases (Combining RAG and SQL Agents): [Link](https://youtu.be/xsCedrNP9w8?si=v-3k-BoDky_1IRsg) + +--- + +## Requirements + +- **Operating System:** Linux or Windows (Tested on Windows 11 with Python 3.9.11) +- **OpenAI API Key:** Required for GPT functionality. +- **Tavily Credentials:** Required for search tools (Free from your Tavily profile). +- **LangChain Credentials:** Required for LangSmith (Free from your LangChain profile). +- **Dependencies:** The necessary libraries are provided in `requirements.txt` file. +--- + +## Installation and Execution + +To set up the project, follow these steps: + +1. Clone the repository: + ```bash + git clone + ``` +2. Install Python and create a virtual environment: + ```bash + python -m venv venv + ``` +3. Activate the virtual environment: + - On Windows: + ```bash + venv\Scripts\activate + ``` + - On Linux/macOS: + ```bash + source venv/bin/activate + ``` +4. Install the required dependencies: + ```bash + pip install -r requirements.txt + ``` +5. Download the travel sql database from this link and paste it into the `data` folder. + +6. Download the chinook SQL database from this link and paste it into the `data` folder. + +7. Prepare the `.env` file and add your `OPEN_AI_API_KEY`, `TAVILY_API_KEY`, and `LANGCHAIN_API_KEY`. + +8. Run `prepare_vector_db.py` module once to prepare both vector databases. + ```bash + python src\prepare_vector_db.py + ``` +9. Run the app: + ```bash + python src\app.py + ``` +Open the Gradio URL generated in the terminal and start chatting. + +*Sample questions are available in `sample_questions.txt`.* + +--- + +### Using Your Own Database + +To use your own data: +1. Place your data in the `data` folder. +2. Update the configurations in `tools_config.yml`. +3. Load the configurations in `src\agent_graph\load_tools_config.py`. + +For unstructured data using Retrieval-Augmented Generation (RAG): +1. Run the following command with your data directory's configuration: + ```bash + python src\prepare_vector_db.py + ``` + +All configurations are managed through YAML files in the `configs` folder, loaded by `src\chatbot\load_config.py` and `src\agent_graph\load_tools_config.py`. These modules are used for a clean distribution of configurations throughout the project. + +Once your databases are ready, you can either connect the current agents to the databases or create new agents. More details can be found in the accompanying YouTube video. + +--- + +## Project Schemas + +### High-level overview + +
+ high-level +
+ +### Detailed Schema + +
+ detailed_schema +
+ +### Graph Schema + +
+ graph_image +
+ +### SQL-agent for large databases strategies + +
+ large_db_strategy +
+ +--- + +## Chatbot User Interface + +
+ ChatBot UI +
+ +--- + +## LangSmith Monitoring System + +
+ langsmith +
+ +--- + +## Databases Used + +- **Travel SQL Database:** [Kaggle Link](https://www.kaggle.com/code/mpwolke/airlines-sqlite) +- **Chinook SQL Database:** [Sample Database](https://database.guide/2-sample-databases-sqlite/) +- **stories VectorDB** +- **Airline Policy FAQ VectorDB** +--- + +## Key Frameworks and Libraries + +- **LangChain:** [Introduction](https://python.langchain.com/docs/get_started/introduction) +- **LangGraph** +- **LangSmith** +- **Gradio:** [Documentation](https://www.gradio.app/docs/interface) +- **OpenAI:** [Developer Quickstart](https://platform.openai.com/docs/quickstart?context=python) +- **Tavily Search** +--- \ No newline at end of file diff --git a/configs/project_config.yml b/configs/project_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..0bffb546f58cc0fff93864e6a5b4b4fe4a5a1595 --- /dev/null +++ b/configs/project_config.yml @@ -0,0 +1,6 @@ +langsmith: + tracing: "true" + project_name: "RAG & SQL Agents" + +memory: + directory: memory \ No newline at end of file diff --git a/configs/tools_config.yml b/configs/tools_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..047327045d6e39aea74fffdd937f6602f0f9d79f --- /dev/null +++ b/configs/tools_config.yml @@ -0,0 +1,46 @@ +primary_agent: + llm: openai/gpt-oss-120b + llm_temperature: 0.0 + +swiss_airline_policy_rag: + unstructured_docs: "data/unstructured_docs/swiss_airline_policy" + vectordb: "data/airline_policy_vectordb" + collection_name: rag-chroma + llm: openai/gpt-oss-120b + llm_temperature: 0.0 + embedding_model: all-MiniLM-L6-v2 + chunk_size: 500 + chunk_overlap: 100 + k: 2 + +stories_rag: + unstructured_docs: "data/unstructured_docs/stories" + vectordb: "data/stories_vectordb" + collection_name: stories-rag-chroma + llm: openai/gpt-oss-120b + llm_temperature: 0.0 + embedding_model: all-MiniLM-L6-v2 + chunk_size: 500 + chunk_overlap: 100 + k: 2 + +travel_sqlagent_configs: + travel_sqldb_dir: "data/travel.sqlite" + llm: "openai/gpt-oss-120b" + llm_temperature: 0.0 + +chinook_sqlagent_configs: + chinook_sqldb_dir: "data/Chinook.db" + llm: "openai/gpt-oss-120b" + llm_temperature: 0.0 + +langsmith: + tracing: "true" + project_name: "rag_sqlagent_project" + +tavily_search_api: + tavily_search_max_results: 2 + +graph_configs: + thread_id: 1 # This can be adjusted to assign a unique value for each user session, so it's easier to access data later on. + \ No newline at end of file diff --git a/data/Chinook.db b/data/Chinook.db new file mode 100644 index 0000000000000000000000000000000000000000..2d6686c4fde9ae525bd66094cb00335f1b213158 --- /dev/null +++ b/data/Chinook.db @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bd892696e956e57c8cfabd90fcf1675d583ba8e961e968ec53c8da7c08c2669 +size 1007616 diff --git a/data/airline_policy_vectordb/chroma.sqlite3 b/data/airline_policy_vectordb/chroma.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..bc12ebba81ab9ce7e234443367f30e13e0a90054 --- /dev/null +++ b/data/airline_policy_vectordb/chroma.sqlite3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:655d90cbf27df7db4b1eb1351dc3f02d4a317133b367ad2f612d70352f6c1a9f +size 548864 diff --git a/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/data_level0.bin b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/data_level0.bin new file mode 100644 index 0000000000000000000000000000000000000000..786c596298c327df7c5fb6958924c481ecd11f36 --- /dev/null +++ b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/data_level0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3c9fd302f000d7790aa403c2d0d8fec363fe46f30b07d53020b6e33b22435a9 +size 1676000 diff --git a/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/header.bin b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/header.bin new file mode 100644 index 0000000000000000000000000000000000000000..ae84e682423ff4214c2e9df782b8799815882036 --- /dev/null +++ b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/header.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e87a1dc8bcae6f2c4bea6d5dd5005454d4dace8637dae29bff3c037ea771411e +size 100 diff --git a/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/length.bin b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/length.bin new file mode 100644 index 0000000000000000000000000000000000000000..e3952f49a1c354490a5ff1be87dd1921938f4894 --- /dev/null +++ b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/length.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea0d08e65dd9d026a96c9353a211aab455c26de1c3844d6b03bafee746fd2396 +size 4000 diff --git a/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/link_lists.bin b/data/airline_policy_vectordb/f2b436bc-82cd-4d2c-8bab-689ae4da5759/link_lists.bin new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/data_level0.bin b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/data_level0.bin new file mode 100644 index 0000000000000000000000000000000000000000..786c596298c327df7c5fb6958924c481ecd11f36 --- /dev/null +++ b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/data_level0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3c9fd302f000d7790aa403c2d0d8fec363fe46f30b07d53020b6e33b22435a9 +size 1676000 diff --git a/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/header.bin b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/header.bin new file mode 100644 index 0000000000000000000000000000000000000000..ae84e682423ff4214c2e9df782b8799815882036 --- /dev/null +++ b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/header.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e87a1dc8bcae6f2c4bea6d5dd5005454d4dace8637dae29bff3c037ea771411e +size 100 diff --git a/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/length.bin b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/length.bin new file mode 100644 index 0000000000000000000000000000000000000000..0ee4e0014d8763f50efed8116acc74459ce10b92 --- /dev/null +++ b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/length.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17ddd8ba7342a60f29345c3078eb4a12057fa3f88dd31ae3690fa4bef4155140 +size 4000 diff --git a/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/link_lists.bin b/data/stories_vectordb/483f4fb1-0512-4ef0-9c4e-890073f4fe17/link_lists.bin new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/data/stories_vectordb/chroma.sqlite3 b/data/stories_vectordb/chroma.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..43112ee7d1022183a3813a5e5afa3eaea2e03540 --- /dev/null +++ b/data/stories_vectordb/chroma.sqlite3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e76231a9270897228d62b369b8edd4f0bf325078a0b6bca22052642edf52d364 +size 258048 diff --git a/data/travel.sqlite b/data/travel.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/data/unstructured_docs/stories/stories.pdf b/data/unstructured_docs/stories/stories.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d53719d65a3d1d229837f193ae1c8c5674dcd16f --- /dev/null +++ b/data/unstructured_docs/stories/stories.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9da1a7de23d1c52a5ea2c4243e71ba616fe21c602496fd78fc868957d6a69e81 +size 427362 diff --git a/data/unstructured_docs/swiss_airline_policy/swiss_faq.pdf b/data/unstructured_docs/swiss_airline_policy/swiss_faq.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3d8d6b7d643543c2a377df04d729c9dd9a09cc06 --- /dev/null +++ b/data/unstructured_docs/swiss_airline_policy/swiss_faq.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ecf417e39093e931f75f851bdc7ae9c6410af3463bd5878b67e2a61ad075fad +size 67566 diff --git a/images/AI_RT.png b/images/AI_RT.png new file mode 100644 index 0000000000000000000000000000000000000000..7d100fc00c72f461cc4fc5213264a075ee92f3b9 --- /dev/null +++ b/images/AI_RT.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f517e046a708e614457ad9ae1421005c2b0bb4be2ea90450ca5ddaf5e8c19976 +size 1745741 diff --git a/images/UI.png b/images/UI.png new file mode 100644 index 0000000000000000000000000000000000000000..33a19484e180ad097ad06ec7e0d9deca9d362202 --- /dev/null +++ b/images/UI.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1428e0f8cb7bc461bcd8a1e60233b17b479929ab933a24e4cb074393422bbe7e +size 106460 diff --git a/images/chat_icon.png b/images/chat_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ac902b1f117d4b1300b08d5d25a64b7ad02a4ebd --- /dev/null +++ b/images/chat_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c09bee276aa1412bd9d24ff1347b319835c63c8d887282ca59aec9307cd12196 +size 2710 diff --git a/images/detailed_schema.png b/images/detailed_schema.png new file mode 100644 index 0000000000000000000000000000000000000000..6be0f69a5f2860286aeee05b5df519333576b8bf --- /dev/null +++ b/images/detailed_schema.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c093f657e4bb44abc3a055a2b3e56b642a57d67d53fd4d4c8df8384575615360 +size 1705449 diff --git a/images/graph_image.png b/images/graph_image.png new file mode 100644 index 0000000000000000000000000000000000000000..047ba9608bf01a35d8123955dbc82c6910d3cf46 --- /dev/null +++ b/images/graph_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd7dd27e4eb04c8307e83f0e039ad8551c4b1abee3ff61fc77ca089dfcc20f13 +size 63760 diff --git a/images/high-level.png b/images/high-level.png new file mode 100644 index 0000000000000000000000000000000000000000..2d9522ac7701d091268d788ae70328058a8649f4 --- /dev/null +++ b/images/high-level.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b11a56801897ea6e169559afdf2473e603690e2da98cbd1aae2dd3fb7a0fbd30 +size 768595 diff --git a/images/langsmith_Screenshot.png b/images/langsmith_Screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..085d14e5aafc963d3d6ca2a255f7ae8f180185d4 --- /dev/null +++ b/images/langsmith_Screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ca515ea86aaaa4bb342100ef640754f5e75def6984ea7eeb30ddd38e32f2e0b +size 289055 diff --git a/images/large_db_strategy.png b/images/large_db_strategy.png new file mode 100644 index 0000000000000000000000000000000000000000..409214e635d99d4f03392b8e46fcb77f3b22e5d5 --- /dev/null +++ b/images/large_db_strategy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb0d6b4604d6936ea8acb418e8e1105e581af74951185bf3878358cc1bdbbc98 +size 367344 diff --git a/images/question2_answer_Screenshot .png b/images/question2_answer_Screenshot .png new file mode 100644 index 0000000000000000000000000000000000000000..9655572105b2ef263c2bfebdfa055d955b435c2c --- /dev/null +++ b/images/question2_answer_Screenshot .png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52ea73e20cd3614fdd74835622933f14fe1a888addb7724230751f55c47507b3 +size 85749 diff --git a/images/question3_answer_Screenshot.png b/images/question3_answer_Screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..c728730f300784c7ff93e3642c0edafb83a2ad86 --- /dev/null +++ b/images/question3_answer_Screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84604c22433a03ee0b0130aac8d75ffd6493c7623a6fd53b6485fac9dbd6e61c +size 87314 diff --git a/images/question4_answer_Screenshot.png b/images/question4_answer_Screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..88d4848774bbf38e04ad938d9a3c1cdc3c142a90 --- /dev/null +++ b/images/question4_answer_Screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37289cf81958d7b5b333f9f741ad4ed38d165e96fd8166164e21675cdde1edc3 +size 77859 diff --git a/images/question_answer_screenshot.png b/images/question_answer_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9e1a48c8e1252dcf25eed0d86e23552311818853 --- /dev/null +++ b/images/question_answer_screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a375f96f10fcf51c391a5e14e5cd7211eba2c1b654938c361f599b712de491f +size 224045 diff --git a/memory/2024-09-16.csv b/memory/2024-09-16.csv new file mode 100644 index 0000000000000000000000000000000000000000..f5b4fe28f598d7a67f2b5ca3cdffb6b0b384de7b --- /dev/null +++ b/memory/2024-09-16.csv @@ -0,0 +1,16 @@ +thread_id,timestamp,user_query,response +1,16:41:38,hello,Hello! How can I assist you today? +1,16:42:15,hi there,Hello! How can I assist you today? +1,16:42:25,"In the stories, who is fred? +","In the stories, Fred is a small red fish who lives in a vibrant coral reef in the ocean. His scales sparkle like rubies, and he is known for his curiosity and adventurous spirit. Fred comes from a loving family, with a wise mother named Coraline and a daring father named Marlin, who shares tales of his explorations. + +Fred's best friend is a playful dolphin named Delphi, and together they embark on an exciting adventure after discovering a treasure map in a bottle. Their journey takes them through various underwater wonders and challenges, including evading a hungry octopus and navigating a storm. Ultimately, they find a sunken pirate ship filled with treasure, but Fred realizes that the true treasure lies in the experiences and memories created during their adventure. + +Fred returns home to share his stories with his family, having grown and learned that home is defined by the love that awaits him there." +1,16:42:48,"Give me the link of Trump and Harris debate on youtube +","Here are two links to the Trump and Harris debate on YouTube: + +1. [Watch live coverage of the presidential debate](https://m.youtube.com/watch?v=SGRydccYp0c) hosted by ABC News. +2. [Highlights from the first 2024 presidential debate](https://m.youtube.com/watch?v=BVlI0vcf5T8) in Philadelphia. + +Feel free to check them out!" diff --git a/memory/2024-09-17.csv b/memory/2024-09-17.csv new file mode 100644 index 0000000000000000000000000000000000000000..66b4b5548001045b93ae1f4844b2ad61b696cbdb --- /dev/null +++ b/memory/2024-09-17.csv @@ -0,0 +1,16 @@ +thread_id,timestamp,user_query,response +1,20:27:35,"In chinook DB, list the total sales per country. Which country's customers spent the most?","Here are the total sales per country from the Chinook database: + +1. **USA**: $523.06 +2. **Canada**: $303.96 +3. **France**: $195.10 +4. **Brazil**: $190.10 +5. **Germany**: $156.48 + +The country whose customers spent the most is the **USA** with total sales of **$523.06**." +1,20:27:54,What are all the genres of Alanis Morisette songs?,The genre of Alanis Morissette's songs in the Chinook database is **Rock**. +1,20:28:02,"In the stories, who is fred?","Fred is a small red fish who lives in the vast ocean. His scales sparkle like rubies, and he is known for his curiosity and adventurous spirit. Fred lives in a cozy coral reef with his loving family, including his wise mother, Coraline, and his daring father, Marlin, who shares tales of his explorations. + +Fred's best friend is a playful dolphin named Delphi, and together they embark on exciting adventures. One day, they discover a treasure map in a bottle, leading them on a journey filled with wonders and dangers. They encounter various marine life, swim through kelp forests, and even face a storm. + +Ultimately, they find a sunken pirate ship filled with treasure, but Fred realizes that the true treasure lies in the experiences and memories created during their adventure. He returns home to share his stories, having grown and learned that home is defined by the love that awaits him there. Fred lives happily ever after, always ready for the next adventure." diff --git a/memory/2026-05-14.csv b/memory/2026-05-14.csv new file mode 100644 index 0000000000000000000000000000000000000000..c3270475d8f202b6652687a465a70eea7f5d92e5 --- /dev/null +++ b/memory/2026-05-14.csv @@ -0,0 +1,270 @@ +thread_id,timestamp,user_query,response +1,12:36:44,"hello +",I'm here to help with any questions or tasks you may have. What would you like to know or discuss? +1,12:52:42,"In the stories, who is fred?","In the collection of stories, **Fred is the small red fish who serves as the main protagonist**. He lives in a coral reef with his family—his mother Coraline and his father Marlin—and his best friend is a playful dolphin named Delphi. Curious and adventurous, Fred sets out on a quest after discovering a treasure map, facing various oceanic challenges and ultimately learning that the true treasure is the journey itself and the love of his family and friends." +1,12:54:12,"Based on the airplane policy, can I cancel my ticket 10 hours before the flight?","**Short answer:** +No – the 24‑hour “no‑penalty” rule only applies to cancellations made **within 24 hours of the time you booked the ticket**, not to cancellations made a few hours before the scheduled departure. If you try to cancel 10 hours before the flight, the airline will treat it as a “regular” cancellation and the outcome will depend on the fare rules of the ticket you purchased. + +**What the Swiss Airlines policy says** + +| Situation | What the policy allows | Typical result | +|-----------|-----------------------|----------------| +| **Cancel ≤ 24 h after booking** (any fare type) | Full refund, no fee (only if you booked directly with Swiss Airlines) | You get the ticket price back. | +| **Cancel after the 24‑hour window** | Cancellation is possible, but fees or partial refunds may apply **depending on the fare class** (refundable vs. non‑refundable) | • Refundable tickets – usually a small admin fee, then a refund of the fare.
• Non‑refundable tickets – generally no refund, though you may receive a credit/voucher for future travel or a partial refund if the airline grants a special exemption (e.g., medical emergency, natural disaster). | +| **Booking made through a travel agent/third‑party site** | You must follow that seller’s own cancellation rules; Swiss Airlines’ 24‑hour rule does not apply. | Same fare‑type rules, but you’ll coordinate the cancellation with the agent. | + +**So, 10 hours before departure:** + +1. **Check your ticket type** – Look at the fare conditions in your e‑ticket or in the “Manage Booking” section on Swiss Airlines’ website. + - If it’s a **refundable fare**, you can cancel, but a cancellation fee (often a fixed amount or a percentage of the fare) will be deducted before the refund is issued. + - If it’s a **non‑refundable fare**, you will not receive a cash refund. Swiss may offer you a travel credit/voucher for a future flight, or you might be able to change the flight for a change‑fee plus any fare difference. + +2. **Contact Swiss Airlines (or your travel agent) promptly** – Because the flight is so close, the airline may charge a higher “last‑minute” fee, and seat re‑allocation may be limited. + +3. **Possible exceptions** – If you have a documented medical emergency, a death in the family, or a government‑mandated travel restriction, Swiss Airlines can sometimes waive fees or provide a full refund even for non‑refundable tickets. You’ll need to provide supporting documentation. + +**What you should do right now** + +1. Log in to **Swiss Airlines → Manage Your Booking** and locate the “Cancellation” or “Change flight” option. +2. Review the fare rules displayed for your specific reservation. +3. If you see a cancellation fee listed, decide whether a credit/voucher is acceptable to you. +4. If you booked through a third‑party site, call the agency’s customer‑service line (the phone number is usually on your confirmation email). +5. If you think you qualify for a special exemption (medical, etc.), gather the required documents (doctor’s note, death certificate, etc.) and submit them through the airline’s “Special Circumstances” portal or via the customer‑service phone line. + +**Bottom line:** The 24‑hour, no‑penalty window does **not** cover a cancellation made 10 hours before departure. You can still cancel, but you’ll likely incur a fee or receive only a travel credit unless you hold a refundable ticket or qualify for an exemption." +1,12:56:06,is there is any flight available on the travel database that goes from CDG to BSL?,"I checked the airline’s travel database for any scheduled services that depart from **Paris‑Charles de Gaulle (CDG)** and arrive at **Basel‑Mulhouse‑Freiburg (BSL)**, and **there are currently no flights stored for that route**. If you need to travel between those cities, you’ll have to consider alternative options such as: + +1. **Connecting flights** – e.g., CDG → Zurich (ZRH) → BSL (a short ground transfer) or CDG → Geneva (GVA) → BSL. +2. **Rail or bus** – Basel is well‑served by high‑speed trains (TGV Lyria) from Paris, which can be a fast and convenient alternative. +3. **Nearby airports** – Fly into Zurich (ZRH) or Geneva (GVA) and then take a train or shuttle to Basel (about 1 hour by train from either city). + +Let me know if you’d like me to look up connecting flight options, train schedules, or help with booking any of those alternatives!" +1,13:27:07,"In the chinook DB, list the total sales per country. which country's customer spent the most?","**Total Sales per Country (Chinook DB)** + +| Rank | Country | Total Sales (USD) | +|------|---------|-------------------| +| 1 | **USA** | **$523.06** | +| 2 | Canada | $303.96 | +| 3 | France | $195.10 | +| 4 | Brazil | $190.10 | +| 5 | Germany | $156.48 | +| … | (other countries) | (lower totals) | + +**Answer:** +The United States (USA) is the country whose customers have spent the most, with total sales of **$523.06**." +1,13:28:17,what are all the genres of Alanis Morisette songs?,"In the Chinook sample database, every track credited to **Alanis Morissette** falls under a single genre: + +**Genre:** **Rock** + +The query shows that “Rock” is the only distinct genre associated with her songs in this dataset. If you have a different database or additional tracks, the list might expand, but within the Chinook DB, **Rock** is the sole genre for Alanis Morissette’s recordings." +1,13:28:55,give me a couble of her albums name?,"In the **Chinook** sample database the only album listed for **Alanis Morissette** is: + +1. **Jagged Little Pill** + +That’s the sole entry for her in this particular dataset. (In the real world, Alanis Morissette has released several other albums—*Supposed Former Infatuation Junkie*, *Under Rug Swept*, *Flavors of the Weak Heart*, etc.—but those aren’t present in the Chinook DB.)" +1,13:29:48,in need more albums names,"Here are **several of Alan Alanis Morissette’s studio (and notable) albums** released over the course of her career: + +| Year | Album (studio / major release) | Notable notes | +|------|--------------------------------|----------------| +| **1991** | **Alanis** | Debut pop‑rock album released only in Canada. | +| **1992** | **Now Is the Time** | Follow‑up to her debut, also a Canada‑only release. | +| **1995** | **Jagged Little Pill** | Break‑through, 10× Platinum worldwide; contains “Ironic,” “You Oughta Know,” etc. | +| **1998** | **Supposed Former Infatuation Junkie** | More experimental, Grammy‑winning “Thank U.” | +| **2002** | **Under Rug Swept** | Self‑produced; singles “Hands Clean,” “Precious Illusions.” | +| **2004** | **Jagged Little Pill Acoustic** | Stripped‑down acoustic re‑recording of the 1995 classic. | +| **2005** | **The Collection** (greatest‑hits compilation) | 18‑track best‑of set. | +| **2008** | **Flavors of Entanglement** | Return to a more pop‑rock sound; includes “Underneath.” | +| **2012** | **Havoc and Bright Lights** | Produced with Guy Bérard; singles “Guardian,” “Lens.” | +| **2020** | **Such Pretty Forks in the Road** | First album of original material in eight years. | +| **2023** | **The Alanis Morissette Collection (2023 Deluxe)** | Expanded anthology spanning her whole catalog (digital/streaming release). | + +**Quick summary** + +- **Core studio albums** (the ones most fans cite) are: *Jagged Little Pill*, *Supposed Former Infatuation Junkie*, *Under Rug Swept*, *Flavors of Entanglement*, *Havoc and Bright Lights*, and *Such Pretty Forks in the Road*. +- Early Canadian releases (*Alanis* and *Now Is the Time*) are often omitted from U.S./global discographies but are part of her official catalog. +- She also has notable non‑studio releases such as the acoustic version of *Jagged Little Pill* and the compilation *The Collection*. + +If you need details on a specific album (track list, chart performance, etc.), just let me know!" +1,13:30:55,give me the links to some websites for langgraph toturial,"Here are a few solid, up‑to‑date tutorials and walkthroughs that show how to get started with **LangGraph** (the graph‑based workflow library built on LangChain). Each link includes code samples, explanations of key concepts (state, nodes, edges, conditional routing, tool‑calling, etc.), and instructions for running the examples locally. + +| # | Title & Source | Type | What You’ll Learn | Direct Link | +|---|----------------|------|-------------------|-------------| +| 1 | **“Building AI Workflows with LangGraph” – Codecademy** | Article / Code‑first tutorial | • Installing LangGraph
• Defining a `StateGraph` and TypedDict state
• Adding nodes, edges, and conditional routing
• Running a simple chatbot workflow | | +| 2 | **“Getting Started with LangGraph: A Beginner’s Guide to Building Intelligent Workflows” – Medium (Ashutosh Sharma)** | Medium post | • Message‑state management with reducers (`add_messages`)
• Tool‑calling integration (Tavily search, custom functions)
• Conditional edges (`tools_condition`)
• Full end‑to‑end example (LLM → ToolNode → End) | | +| 3 | **LangGraph Official Docs – “Quickstart”** | Official documentation | • Step‑by‑step setup (pip install, basic graph)
• Explanation of `StateGraph`, `START`, `END`
• Example of branching logic with `add_conditional_edges` | | +| 4 | **YouTube – “LangGraph Tutorial – Build Conditional AI Workflows” (Tech With Tim)** | Video tutorial | • Live coding of a conditional workflow
• Visual walkthrough of graph construction
• Debugging tips and best practices | | +| 5 | **GitHub – LangGraph Examples Repository** | Code repository | • Ready‑to‑run notebooks for:
 • Simple state graph
 • Tool‑calling with OpenAI & Tavily
 • Multi‑branch decision trees
• Instructions for cloning and running locally | | +| 6 | **“Advanced LangGraph Patterns” – LangChain Blog** | Blog post | • Parallel execution of nodes
• Looping / recursion in graphs
• Persisting state to a database (SQLite, Redis)
• Scaling with LangGraph + LangServe | | + +### Quick “Hello‑World” Snippet (so you can test it right away) + +```python +# Install first +# pip install ""langgraph[all]"" # includes LangChain, OpenAI, etc. + +from typing import TypedDict, Annotated +from langchain_core.messages import AnyMessage +from langgraph.graph import StateGraph, START, END +from langgraph.graph.message import add_messages + +# 1️⃣ Define the shared state +class ChatState(TypedDict): + messages: Annotated[list[AnyMessage], add_messages] # list of chat messages + +# 2️⃣ Node functions +def greet(state: ChatState): + return {""messages"": [""👋 Hi! How can I help you today?""]} + +def echo(state: ChatState): + # Grab the last user message (if any) and echo it back + last = state[""messages""][-1] if state[""messages""] else ""..."" + return {""messages"": [f""🔁 You said: {last}""]} + +# 3️⃣ Build the graph +graph = StateGraph(ChatState) +graph.add_node(""greet"", greet) +graph.add_node(""echo"", echo) + +# Define the flow: start → greet → echo → end +graph.add_edge(START, ""greet"") +graph.add_edge(""greet"", ""echo"") +graph.add_edge(""echo"", END) + +app = graph.compile() + +# 4️⃣ Run it +result = app.invoke({""messages"": []}) +print(result[""messages""]) +# → ['👋 Hi! How can I help you today?', '🔁 You said: ...'] +``` + +Feel free to explore any of the links above for deeper dives, and let me know if you’d like a more specific example (e.g., integrating a web‑search tool, building a multi‑branch decision graph, or deploying with LangServe). Happy graph‑building!" +1,13:37:50,"in the stories, who is fred?","**Fred** is a character from one of the fictional stories in the system’s story collection. + +- **Who he is:** Fred is a **small red fish** who lives in a coral reef. +- **Family:** His mother is **Coraline** (wise and nurturing) and his father is **Marlin** (a daring explorer). +- **Friend:** His best friend is **Delphi**, a playful dolphin. +- **Role in the story:** Fred is the adventurous protagonist. He discovers a treasure map, sets out on a daring journey across the ocean with Delphi, faces various challenges (kelp forests, a hungry octopus, a storm, bioluminescent jellyfish), finds a sunken pirate‑ship treasure, and ultimately returns home wiser and celebrated by his family. + +So, in the story collection, **Fred is the red‑scaled fish whose curiosity drives the tale of exploration and treasure‑hunting.**" +1,13:41:02,"in the stories, who is fred?","In the collection of stories, **Fred** is the main character—a small, bright‑red fish who lives in a coral reef. He’s curious, adventurous, and eager to explore the ocean beyond his home. Accompanied by his best friend Delphi the dolphin, Fred embarks on a treasure‑hunting quest that takes him through kelp forests, bioluminescent jelly‑fish gardens, and a sunken pirate ship. By the end of his journey he learns that the true treasure is the experiences and friendships he gains, and he returns home a wiser, more confident fish." +1,13:50:13,"in the stories, who is fred?","In the collection of stories, **Fred** is the main character—a small, bright‑red fish who lives in a coral reef. He’s curious, adventurous, and eager to explore the ocean beyond his home. Accompanied by his best friend Delphi the dolphin, Fred embarks on a treasure‑hunting quest that takes him through kelp forests, bioluminescent jelly‑fish gardens, and a sunken pirate ship. By the end of his journey he learns that the true treasure is the experiences and friendships he gains, and he returns home a wiser, more confident fish." +1,13:57:18,give me the links to some websites for langgraph toturial,"Here are a few solid, up‑to‑date tutorials that walk through building workflows with **LangGraph** (the graph‑based orchestration library for LangChain). All of the links are free to view and include code samples you can run locally. + +| # | Title & Source | Type of Content | Key Topics Covered | Direct Link | +|---|----------------|----------------|--------------------|-------------| +| 1 | **“Building AI Workflows with LangGraph” – Codecademy** | Written tutorial + runnable code snippets | Installing LangGraph, defining `StateGraph`, creating nodes, conditional edges, running a simple chatbot workflow | | +| 2 | **“Getting Started with LangGraph: A Beginner’s Guide to Intelligent Workflows” – Medium (Ashutosh Sharma)** | Blog post with full example | `MessageState` with reducers, tool‑calling integration, `ToolNode`, conditional routing, using Tavily search for live results | | +| 3 | **LangGraph Documentation – Official LangChain Site** | Official docs (interactive) | Core concepts (`StateGraph`, `Graph`, `add_messages` reducer), advanced patterns (parallel branches, looping, checkpointing), deployment tips | | +| 4 | **YouTube – “LangGraph Crash Course” by **DataTalksClub** | Video walkthrough (≈20 min) | Installing, building a graph with LLM and tool nodes, visualizing the graph, debugging state | | +| 5 | **GitHub – LangGraph Examples Repository** | Code repository | Ready‑to‑run notebooks: simple state graph, tool‑calling, multi‑agent orchestration, streaming responses | | +| 6 | **“Conditional Logic in LangGraph” – Towards Data Science** | Article + code | Using `add_conditional_edges`, branching based on LLM output, handling fallback paths | | +| 7 | **“Deploying a LangGraph Workflow on AWS Lambda” – Dev.to** | Blog post | Packaging dependencies, creating a Lambda layer, invoking the graph via API Gateway | | + +### Quick “Hello‑World” Snapshot (from the Codecademy tutorial) + +```python +from typing import TypedDict, Annotated +from langchain_core.messages import AnyMessage +from langgraph.graph import StateGraph, START, END +from langgraph.graph.message import add_messages + +# 1️⃣ Define the shared state +class ChatState(TypedDict): + messages: Annotated[list[AnyMessage], add_messages] # automatically appends + +# 2️⃣ Node functions +def welcome(state: ChatState): + return {""messages"": [""👋 Welcome! How can I help you today?""]} + +def echo(state: ChatState): + # Echo the last user message back + last = state[""messages""][-1] + return {""messages"": [f""🔁 You said: {last}""]} + +# 3️⃣ Build the graph +graph = StateGraph(ChatState) +graph.add_node(""welcome"", welcome) +graph.add_node(""echo"", echo) + +# Define the flow +graph.add_edge(START, ""welcome"") +graph.add_edge(""welcome"", ""echo"") +graph.add_edge(""echo"", END) + +# 4️⃣ Compile & run +app = graph.compile() +result = app.invoke({""messages"": []}) +print(result[""messages""]) +``` + +Running the snippet prints a welcome message followed by an echo of the user’s input—showing how easy it is to stitch together stateful steps with LangGraph. + +Feel free to explore any of the resources above; they each take a slightly different angle (basic intro, tool integration, deployment, visual learning) so you can pick the style that fits you best. Happy graph building!" +1,13:59:10,"In the chinook DB, list the total sales per country. which country's customer spent the most?","**Total sales per country (Chinook sample database)** + +| Rank | Country | Total sales (USD) | +|------|-------------------|-------------------| +| 1 | USA | **$523.06** | +| 2 | Canada | $303.96 | +| 3 | France | $195.10 | +| 4 | Brazil | $190.10 | +| 5 | Germany | $156.48 | +| 6 | United Kingdom | $112.86 | +| 7 | Czech Republic | $90.24 | +| 8 | Portugal | $77.24 | +| 9 | India | $75.26 | +| 10 | Chile | $46.62 | +| 11 | Ireland | $45.62 | +| 12 | Hungary | $45.62 | +| 13 | Austria | $42.62 | +| 14 | Finland | $41.62 | +| 15 | Netherlands | $40.62 | +| 16 | Norway | $39.62 | +| 17 | Sweden | $38.62 | +| 18 | Spain | $37.62 | +| 19 | Poland | $37.62 | +| 20 | Italy | $37.62 | +| 21 | Denmark | $37.62 | +| 22 | Belgium | $37.62 | +| 23 | Australia | $37.62 | +| 24 | Argentina | $37.62 | + +### Which country’s customers spent the most? +**The United States (USA) leads by a wide margin, with total sales of $523.06.** + +*All figures are taken directly from the `Invoice` table of the Chinook sample database (`SUM(Total)` grouped by `BillingCountry`).*" +1,14:00:38,what are all the genres of Alanis Morisette songs?,"In the Chinook sample database, **all of Alan Alanis Morissette’s tracks are classified under a single genre**: + +- **Rock** + +So the only genre associated with Alanis Morissette songs in this dataset is **Rock**." +1,14:02:19,give me a couble of her albums name?,"In the Chinook sample database, the only Alanis Morissette album listed is: + +- **Jagged Little Pill** + +(That’s the sole album entry for her in this particular dataset.)" +1,17:09:44,give me the links to some websites for langgraph toturial,"Here are several high‑quality, freely‑available tutorials and walkthroughs that cover LangGraph (the workflow‑oriented library built on top of LangChain). They range from beginner‑friendly introductions to more advanced, production‑ready examples. + +| # | Title / Description | Type | Link | +|---|----------------------|------|------| +| 1 | **Getting Started with LangGraph – A Beginner’s Guide** – step‑by‑step walkthrough that shows how to define a state, add nodes, and run a simple graph. | Blog post | | +| 2 | **Complete Guide to LangGraph (2026 Edition)** – comprehensive tutorial covering state definitions, conditional edges, tool‑calling, streaming, and checkpointing. | LinkedIn article | | +| 3 | **LangGraph Documentation – Official Reference** – the canonical source for API reference, installation, and example notebooks. | Docs site | | +| 4 | **LangGraph Quickstart Notebook** – an interactive Jupyter notebook (hosted on GitHub) that you can run locally or in Google Colab to see a full end‑to‑end graph with tool calling. | GitHub/Colab | | +| 5 | **Building Conversational Agents with LangGraph** – a video tutorial (YouTube) that walks through building a chatbot that can call external APIs (e.g., Tavily search) using LangGraph’s `ToolNode`. | YouTube video | | +| 6 | **LangGraph + LangChain: Real‑Time Streaming & Memory** – blog post that demonstrates how to use `MemorySaver`, streaming responses, and persistent thread IDs. | Blog post | | +| 7 | **LangGraph Advanced Patterns – Conditional Routing & Loops** – a deep‑dive article that explains how to create conditional edges, loops, and sub‑graphs for complex workflows. | Medium article | | +| 8 | **LangGraph Example: Multi‑Tool Agent with Tavily Search** – a concise example that integrates the Tavily search tool, showing how to set up tool calling and handle tool results. | GitHub repo | | +| 9 | **LangGraph Cheat Sheet** – a printable PDF summarizing the most common classes, decorators, and patterns (great for quick reference). | PDF | | + +### How to use these resources + +1. **Start with the official docs** (link 3) to get the installation command and basic concepts. +2. **Follow the “Getting Started” blog post** (link 1) for a hands‑on example you can run in a notebook. +3. **Experiment with the Quickstart notebook** (link 4) or open it directly in Google Colab for an interactive experience. +4. **If you need tool‑calling** (e.g., web search, database queries), check out the Tavily example (link 8) and the YouTube video (link 5) that demonstrates the full flow. +5. **For production‑grade features** like persistence, streaming, and checkpointing, read the streaming & memory article (link 6) and the advanced patterns guide (link 7). +6. **Keep the cheat sheet** (link 9) handy while you code. + +These links should give you a solid foundation—from the basics to more sophisticated use‑cases—so you can start building LangGraph workflows right away. Happy coding!" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..1c44d00d5ce9fb510f9269a8b763a97957854bad --- /dev/null +++ b/requirements.txt @@ -0,0 +1,30 @@ +Chroma==0.2.0 +chroma-hnswlib==0.7.3 +chromadb==0.5.3 +gradio==4.43.0 +ipykernel==6.29.5 +langchain==0.2.16 +langchain-chroma==0.1.3 +langchain-community==0.2.16 +langchain-core==0.2.38 +langchain-openai==0.1.23 +langchain-groq==0.1.9 +langchain-huggingface==0.0.3 +huggingface_hub==0.23.4 +sentence-transformers==2.7.0 +langchain-text-splitters==0.2.4 +langgraph==0.2.19 +langgraph-checkpoint==1.0.9 +langsmith==0.1.116 +numpy==1.26.4 +openai==1.44.0 +pandas==2.2.2 +pydantic==2.9.0 +pydantic_core==2.23.2 +pypdf==4.3.1 +pyprojroot==0.3.0 +PyYAML==6.0.2 +SQLAlchemy==2.0.34 + + + diff --git a/sample_questions.txt b/sample_questions.txt new file mode 100644 index 0000000000000000000000000000000000000000..e3851a907b0f1d58f6e6a3b5af56890f4a2677e6 --- /dev/null +++ b/sample_questions.txt @@ -0,0 +1,28 @@ +Intro: + +In the stories, who is lily? + +Based on the airline policy, do I need to reconfirm my flight? + +Give me the table names in travel database + +Give me the table names in chinook database + +Give me the link of Trump and Harris debate on youtube +======================================================================== +Final test: + +In the stories, who is fred? + +Based on the airline policy, can I cancel my ticket 10 hours before the flight? + +Is there any flight available on the travel database that goes from CDG to BSL? + +In chinook DB, list the total sales per country. Which country's customers spent the most? + +What are all the genres of Alanis Morisette songs? + +Give me a couple of his albums' names + +Give me the link to some websites for langgraph tutorial +======================================================================== diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/agent_graph/__init__.py b/src/agent_graph/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/agent_graph/agent_backend.py b/src/agent_graph/agent_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..8fb3ceba7e9d49706e6d4370c47b7a32bd947902 --- /dev/null +++ b/src/agent_graph/agent_backend.py @@ -0,0 +1,119 @@ +import json +from IPython.display import Image, display +from typing import Annotated, Literal +from typing_extensions import TypedDict +from langchain_core.messages import ToolMessage +from langgraph.graph.message import add_messages + + +class State(TypedDict): + """Represents the state structure containing a list of messages. + + Attributes: + messages (list): A list of messages, where each message can be processed + by adding messages using the `add_messages` function. + """ + messages: Annotated[list, add_messages] + + +class BasicToolNode: + """A node that runs the tools requested in the last AIMessage. + + This class retrieves tool calls from the most recent AIMessage in the input + and invokes the corresponding tool to generate responses. + + Attributes: + tools_by_name (dict): A dictionary mapping tool names to tool instances. + """ + + def __init__(self, tools: list) -> None: + """Initializes the BasicToolNode with available tools. + + Args: + tools (list): A list of tool objects, each having a `name` attribute. + """ + self.tools_by_name = {tool.name: tool for tool in tools} + + def __call__(self, inputs: dict): + """Executes the tools based on the tool calls in the last message. + + Args: + inputs (dict): A dictionary containing the input state with messages. + + Returns: + dict: A dictionary with a list of `ToolMessage` outputs. + + Raises: + ValueError: If no messages are found in the input. + """ + if messages := inputs.get("messages", []): + message = messages[-1] + else: + raise ValueError("No message found in input") + outputs = [] + for tool_call in message.tool_calls: + tool_result = self.tools_by_name[tool_call["name"]].invoke( + tool_call["args"] + ) + outputs.append( + ToolMessage( + content=json.dumps(tool_result), + name=tool_call["name"], + tool_call_id=tool_call["id"], + ) + ) + return {"messages": outputs} + + +def route_tools( + state: State, +) -> Literal["tools", "__end__"]: + """ + + Determines whether to route to the ToolNode or end the flow. + + This function is used in the conditional_edge and checks the last message in the state for tool calls. If tool + calls exist, it routes to the 'tools' node; otherwise, it routes to the end. + + Args: + state (State): The input state containing a list of messages. + + Returns: + Literal["tools", "__end__"]: Returns 'tools' if there are tool calls; + '__end__' otherwise. + + Raises: + ValueError: If no messages are found in the input state. + """ + if isinstance(state, list): + ai_message = state[-1] + elif messages := state.get("messages", []): + ai_message = messages[-1] + else: + raise ValueError( + f"No messages found in input state to tool_edge: {state}") + if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0: + return "tools" + return "__end__" + + +def plot_agent_schema(graph): + """Plots the agent schema using a graph object, if possible. + + Tries to display a visual representation of the agent's graph schema + using Mermaid format and IPython's display capabilities. If the required + dependencies are missing, it catches the exception and prints a message + instead. + + Args: + graph: A graph object that has a `get_graph` method, returning a graph + structure that supports Mermaid diagram generation. + + Returns: + None + """ + try: + display(Image(graph.get_graph().draw_mermaid_png())) + except Exception: + # This requires some extra dependencies and is optional + return print("Graph could not be displayed.") diff --git a/src/agent_graph/build_full_graph.py b/src/agent_graph/build_full_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..efc62cb160567233ca5b8920b48d52f19e1262e1 --- /dev/null +++ b/src/agent_graph/build_full_graph.py @@ -0,0 +1,96 @@ +from langgraph.checkpoint.memory import MemorySaver +from langgraph.graph import StateGraph, START +from langchain_groq import ChatGroq +from agent_graph.tool_chinook_sqlagent import query_chinook_sqldb +from agent_graph.tool_travel_sqlagent import query_travel_sqldb +from agent_graph.tool_lookup_policy_rag import lookup_swiss_airline_policy +from agent_graph.tool_tavily_search import load_tavily_search_tool +from agent_graph.tool_stories_rag import lookup_stories +from agent_graph.load_tools_config import LoadToolsConfig +from agent_graph.agent_backend import State, BasicToolNode, route_tools, plot_agent_schema + +TOOLS_CFG = LoadToolsConfig() + + +def build_graph(): + """ + Builds an agent decision-making graph by combining an LLM with various tools + and defining the flow of interactions between them. + + This function sets up a state graph where a primary language model (LLM) interacts + with several predefined tools (e.g., databases, search functions, policy lookup, etc.). + The agent can invoke tools based on conditions and use their outputs to inform + further decisions. The flow involves conditional tool invocation, returning back + to the chatbot after tool execution to guide the next step. + + Steps: + 1. Initializes the primary language model (LLM) with tool-binding functionality. + 2. Defines nodes in the graph where each node represents a specific action: + - Chatbot node: Executes the LLM with the given state and messages. + - Tools node: Runs the tool invocations based on the last message in the input state. + 3. Implements conditional routing between the chatbot and tools: + - If a tool is required, it routes to the tools node. + - Otherwise, the flow ends. + 4. Establishes connections between the chatbot and tools nodes to form the agent loop. + 5. Uses a memory-saving mechanism to track and save checkpoints in the graph. + + Returns: + graph (StateGraph): The compiled state graph that represents the decision-making process + of the agent, integrating the chatbot, tools, and conditional routing. + + Components: + - `primary_llm`: The primary language model responsible for generating responses. + - `tools`: A list of tools including SQL queries, search functionalities, policy lookups, etc. + - `tool_node`: A node responsible for handling tool execution based on the chatbot's request. + - `chatbot`: A function that takes the state as input and returns a message generated by the LLM. + - `route_tools`: A conditional function to determine whether the chatbot should call a tool. + - `graph`: The complete graph with nodes and conditional edges. + """ + primary_llm = ChatGroq(model=TOOLS_CFG.primary_agent_llm, + temperature=TOOLS_CFG.primary_agent_llm_temperature) + graph_builder = StateGraph(State) + # Load tools with their proper configs + search_tool = load_tavily_search_tool(TOOLS_CFG.tavily_search_max_results) + tools = [search_tool, + lookup_swiss_airline_policy, + lookup_stories, + query_travel_sqldb, + query_chinook_sqldb, + ] + # Tell the LLM which tools it can call + primary_llm_with_tools = primary_llm.bind_tools(tools) + + def chatbot(state: State): + """Executes the primary language model with tools bound and returns the generated message.""" + return {"messages": [primary_llm_with_tools.invoke(state["messages"])]} + + graph_builder.add_node("chatbot", chatbot) + tool_node = BasicToolNode( + tools=[ + search_tool, + lookup_swiss_airline_policy, + lookup_stories, + query_travel_sqldb, + query_chinook_sqldb, + ]) + graph_builder.add_node("tools", tool_node) + # The `tools_condition` function returns "tools" if the chatbot asks to use a tool, and "__end__" if + # it is fine directly responding. This conditional routing defines the main agent loop. + graph_builder.add_conditional_edges( + "chatbot", + route_tools, + # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node + # It defaults to the identity function, but if you + # want to use a node named something else apart from "tools", + # You can update the value of the dictionary to something else + # e.g., "tools": "my_tools" + {"tools": "tools", "__end__": "__end__"}, + ) + + # Any time a tool is called, we return to the chatbot to decide the next step + graph_builder.add_edge("tools", "chatbot") + graph_builder.add_edge(START, "chatbot") + memory = MemorySaver() + graph = graph_builder.compile(checkpointer=memory) + plot_agent_schema(graph) + return graph diff --git a/src/agent_graph/load_tools_config.py b/src/agent_graph/load_tools_config.py new file mode 100644 index 0000000000000000000000000000000000000000..e40977c1c8da2673897830025bc64a73ded15a29 --- /dev/null +++ b/src/agent_graph/load_tools_config.py @@ -0,0 +1,72 @@ + +import os +import yaml +from dotenv import load_dotenv +from pyprojroot import here + +load_dotenv() + + +class LoadToolsConfig: + + def __init__(self) -> None: + with open(here("configs/tools_config.yml")) as cfg: + app_config = yaml.load(cfg, Loader=yaml.FullLoader) + + # Set environment variables + groq_api_key = os.getenv("GROQ_API_KEY") + tavily_api_key = os.getenv("TAVILY_API_KEY") + + # Primary agent + self.primary_agent_llm = app_config["primary_agent"]["llm"] + self.primary_agent_llm_temperature = app_config["primary_agent"]["llm_temperature"] + + # Internet Search config + self.tavily_search_max_results = int( + app_config["tavily_search_api"]["tavily_search_max_results"]) + + # Swiss Airline Policy RAG configs + self.policy_rag_llm = app_config["swiss_airline_policy_rag"]["llm"] + self.policy_rag_llm_temperature = float( + app_config["swiss_airline_policy_rag"]["llm_temperature"]) + self.policy_rag_embedding_model = app_config["swiss_airline_policy_rag"]["embedding_model"] + self.policy_rag_vectordb_directory = str(here( + app_config["swiss_airline_policy_rag"]["vectordb"])) # needs to be strin for summation in chromadb backend: self._settings.require("persist_directory") + "/chroma.sqlite3" + self.policy_rag_unstructured_docs_directory = str(here( + app_config["swiss_airline_policy_rag"]["unstructured_docs"])) + self.policy_rag_k = app_config["swiss_airline_policy_rag"]["k"] + self.policy_rag_chunk_size = app_config["swiss_airline_policy_rag"]["chunk_size"] + self.policy_rag_chunk_overlap = app_config["swiss_airline_policy_rag"]["chunk_overlap"] + self.policy_rag_collection_name = app_config["swiss_airline_policy_rag"]["collection_name"] + + # Stories RAG configs + self.stories_rag_llm = app_config["stories_rag"]["llm"] + self.stories_rag_llm_temperature = float( + app_config["stories_rag"]["llm_temperature"]) + self.stories_rag_embedding_model = app_config["stories_rag"]["embedding_model"] + self.stories_rag_vectordb_directory = str(here( + app_config["stories_rag"]["vectordb"])) # needs to be strin for summation in chromadb backend: self._settings.require("persist_directory") + "/chroma.sqlite3" + self.stories_rag_unstructured_docs_directory = str(here( + app_config["stories_rag"]["unstructured_docs"])) + self.stories_rag_k = app_config["stories_rag"]["k"] + self.stories_rag_chunk_size = app_config["stories_rag"]["chunk_size"] + self.stories_rag_chunk_overlap = app_config["stories_rag"]["chunk_overlap"] + self.stories_rag_collection_name = app_config["stories_rag"]["collection_name"] + + # Travel SQL Agent configs + self.travel_sqldb_directory = str(here( + app_config["travel_sqlagent_configs"]["travel_sqldb_dir"])) + self.travel_sqlagent_llm = app_config["travel_sqlagent_configs"]["llm"] + self.travel_sqlagent_llm_temperature = float( + app_config["travel_sqlagent_configs"]["llm_temperature"]) + + # Chinook SQL agent configs + self.chinook_sqldb_directory = str(here( + app_config["chinook_sqlagent_configs"]["chinook_sqldb_dir"])) + self.chinook_sqlagent_llm = app_config["chinook_sqlagent_configs"]["llm"] + self.chinook_sqlagent_llm_temperature = float( + app_config["chinook_sqlagent_configs"]["llm_temperature"]) + + # Graph configs + self.thread_id = str( + app_config["graph_configs"]["thread_id"]) diff --git a/src/agent_graph/tool_chinook_sqlagent.py b/src/agent_graph/tool_chinook_sqlagent.py new file mode 100644 index 0000000000000000000000000000000000000000..e78ec57fe62baab19370e11e2fd5298d6899d370 --- /dev/null +++ b/src/agent_graph/tool_chinook_sqlagent.py @@ -0,0 +1,148 @@ +from typing import List +from langchain_groq import ChatGroq +from langchain_core.pydantic_v1 import BaseModel, Field +from langchain.chains.openai_tools import create_extraction_chain_pydantic +from langchain_community.utilities import SQLDatabase +from langchain.chains import create_sql_query_chain +from langchain_core.runnables import RunnablePassthrough +from operator import itemgetter +from langchain_core.tools import tool +from agent_graph.load_tools_config import LoadToolsConfig +import re +from langchain_core.prompts import PromptTemplate + +TOOLS_CFG = LoadToolsConfig() + +def extract_sql(raw: str) -> str: + """Strip LLM reasoning text and extract only the SQL statement.""" + # Remove **Question:** ... **Answer:** ... patterns + raw = re.sub(r'(?i)\*{0,2}question\*{0,2}\s*:.*?(?=select|insert|update|delete|with|\*{0,2}sql)', '', raw, flags=re.DOTALL) + raw = re.sub(r'(?i)\*{0,2}answer\*{0,2}\s*:.*', '', raw, flags=re.DOTALL) + # Remove 'SQLQuery:' / 'SQL:' prefixes + raw = re.sub(r'(?i)\*{0,2}sql\s*query\*{0,2}\s*:', '', raw).strip() + raw = re.sub(r'(?i)^\*{0,2}sql\*{0,2}\s*:', '', raw).strip() + # Extract the first valid SQL statement + match = re.search(r'(SELECT|INSERT|UPDATE|DELETE|WITH)[\s\S]+?;', raw, re.IGNORECASE) + if match: + return match.group(0).strip() + return raw.strip() + +class Table(BaseModel): + """ + Represents a table in the SQL database. + + Attributes: + name (str): The name of the table in the SQL database. + """ + + name: str = Field(description="Name of table in SQL database.") + + +def get_tables(categories: List[Table]) -> List[str]: + """Maps category names to corresponding SQL table names. + + Args: + categories (List[Table]): A list of `Table` objects representing different categories. + + Returns: + List[str]: A list of SQL table names corresponding to the provided categories. + """ + tables = [] + for category in categories: + if category.name == "Music": + tables.extend( + [ + "Album", + "Artist", + "Genre", + "MediaType", + "Playlist", + "PlaylistTrack", + "Track", + ] + ) + elif category.name == "Business": + tables.extend( + ["Customer", "Employee", "Invoice", "InvoiceLine"]) + return tables + + +class ChinookSQLAgent: + """ + A specialized SQL agent that interacts with the Chinook SQL database using an LLM (Large Language Model). + + The agent handles SQL queries by mapping user questions to relevant SQL tables based on categories like "Music" + and "Business". It uses an extraction chain to determine relevant tables based on the question and then + executes queries against the database using the appropriate tables. + + Attributes: + sql_agent_llm (ChatOpenAI): The language model used for interpreting and interacting with the database. + db (SQLDatabase): The SQL database object, representing the Chinook database. + full_chain (Runnable): A chain of operations that maps user questions to SQL tables and executes queries. + + Methods: + __init__: Initializes the agent by setting up the LLM, connecting to the SQL database, and creating query chains. + + Args: + sqldb_directory (str): The directory where the Chinook SQLite database file is located. + llm (str): The name of the LLM model to use (e.g., "gpt-3.5-turbo"). + llm_temperature (float): The temperature setting for the LLM, controlling the randomness of responses. + """ + + + + def __init__(self, sqldb_directory: str, llm: str, llm_temerature: float) -> None: + """Initializes the ChinookSQLAgent with the LLM and database connection. + + Args: + sqldb_directory (str): The directory path to the SQLite database file. + llm (str): The LLM model identifier (e.g., "gpt-3.5-turbo"). + llm_temerature (float): The temperature value for the LLM, determining the randomness of the model's output. + """ + self.sql_agent_llm = ChatGroq(model=llm, temperature=llm_temerature) + + self.db = SQLDatabase.from_uri(f"sqlite:///{sqldb_directory}") + print(self.db.get_usable_table_names()) + + category_chain_system = """Return the names of the SQL tables that are relevant to the user question. \ + The tables are: + + Music + Business""" + + category_chain = create_extraction_chain_pydantic( + Table, self.sql_agent_llm, system_message=category_chain_system) + table_chain = category_chain | get_tables # noqa + + custom_prompt = PromptTemplate.from_template( + """You are a SQLite expert. Given a question, return ONLY the raw SQL query. + DO NOT include 'Question:', 'Answer:', 'SQLQuery:', markdown formatting, or any explanation. + Output the SQL statement ONLY, ending with a semicolon. + + Question: {input} + Table info: {table_info} + Top K: {top_k} + """ + ) + query_chain = create_sql_query_chain(self.sql_agent_llm, self.db, prompt=custom_prompt) + + # Convert "question" key to the "input" key expected by current table_chain. + table_chain = {"input": itemgetter("question")} | table_chain + # Set table_names_to_use using table_chain. + self.full_chain = RunnablePassthrough.assign( + table_names_to_use=table_chain) | query_chain + + +@tool +def query_chinook_sqldb(query: str) -> str: + """Query the Chinook SQL Database. Input should be a search query.""" + # Create an instance of ChinookSQLAgent + agent = ChinookSQLAgent( + sqldb_directory=TOOLS_CFG.chinook_sqldb_directory, + llm=TOOLS_CFG.chinook_sqlagent_llm, + llm_temerature=TOOLS_CFG.chinook_sqlagent_llm_temperature + ) + + raw_query = agent.full_chain.invoke({"question": query}) + clean_query = extract_sql(raw_query) + return agent.db.run(clean_query) diff --git a/src/agent_graph/tool_lookup_policy_rag.py b/src/agent_graph/tool_lookup_policy_rag.py new file mode 100644 index 0000000000000000000000000000000000000000..0c76bfec4f58694212854350efea3ca2b541287f --- /dev/null +++ b/src/agent_graph/tool_lookup_policy_rag.py @@ -0,0 +1,68 @@ +from langchain_chroma import Chroma +from langchain_huggingface import HuggingFaceEmbeddings +from langchain_core.tools import tool +from agent_graph.load_tools_config import LoadToolsConfig + +TOOLS_CFG = LoadToolsConfig() + + +class SwissAirlinePolicyRAGTool: + """ + A tool for retrieving relevant Swiss Airline policy documents using a + Retrieval-Augmented Generation (RAG) approach with vector embeddings. + + This tool uses a pre-trained OpenAI embedding model to transform queries into + vector representations. These vectors are then used to query a Chroma-based + vector database (persisted on disk) to retrieve the top-k most relevant + documents or entries from a specific collection, such as Swiss Airline policies. + + Attributes: + embedding_model (str): The name of the OpenAI embedding model used for + generating vector representations of the queries. + vectordb_dir (str): The directory where the Chroma vector database is + persisted on disk. + k (int): The number of top-k nearest neighbors (most relevant documents) + to retrieve from the vector database. + vectordb (Chroma): The Chroma vector database instance connected to the + specified collection and embedding model. + + Methods: + __init__: Initializes the tool by setting up the embedding model, + vector database, and retrieval parameters. + """ + + def __init__(self, embedding_model: str, vectordb_dir: str, k: int, collection_name: str) -> None: + """ + Initializes the SwissAirlinePolicyRAGTool with the necessary configuration. + + Args: + embedding_model (str): The name of the embedding model (e.g., "text-embedding-ada-002") + used to convert queries into vector representations. + vectordb_dir (str): The directory path where the Chroma vector database is stored + and persisted on disk. + k (int): The number of nearest neighbor documents to retrieve based on query similarity. + collection_name (str): The name of the collection inside the vector database that holds + the Swiss Airline policy documents. + """ + self.embedding_model = embedding_model + self.vectordb_dir = vectordb_dir + self.k = k + self.vectordb = Chroma( + collection_name=collection_name, + persist_directory=self.vectordb_dir, + embedding_function=HuggingFaceEmbeddings(model_name=self.embedding_model) + ) + print("Number of vectors in vectordb:", + self.vectordb._collection.count(), "\n\n") + + +@tool +def lookup_swiss_airline_policy(query: str) -> str: + """Consult the company policies to check whether certain options are permitted.""" + rag_tool = SwissAirlinePolicyRAGTool( + embedding_model=TOOLS_CFG.policy_rag_embedding_model, + vectordb_dir=TOOLS_CFG.policy_rag_vectordb_directory, + k=TOOLS_CFG.policy_rag_k, + collection_name=TOOLS_CFG.policy_rag_collection_name) + docs = rag_tool.vectordb.similarity_search(query, k=rag_tool.k) + return "\n\n".join([doc.page_content for doc in docs]) diff --git a/src/agent_graph/tool_stories_rag.py b/src/agent_graph/tool_stories_rag.py new file mode 100644 index 0000000000000000000000000000000000000000..56b7582733322827fe8db6502e99debf04c4466e --- /dev/null +++ b/src/agent_graph/tool_stories_rag.py @@ -0,0 +1,59 @@ +from langchain_chroma import Chroma +from langchain_huggingface import HuggingFaceEmbeddings +from langchain_core.tools import tool +from agent_graph.load_tools_config import LoadToolsConfig + +TOOLS_CFG = LoadToolsConfig() + + +class StoriesRAGTool: + """ + A tool for retrieving relevant stories using a Retrieval-Augmented Generation (RAG) approach with vector embeddings. + + This tool leverages a pre-trained OpenAI embedding model to transform user queries into vector embeddings. + It then uses these embeddings to query a Chroma-based vector database to retrieve the top-k most relevant + stories from a specific collection stored in the database. + + Attributes: + embedding_model (str): The name of the OpenAI embedding model used for generating vector representations of queries. + vectordb_dir (str): The directory where the Chroma vector database is persisted on disk. + k (int): The number of top-k nearest neighbor stories to retrieve from the vector database. + vectordb (Chroma): The Chroma vector database instance connected to the specified collection and embedding model. + + Methods: + __init__: Initializes the tool with the specified embedding model, vector database, and retrieval parameters. + """ + + def __init__(self, embedding_model: str, vectordb_dir: str, k: int, collection_name: str) -> None: + """ + Initializes the StoriesRAGTool with the necessary configurations. + + Args: + embedding_model (str): The name of the embedding model (e.g., "text-embedding-ada-002") + used to convert queries into vector representations. + vectordb_dir (str): The directory path where the Chroma vector database is stored and persisted on disk. + k (int): The number of nearest neighbor stories to retrieve based on query similarity. + collection_name (str): The name of the collection inside the vector database that holds the relevant stories. + """ + self.embedding_model = embedding_model + self.vectordb_dir = vectordb_dir + self.k = k + self.vectordb = Chroma( + collection_name=collection_name, + persist_directory=self.vectordb_dir, + embedding_function=HuggingFaceEmbeddings(model_name=self.embedding_model) + ) + print("Number of vectors in vectordb:", + self.vectordb._collection.count(), "\n\n") + + +@tool +def lookup_stories(query: str) -> str: + """Search among the fictional stories and find the answer to the query. Input should be the query.""" + rag_tool = StoriesRAGTool( + embedding_model=TOOLS_CFG.stories_rag_embedding_model, + vectordb_dir=TOOLS_CFG.stories_rag_vectordb_directory, + k=TOOLS_CFG.stories_rag_k, + collection_name=TOOLS_CFG.stories_rag_collection_name) + docs = rag_tool.vectordb.similarity_search(query, k=rag_tool.k) + return "\n\n".join([doc.page_content for doc in docs]) diff --git a/src/agent_graph/tool_tavily_search.py b/src/agent_graph/tool_tavily_search.py new file mode 100644 index 0000000000000000000000000000000000000000..d661c649a0486f6f646fd634bd50a2b90c472069 --- /dev/null +++ b/src/agent_graph/tool_tavily_search.py @@ -0,0 +1,16 @@ +from langchain_community.tools.tavily_search import TavilySearchResults + + +def load_tavily_search_tool(tavily_search_max_results: int): + """ + This function initializes a Tavily search tool, which performs searches and returns results + based on user queries. The `max_results` parameter controls how many search results are + retrieved for each query. + + Args: + tavily_search_max_results (int): The maximum number of search results to return for each query. + + Returns: + TavilySearchResults: A configured instance of the Tavily search tool with the specified `max_results`. + """ + return TavilySearchResults(max_results=tavily_search_max_results) diff --git a/src/agent_graph/tool_travel_sqlagent.py b/src/agent_graph/tool_travel_sqlagent.py new file mode 100644 index 0000000000000000000000000000000000000000..9af8ce905dafdb5a411568b849a39ff75bc482bb --- /dev/null +++ b/src/agent_graph/tool_travel_sqlagent.py @@ -0,0 +1,78 @@ +from langchain_core.tools import tool +from langchain_community.utilities import SQLDatabase +from langchain.chains import create_sql_query_chain +from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool +from langchain_core.prompts import PromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain_core.runnables import RunnablePassthrough +from operator import itemgetter +from langchain_groq import ChatGroq +from agent_graph.load_tools_config import LoadToolsConfig + +TOOLS_CFG = LoadToolsConfig() + + +class TravelSQLAgentTool: + """ + A tool for interacting with a travel-related SQL database using an LLM (Language Model) to generate and execute SQL queries. + + This tool enables users to ask travel-related questions, which are transformed into SQL queries by a language model. + The SQL queries are executed on the provided SQLite database, and the results are processed by the language model to + generate a final answer for the user. + + Attributes: + sql_agent_llm (ChatOpenAI): An instance of a ChatOpenAI language model used to generate and process SQL queries. + system_role (str): A system prompt template that guides the language model in answering user questions based on SQL query results. + db (SQLDatabase): An instance of the SQL database used to execute queries. + chain (RunnablePassthrough): A chain of operations that creates SQL queries, executes them, and generates a response. + + Methods: + __init__: Initializes the TravelSQLAgentTool by setting up the language model, SQL database, and query-answering pipeline. + """ + + def __init__(self, llm: str, sqldb_directory: str, llm_temerature: float) -> None: + """ + Initializes the TravelSQLAgentTool with the necessary configurations. + + Args: + llm (str): The name of the language model to be used for generating and interpreting SQL queries. + sqldb_directory (str): The directory path where the SQLite database is stored. + llm_temerature (float): The temperature setting for the language model, controlling response randomness. + """ + self.sql_agent_llm = ChatGroq( + model=llm, temperature=llm_temerature) + self.system_role = """Given the following user question, corresponding SQL query, and SQL result, answer the user question.\n + Question: {question}\n + SQL Query: {query}\n + SQL Result: {result}\n + Answer: + """ + self.db = SQLDatabase.from_uri( + f"sqlite:///{sqldb_directory}") + print(self.db.get_usable_table_names()) + + execute_query = QuerySQLDataBaseTool(db=self.db) + write_query = create_sql_query_chain( + self.sql_agent_llm, self.db) + answer_prompt = PromptTemplate.from_template( + self.system_role) + + answer = answer_prompt | self.sql_agent_llm | StrOutputParser() + self.chain = ( + RunnablePassthrough.assign(query=write_query).assign( + result=itemgetter("query") | execute_query + ) + | answer + ) + + +@tool +def query_travel_sqldb(query: str) -> str: + """Query the Swiss Airline SQL Database and access all the company's information. Input should be a search query.""" + agent = TravelSQLAgentTool( + llm=TOOLS_CFG.travel_sqlagent_llm, + sqldb_directory=TOOLS_CFG.travel_sqldb_directory, + llm_temerature=TOOLS_CFG.travel_sqlagent_llm_temperature + ) + response = agent.chain.invoke({"question": query}) + return response diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000000000000000000000000000000000000..a0b5ea57f8e3a9b80c6f2505864265ebcde373ba --- /dev/null +++ b/src/app.py @@ -0,0 +1,64 @@ +import gradio as gr +from chatbot.chatbot_backend import ChatBot +from utils.ui_settings import UISettings + + +with gr.Blocks() as demo: + with gr.Tabs(): + with gr.TabItem("AgentGraph"): + ############## + # First ROW: + ############## + with gr.Row() as row_one: + chatbot = gr.Chatbot( + [], + elem_id="chatbot", + bubble_full_width=False, + height=500, + avatar_images=( + ("images/AI_RT.png"), "images/chat_icon.png"), + # render=False + ) + # **Adding like/dislike icons + chatbot.like(UISettings.feedback, None, None) + ############## + # SECOND ROW: + ############## + with gr.Row(): + input_txt = gr.Textbox( + lines=3, + scale=8, + placeholder="Enter text and press enter, or upload PDF files", + container=False, + ) + + ############## + # Third ROW: + ############## + with gr.Row() as row_two: + text_submit_btn = gr.Button(value="Submit text") + clear_button = gr.ClearButton([input_txt, chatbot]) + ############## + # Process: + ############## + txt_msg = input_txt.submit(fn=ChatBot.respond, + inputs=[chatbot, input_txt], + outputs=[input_txt, + chatbot], + queue=False).then(lambda: gr.Textbox(interactive=True), + None, [input_txt], queue=False) + + txt_msg = text_submit_btn.click(fn=ChatBot.respond, + inputs=[chatbot, input_txt], + outputs=[input_txt, + chatbot], + queue=False).then(lambda: gr.Textbox(interactive=True), + None, [input_txt], queue=False) + + +if __name__ == "__main__": + # demo.launch() + demo.launch( + server_name="0.0.0.0", + server_port=7860 +) diff --git a/src/chatbot/chatbot_backend.py b/src/chatbot/chatbot_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..7a7197d6d08fe585a9cebfb78e6ce5994f424c23 --- /dev/null +++ b/src/chatbot/chatbot_backend.py @@ -0,0 +1,56 @@ +from typing import List, Tuple +from chatbot.load_config import LoadProjectConfig +from agent_graph.load_tools_config import LoadToolsConfig +from agent_graph.build_full_graph import build_graph +from utils.app_utils import create_directory +from chatbot.memory import Memory + + +PROJECT_CFG = LoadProjectConfig() +TOOLS_CFG = LoadToolsConfig() + +graph = build_graph() +config = {"configurable": {"thread_id": TOOLS_CFG.thread_id}} + +create_directory("memory") + + +class ChatBot: + """ + A class to handle chatbot interactions by utilizing a pre-defined agent graph. The chatbot processes + user messages, generates appropriate responses, and saves the chat history to a specified memory directory. + + Attributes: + config (dict): A configuration dictionary that stores specific settings such as the `thread_id`. + + Methods: + respond(chatbot: List, message: str) -> Tuple: + Processes the user message through the agent graph, generates a response, appends it to the chat history, + and writes the chat history to a file. + """ + @staticmethod + def respond(chatbot: List, message: str) -> Tuple: + """ + Processes a user message using the agent graph, generates a response, and appends it to the chat history. + The chat history is also saved to a memory file for future reference. + + Args: + chatbot (List): A list representing the chatbot conversation history. Each entry is a tuple of the user message and the bot response. + message (str): The user message to process. + + Returns: + Tuple: Returns an empty string (representing the new user input placeholder) and the updated conversation history. + """ + # The config is the **second positional argument** to stream() or invoke()! + events = graph.stream( + {"messages": [("user", message)]}, config, stream_mode="values" + ) + for event in events: + event["messages"][-1].pretty_print() + + chatbot.append( + (message, event["messages"][-1].content)) + + Memory.write_chat_history_to_file( + gradio_chatbot=chatbot, folder_path=PROJECT_CFG.memory_dir, thread_id=TOOLS_CFG.thread_id) + return "", chatbot diff --git a/src/chatbot/load_config.py b/src/chatbot/load_config.py new file mode 100644 index 0000000000000000000000000000000000000000..bae967caa0052c25234ec9698322103b43fd2a69 --- /dev/null +++ b/src/chatbot/load_config.py @@ -0,0 +1,22 @@ + +import os +import yaml +from dotenv import load_dotenv +from pyprojroot import here + +load_dotenv() + +with open(here("configs/project_config.yml")) as cfg: + app_config = yaml.load(cfg, Loader=yaml.FullLoader) + + +class LoadProjectConfig: + def __init__(self) -> None: + + # Load langsmith config + os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY") + os.environ["LANGCHAIN_TRACING_V2"] = app_config["langsmith"]["tracing"] + os.environ["LANGCHAIN_PROJECT"] = app_config["langsmith"]["project_name"] + + # Load memory config + self.memory_dir = here(app_config["memory"]["directory"]) diff --git a/src/chatbot/memory.py b/src/chatbot/memory.py new file mode 100644 index 0000000000000000000000000000000000000000..5f7cd35d954dfa250303871d2c126f9ca04cde58 --- /dev/null +++ b/src/chatbot/memory.py @@ -0,0 +1,58 @@ +import os +import pandas as pd +from typing import List +from datetime import datetime, date + + +class Memory: + """ + A class for handling the storage of chatbot conversation history by writing chat logs to a CSV file. + + Methods: + write_chat_history_to_file(gradio_chatbot: List, thread_id: str, folder_path: str) -> None: + Writes the most recent chatbot interaction (user query and bot response) to a CSV file. + The chat log is saved with the current date as the filename, and the interaction is + timestamped. + """ + @staticmethod + def write_chat_history_to_file(gradio_chatbot: List, thread_id: str, folder_path: str) -> None: + """ + Writes the most recent chatbot interaction (user query and response) to a CSV file. The log includes + the thread ID and timestamp of the interaction. The file for each day is saved with the current date as the filename. + + Args: + gradio_chatbot (List): A list containing tuples of user queries and chatbot responses. + The most recent interaction is appended to the log. + thread_id (str): The unique identifier for the chat session (or thread). + folder_path (str): The directory path where the chat log CSV files should be stored. + + Returns: + None + + File Structure: + - The chat log for each day is saved as a separate CSV file in the specified folder. + - The CSV file is named using the current date in 'YYYY-MM-DD' format. + - Each row in the CSV file contains the following columns: 'thread_id', 'timestamp', 'user_query', 'response'. + """ + tmp_list = list(gradio_chatbot[-1]) # Convert the tuple to a list + + today_str = date.today().strftime('%Y-%m-%d') + tmp_list.insert(0, thread_id) # Add the new value to the list + + current_time_str = datetime.now().strftime('%H:%M:%S') + tmp_list.insert(1, current_time_str) # Add the new value to the list + + # File path for today's CSV file + file_path = os.path.join(folder_path, f'{today_str}.csv') + + # Create a DataFrame from the list + new_df = pd.DataFrame([tmp_list], columns=[ + "thread_id", "timestamp", "user_query", "response"]) + + # Check if the file for today exists + if os.path.exists(file_path): + # If it exists, append the new data to the CSV file + new_df.to_csv(file_path, mode='a', header=False, index=False) + else: + # If it doesn't exist, create the CSV file with the new data + new_df.to_csv(file_path, mode='w', header=True, index=False) diff --git a/src/prepare_vector_db.py b/src/prepare_vector_db.py new file mode 100644 index 0000000000000000000000000000000000000000..af0d7a2c81ca1567ef54201ddd6fd0a9f4e5f639 --- /dev/null +++ b/src/prepare_vector_db.py @@ -0,0 +1,169 @@ +import os +import yaml +from pyprojroot import here +from langchain_chroma import Chroma +from langchain_community.document_loaders import PyPDFLoader +from langchain_huggingface import HuggingFaceEmbeddings +from langchain_text_splitters import RecursiveCharacterTextSplitter +from dotenv import load_dotenv + + +class PrepareVectorDB: + """ + A class to prepare and manage a Vector Database (VectorDB) using documents from a specified directory. + The class performs the following tasks: + - Loads and splits documents (PDFs). + - Splits the text into chunks based on the specified chunk size and overlap. + - Embeds the document chunks using a specified embedding model. + - Stores the embedded vectors in a persistent VectorDB directory. + + Attributes: + doc_dir (str): Path to the directory containing documents (PDFs) to be processed. + chunk_size (int): The maximum size of each chunk (in characters) into which the document text will be split. + chunk_overlap (int): The number of overlapping characters between consecutive chunks. + embedding_model (str): The name of the embedding model to be used for generating vector representations of text. + vectordb_dir (str): Directory where the resulting vector database will be stored. + collection_name (str): The name of the collection to be used within the vector database. + + Methods: + path_maker(file_name: str, doc_dir: str) -> str: + Creates a full file path by joining the given directory and file name. + + run() -> None: + Executes the process of reading documents, splitting text, embedding them into vectors, and + saving the resulting vector database. If the vector database directory already exists, it skips + the creation process. + """ + + def __init__(self, + doc_dir: str, + chunk_size: int, + chunk_overlap: int, + embedding_model: str, + vectordb_dir: str, + collection_name: str + ) -> None: + + self.doc_dir = doc_dir + self.chunk_size = chunk_size + self.chunk_overlap = chunk_overlap + self.embedding_model = embedding_model + self.vectordb_dir = vectordb_dir + self.collection_name = collection_name + + def path_maker(self, file_name: str, doc_dir): + """ + Creates a full file path by joining the provided directory and file name. + + Args: + file_name (str): Name of the file. + doc_dir (str): Path of the directory. + + Returns: + str: Full path of the file. + """ + return os.path.join(here(doc_dir), file_name) + + def run(self): + """ + Executes the main logic to create and store document embeddings in a VectorDB. + + If the vector database directory doesn't exist: + - It loads PDF documents from the `doc_dir`, splits them into chunks, + - Embeds the document chunks using the specified embedding model, + - Stores the embeddings in a persistent VectorDB directory. + + If the directory already exists, it skips the embedding creation process. + + Prints the creation status and the number of vectors in the vector database. + + Returns: + None + """ + if not os.path.exists(here(self.vectordb_dir)): + # If it doesn't exist, create the directory and create the embeddings + os.makedirs(here(self.vectordb_dir)) + print(f"Directory '{self.vectordb_dir}' was created.") + + file_list = os.listdir(here(self.doc_dir)) + docs = [PyPDFLoader(self.path_maker( + fn, self.doc_dir)).load_and_split() for fn in file_list] + docs_list = [item for sublist in docs for item in sublist] + + text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( + chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap + ) + doc_splits = text_splitter.split_documents(docs_list) + # Add to vectorDB + # vectordb = Chroma.from_documents( + # documents=doc_splits, + # collection_name=self.collection_name, + # embedding=HuggingFaceEmbeddings(model_name=self.embedding_model), + # persist_directory=str(here(self.vectordb_dir)) + # ) + # print("VectorDB is created and saved.") + # print("Number of vectors in vectordb:", + # vectordb._collection.count(), "\n\n") + vectordb = Chroma.from_documents( + documents=doc_splits, + collection_name=self.collection_name, + embedding=HuggingFaceEmbeddings( + model_name=self.embedding_model + ), + persist_directory=str(here(self.vectordb_dir)) + ) + + print("VectorDB is created and saved.") + + print( + "Number of vectors in vectordb:", + vectordb._collection.count(), + "\n\n" + ) + else: + print(f"Directory '{self.vectordb_dir}' already exists.") + + +if __name__ == "__main__": + load_dotenv() + os.environ['GROQ_API_KEY'] = os.getenv("GROQ_API_KEY") + + with open(here("configs/tools_config.yml")) as cfg: + app_config = yaml.load(cfg, Loader=yaml.FullLoader) + + # Uncomment the following configs to run for swiss airline policy document + chunk_size = app_config["swiss_airline_policy_rag"]["chunk_size"] + chunk_overlap = app_config["swiss_airline_policy_rag"]["chunk_overlap"] + embedding_model = app_config["swiss_airline_policy_rag"]["embedding_model"] + vectordb_dir = app_config["swiss_airline_policy_rag"]["vectordb"] + collection_name = app_config["swiss_airline_policy_rag"]["collection_name"] + doc_dir = app_config["swiss_airline_policy_rag"]["unstructured_docs"] + + prepare_db_instance = PrepareVectorDB( + doc_dir=doc_dir, + chunk_size=chunk_size, + chunk_overlap=chunk_overlap, + embedding_model=embedding_model, + vectordb_dir=vectordb_dir, + collection_name=collection_name) + + prepare_db_instance.run() + + # Uncomment the following configs to run for stories document + chunk_size = app_config["stories_rag"]["chunk_size"] + chunk_overlap = app_config["stories_rag"]["chunk_overlap"] + embedding_model = app_config["stories_rag"]["embedding_model"] + vectordb_dir = app_config["stories_rag"]["vectordb"] + collection_name = app_config["stories_rag"]["collection_name"] + doc_dir = app_config["stories_rag"]["unstructured_docs"] + + prepare_db_instance = PrepareVectorDB( + doc_dir=doc_dir, + chunk_size=chunk_size, + chunk_overlap=chunk_overlap, + embedding_model=embedding_model, + vectordb_dir=vectordb_dir, + collection_name=collection_name) + + prepare_db_instance.run() + print(here(vectordb_dir)) diff --git a/src/utils/app_utils.py b/src/utils/app_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..8e68657c16563a8cf272119e3fba5912f3a38985 --- /dev/null +++ b/src/utils/app_utils.py @@ -0,0 +1,19 @@ +import os +from pyprojroot import here + + +def create_directory(directory_path: str) -> None: + """ + Create a directory if it does not exist. + + Parameters: + directory_path (str): The path of the directory to be created. + + Example: + ```python + create_directory("/path/to/new/directory") + ``` + + """ + if not os.path.exists(here(directory_path)): + os.makedirs(here(directory_path)) diff --git a/src/utils/ui_settings.py b/src/utils/ui_settings.py new file mode 100644 index 0000000000000000000000000000000000000000..ef8aea0e0d6eaacaf4a0478f7656e871388cef0c --- /dev/null +++ b/src/utils/ui_settings.py @@ -0,0 +1,19 @@ +import gradio as gr + + +class UISettings: + """ + Utility class for managing UI settings. + """ + @staticmethod + def feedback(data: gr.LikeData): + """ + Process user feedback on the generated response. + + Parameters: + data (gr.LikeData): Gradio LikeData object containing user feedback. + """ + if data.liked: + print("You upvoted this response: " + data.value) + else: + print("You downvoted this response: " + data.value)