From adbdd15b102ebc5ea8a0ca5c3d80d25e3bd5ab5b Mon Sep 17 00:00:00 2001 From: bilgeyucel Date: Thu, 2 Jan 2025 14:40:58 +0300 Subject: [PATCH] Update the warnings * Add more explanation for 2.x tutorials * Put alternatives for 1.x tutorials --- tutorials/01_Basic_QA_Pipeline.ipynb | 2 +- .../02_Finetune_a_model_on_your_data.ipynb | 4 + tutorials/03_Scalable_QA_System.ipynb | 4 + tutorials/04_FAQ_style_QA.ipynb | 1439 ++++----- tutorials/05_Evaluation.ipynb | 4 + ...er_Retrieval_via_Embedding_Retrieval.ipynb | 2 +- tutorials/07_RAG_Generator.ipynb | 4 +- tutorials/08_Preprocessing.ipynb | 2 +- tutorials/09_DPR_training.ipynb | 4 + tutorials/10_Knowledge_Graph.ipynb | 4 +- tutorials/11_Pipelines.ipynb | 6 +- tutorials/12_LFQA.ipynb | 4 +- tutorials/13_Question_generation.ipynb | 4 + tutorials/14_Query_Classifier.ipynb | 4 + tutorials/15_TableQA.ipynb | 4 + ...16_Document_Classifier_at_Index_Time.ipynb | 4 + tutorials/17_Audio.ipynb | 11 +- tutorials/18_GPL.ipynb | 4 + ...h_pipeline_with_MultiModal_Retriever.ipynb | 9 +- .../20_Using_Haystack_with_REST_API.ipynb | 4 + tutorials/21_Customizing_PromptNode.ipynb | 10 +- tutorials/22_Pipeline_with_PromptNode.ipynb | 13 +- ...ering_Multihop_Questions_with_Agents.ipynb | 4 + tutorials/24_Building_Chat_App.ipynb | 4 + tutorials/25_Customizing_Agent.ipynb | 4 + tutorials/26_Hybrid_Retrieval.ipynb | 13 +- tutorials/27_First_RAG_Pipeline.ipynb | 2689 ++++++++--------- .../28_Structured_Output_With_Loop.ipynb | 972 +++--- tutorials/29_Serializing_Pipelines.ipynb | 47 +- ...le_Type_Preprocessing_Index_Pipeline.ipynb | 5 +- tutorials/31_Metadata_Filtering.ipynb | 4 +- ...ng_Documents_and_Queries_by_Language.ipynb | 10 +- tutorials/33_Hybrid_Retrieval.ipynb | 4 +- tutorials/34_Extractive_QA_Pipeline.ipynb | 2 +- tutorials/35_Evaluating_RAG_Pipelines.ipynb | 137 +- ...g_Fallbacks_with_Conditional_Routing.ipynb | 1071 +++---- ...ing_Pipeline_Inputs_with_Multiplexer.ipynb | 4 +- ...ding_Metadata_for_Improved_Retrieval.ipynb | 67 +- ...at_Application_with_Function_Calling.ipynb | 4 +- 39 files changed, 3159 insertions(+), 3428 deletions(-) diff --git a/tutorials/01_Basic_QA_Pipeline.ipynb b/tutorials/01_Basic_QA_Pipeline.ipynb index 7000b94c..7162d354 100644 --- a/tutorials/01_Basic_QA_Pipeline.ipynb +++ b/tutorials/01_Basic_QA_Pipeline.ipynb @@ -7,7 +7,7 @@ "source": [ "# Tutorial: Build Your First Question Answering System\n", "\n", - "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.0 and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) and [Build an Extractive QA Pipeline](https://haystack.deepset.ai/tutorials/34_extractive_qa_pipeline). \n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) and [Build an Extractive QA Pipeline](https://haystack.deepset.ai/tutorials/34_extractive_qa_pipeline). \n", ">\n", "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", diff --git a/tutorials/02_Finetune_a_model_on_your_data.ipynb b/tutorials/02_Finetune_a_model_on_your_data.ipynb index 3620e168..07d2bece 100644 --- a/tutorials/02_Finetune_a_model_on_your_data.ipynb +++ b/tutorials/02_Finetune_a_model_on_your_data.ipynb @@ -7,6 +7,10 @@ "source": [ "# Tutorial: Fine-Tuning a Model on Your Own Data\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook)\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 15 minutes\n", "- **Nodes Used**: `FARMReader`\n", diff --git a/tutorials/03_Scalable_QA_System.ipynb b/tutorials/03_Scalable_QA_System.ipynb index 810889f7..ecd098da 100644 --- a/tutorials/03_Scalable_QA_System.ipynb +++ b/tutorials/03_Scalable_QA_System.ipynb @@ -7,6 +7,10 @@ "source": [ "# Tutorial: Build a Scalable Question Answering System\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook)\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Beginner\n", "- **Time to complete**: 20 minutes\n", "- **Nodes Used**: `ElasticsearchDocumentStore`, `BM25Retriever`, `FARMReader`\n", diff --git a/tutorials/04_FAQ_style_QA.ipynb b/tutorials/04_FAQ_style_QA.ipynb index 4bb2cf38..382463c0 100644 --- a/tutorials/04_FAQ_style_QA.ipynb +++ b/tutorials/04_FAQ_style_QA.ipynb @@ -1,736 +1,741 @@ { - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "jUbPUmtaozIP" - }, - "source": [ - "# Utilizing existing FAQs for Question Answering\n", - "- **Level**: Beginner\n", - "- **Time to complete**: 15 minutes\n", - "- **Nodes Used**: `InMemoryDocumentStore`, `EmbeddingRetriever`\n", - "- **Goal**: Learn how to use the `EmbeddingRetriever` in a `FAQPipeline` to answer incoming questions by matching them to the most similar questions in your existing FAQ.\n", - "\n", - "# Overview\n", - "While *extractive Question Answering* works on pure texts and is therefore more generalizable, there's also a common alternative that utilizes existing FAQ data.\n", - "\n", - "**Pros**:\n", - "\n", - "- Very fast at inference time\n", - "- Utilize existing FAQ data\n", - "- Quite good control over answers\n", - "\n", - "**Cons**:\n", - "\n", - "- Generalizability: We can only answer questions that are similar to existing ones in FAQ\n", - "\n", - "In some use cases, a combination of extractive QA and FAQ-style can also be an interesting option." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "collapsed": false, - "id": "zBOtphIMozIT" - }, - "source": [ - "\n", - "## Preparing the Colab Environment\n", - "\n", - "- [Enable GPU Runtime](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration#enabling-the-gpu-in-colab)\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "ENpLjBejozIW" - }, - "source": [ - "## Installing Haystack\n", - "\n", - "To start, let's install the latest release of Haystack with `pip`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "q_y78_4LozIW" - }, - "outputs": [], - "source": [ - "%%bash\n", - "\n", - "pip install --upgrade pip\n", - "pip install farm-haystack[colab,inference]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Enabling Telemetry \n", - "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from haystack.telemetry import tutorial_running\n", - "\n", - "tutorial_running(4)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "collapsed": false, - "id": "Wl9Q6E3hozIW", - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Set the logging level to INFO:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "Edvocv1ZozIX", - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "logging.basicConfig(format=\"%(levelname)s - %(name)s - %(message)s\", level=logging.WARNING)\n", - "logging.getLogger(\"haystack\").setLevel(logging.INFO)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "noVtM20ZozIX" - }, - "source": [ - "### Create a simple DocumentStore\n", - "The InMemoryDocumentStore is good for quick development and prototyping. For more scalable options, check-out the [docs](https://docs.haystack.deepset.ai/docs/document_store)." - ] + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "id": "jUbPUmtaozIP" + }, + "source": [ + "# Utilizing existing FAQs for Question Answering\n", + "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", + "- **Level**: Beginner\n", + "- **Time to complete**: 15 minutes\n", + "- **Nodes Used**: `InMemoryDocumentStore`, `EmbeddingRetriever`\n", + "- **Goal**: Learn how to use the `EmbeddingRetriever` in a `FAQPipeline` to answer incoming questions by matching them to the most similar questions in your existing FAQ.\n", + "\n", + "# Overview\n", + "While *extractive Question Answering* works on pure texts and is therefore more generalizable, there's also a common alternative that utilizes existing FAQ data.\n", + "\n", + "**Pros**:\n", + "\n", + "- Very fast at inference time\n", + "- Utilize existing FAQ data\n", + "- Quite good control over answers\n", + "\n", + "**Cons**:\n", + "\n", + "- Generalizability: We can only answer questions that are similar to existing ones in FAQ\n", + "\n", + "In some use cases, a combination of extractive QA and FAQ-style can also be an interesting option." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "id": "zBOtphIMozIT" + }, + "source": [ + "\n", + "## Preparing the Colab Environment\n", + "\n", + "- [Enable GPU Runtime](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration#enabling-the-gpu-in-colab)\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "id": "ENpLjBejozIW" + }, + "source": [ + "## Installing Haystack\n", + "\n", + "To start, let's install the latest release of Haystack with `pip`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "q_y78_4LozIW" + }, + "outputs": [], + "source": [ + "%%bash\n", + "\n", + "pip install --upgrade pip\n", + "pip install farm-haystack[colab,inference]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Enabling Telemetry \n", + "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from haystack.telemetry import tutorial_running\n", + "\n", + "tutorial_running(4)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "id": "Wl9Q6E3hozIW", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Set the logging level to INFO:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "Edvocv1ZozIX", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "logging.basicConfig(format=\"%(levelname)s - %(name)s - %(message)s\", level=logging.WARNING)\n", + "logging.getLogger(\"haystack\").setLevel(logging.INFO)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "id": "noVtM20ZozIX" + }, + "source": [ + "### Create a simple DocumentStore\n", + "The InMemoryDocumentStore is good for quick development and prototyping. For more scalable options, check-out the [docs](https://docs.haystack.deepset.ai/docs/document_store)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "zeVfvRLZozIY" + }, + "outputs": [], + "source": [ + "from haystack.document_stores import InMemoryDocumentStore\n", + "\n", + "document_store = InMemoryDocumentStore()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "id": "zHevRxxaozIa" + }, + "source": [ + "### Create a Retriever using embeddings\n", + "Instead of retrieving via Elasticsearch's plain BM25, we want to use vector similarity of the questions (user question vs. FAQ ones).\n", + "We can use the `EmbeddingRetriever` for this purpose and specify a model that we use for the embeddings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oFNXb3kIozIb", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from haystack.nodes import EmbeddingRetriever\n", + "\n", + "retriever = EmbeddingRetriever(\n", + " document_store=document_store,\n", + " embedding_model=\"sentence-transformers/all-MiniLM-L6-v2\",\n", + " use_gpu=True,\n", + " scale_score=False,\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "id": "uLv8ysluozIb" + }, + "source": [ + "### Prepare & Index FAQ data\n", + "We create a pandas dataframe containing some FAQ data (i.e curated pairs of question + answer) and index those in our documentstore.\n", + "Here: We download some question-answer pairs related to COVID-19" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "AHiSltp4ozIb", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "from haystack.utils import fetch_archive_from_http\n", + "\n", + "\n", + "# Download\n", + "doc_dir = \"data/tutorial4\"\n", + "s3_url = \"https://s3.eu-central-1.amazonaws.com/deepset.ai-farm-qa/datasets/documents/small_faq_covid.csv.zip\"\n", + "fetch_archive_from_http(url=s3_url, output_dir=doc_dir)\n", + "\n", + "# Get dataframe with columns \"question\", \"answer\" and some custom metadata\n", + "df = pd.read_csv(f\"{doc_dir}/small_faq_covid.csv\")\n", + "# Minimal cleaning\n", + "df.fillna(value=\"\", inplace=True)\n", + "df[\"question\"] = df[\"question\"].apply(lambda x: x.strip())\n", + "print(df.head())\n", + "\n", + "# Create embeddings for our questions from the FAQs\n", + "# In contrast to most other search use cases, we don't create the embeddings here from the content of our documents,\n", + "# but rather from the additional text field \"question\" as we want to match \"incoming question\" <-> \"stored question\".\n", + "questions = list(df[\"question\"].values)\n", + "df[\"embedding\"] = retriever.embed_queries(queries=questions).tolist()\n", + "df = df.rename(columns={\"question\": \"content\"})\n", + "\n", + "# Convert Dataframe to list of dicts and index them in our DocumentStore\n", + "docs_to_index = df.to_dict(orient=\"records\")\n", + "document_store.write_documents(docs_to_index)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "id": "MXteNgYRozIb" + }, + "source": [ + "### Ask questions\n", + "Initialize a Pipeline (this time without a reader) and ask questions" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "id": "F5O7r3poozIb", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from haystack.pipelines import FAQPipeline\n", + "\n", + "pipe = FAQPipeline(retriever=retriever)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 709, + "referenced_widgets": [ + "070f7d6a12804647b2c4f5ec98241ced", + "8678507de5e748219ba28bb7970c0e63", + "35855d91133f474092381950bdbfce58", + "0656e34a277141d184aef005e4d39f88", + "612af309a6a94477b56dcea22c7a0940", + "dc9c54def7bf47d39819a97b7ceed839", + "09f4ba018a514f1ca2929ece4d0335e2", + "58f3458cddc747d7b1a1c05f8f0664ed", + "cd2614a0933c48a391966cb572044710", + "52abbb2d8eb043a0924d705a99577303", + "04495cdbd0e04e02a91ae3b026ef4c46" + ] }, + "id": "QX6qbic2ozIc", + "outputId": "af0a8eda-f7f6-4c97-cda7-13566ff888b1", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "zeVfvRLZozIY" + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "070f7d6a12804647b2c4f5ec98241ced", + "version_major": 2, + "version_minor": 0 }, - "outputs": [], - "source": [ - "from haystack.document_stores import InMemoryDocumentStore\n", - "\n", - "document_store = InMemoryDocumentStore()" + "text/plain": [ + "Batches: 0%| | 0/1 [00:00 \"stored question\".\n", - "questions = list(df[\"question\"].values)\n", - "df[\"embedding\"] = retriever.embed_queries(queries=questions).tolist()\n", - "df = df.rename(columns={\"question\": \"content\"})\n", - "\n", - "# Convert Dataframe to list of dicts and index them in our DocumentStore\n", - "docs_to_index = df.to_dict(orient=\"records\")\n", - "document_store.write_documents(docs_to_index)" - ] + "09f4ba018a514f1ca2929ece4d0335e2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "collapsed": false, - "id": "MXteNgYRozIb" - }, - "source": [ - "### Ask questions\n", - "Initialize a Pipeline (this time without a reader) and ask questions" - ] + "35855d91133f474092381950bdbfce58": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_58f3458cddc747d7b1a1c05f8f0664ed", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_cd2614a0933c48a391966cb572044710", + "value": 1 + } }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "id": "F5O7r3poozIb", - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from haystack.pipelines import FAQPipeline\n", - "\n", - "pipe = FAQPipeline(retriever=retriever)" - ] + "52abbb2d8eb043a0924d705a99577303": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 709, - "referenced_widgets": [ - "070f7d6a12804647b2c4f5ec98241ced", - "8678507de5e748219ba28bb7970c0e63", - "35855d91133f474092381950bdbfce58", - "0656e34a277141d184aef005e4d39f88", - "612af309a6a94477b56dcea22c7a0940", - "dc9c54def7bf47d39819a97b7ceed839", - "09f4ba018a514f1ca2929ece4d0335e2", - "58f3458cddc747d7b1a1c05f8f0664ed", - "cd2614a0933c48a391966cb572044710", - "52abbb2d8eb043a0924d705a99577303", - "04495cdbd0e04e02a91ae3b026ef4c46" - ] - }, - "id": "QX6qbic2ozIc", - "outputId": "af0a8eda-f7f6-4c97-cda7-13566ff888b1", - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "070f7d6a12804647b2c4f5ec98241ced", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Batches: 0%| | 0/1 [00:00 This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Evaluating RAG Pipelines](https://haystack.deepset.ai/tutorials/35_evaluating_rag_pipelines). \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "To be able to make a statement about the quality of results a question-answering pipeline or any other pipeline in haystack produces, it is important to evaluate it. Furthermore, evaluation allows determining which components of the pipeline can be improved.\n", "The results of the evaluation can be saved as CSV files, which contain all the information to calculate additional metrics later on or inspect individual predictions." ] diff --git a/tutorials/06_Better_Retrieval_via_Embedding_Retrieval.ipynb b/tutorials/06_Better_Retrieval_via_Embedding_Retrieval.ipynb index 8263ba60..db82999d 100644 --- a/tutorials/06_Better_Retrieval_via_Embedding_Retrieval.ipynb +++ b/tutorials/06_Better_Retrieval_via_Embedding_Retrieval.ipynb @@ -9,7 +9,7 @@ "source": [ "# Better Retrieval via \"Embedding Retrieval\"\n", "\n", - "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.0 and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) and [Build an Extractive QA Pipeline](https://haystack.deepset.ai/tutorials/34_extractive_qa_pipeline). \n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) and [Build an Extractive QA Pipeline](https://haystack.deepset.ai/tutorials/34_extractive_qa_pipeline). \n", ">\n", "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", diff --git a/tutorials/07_RAG_Generator.ipynb b/tutorials/07_RAG_Generator.ipynb index 2b239d31..b7dffe25 100644 --- a/tutorials/07_RAG_Generator.ipynb +++ b/tutorials/07_RAG_Generator.ipynb @@ -15,7 +15,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "> As of version 1.16, `RAGenerator` has been deprecated in Haystack and completely removed from Haystack as of v1.18. We recommend following the tutorial on [Creating a Generative QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/22_pipeline_with_promptnode) instead. For more details about this deprecation, check out [our announcement](https://github.com/deepset-ai/haystack/discussions/4816) on Github." + "> As of version 1.16 (`farm-haystack`), `RAGenerator` has been deprecated in Haystack and completely removed from Haystack as of v1.18. We recommend using Haystack 2.x (`haystack-ai`) and following the tutorial on [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) instead. \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release)." ] }, { diff --git a/tutorials/08_Preprocessing.ipynb b/tutorials/08_Preprocessing.ipynb index a0be3bca..a24168b3 100644 --- a/tutorials/08_Preprocessing.ipynb +++ b/tutorials/08_Preprocessing.ipynb @@ -8,7 +8,7 @@ "source": [ "# Preprocessing\n", "\n", - "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.0 and would like to follow the updated version of this tutorial, check out [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline). \n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline). \n", ">\n", "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", diff --git a/tutorials/09_DPR_training.ipynb b/tutorials/09_DPR_training.ipynb index 611450ae..90ac0445 100644 --- a/tutorials/09_DPR_training.ipynb +++ b/tutorials/09_DPR_training.ipynb @@ -12,6 +12,10 @@ "source": [ "# Training Your Own \"Dense Passage Retrieval\" Model\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "Haystack contains all the tools needed to train your own Dense Passage Retrieval model.\n", "This tutorial will guide you through the steps required to create a retriever that is specifically tailored to your domain." ] diff --git a/tutorials/10_Knowledge_Graph.ipynb b/tutorials/10_Knowledge_Graph.ipynb index 5f728a64..198aba3e 100644 --- a/tutorials/10_Knowledge_Graph.ipynb +++ b/tutorials/10_Knowledge_Graph.ipynb @@ -11,7 +11,9 @@ "source": [ "# Question Answering on a Knowledge Graph\n", "\n", - "> Starting from version 1.15, `BaseKnowledgeGraph`, `GraphDBKnowledgeGraph`, `InMemoryKnowledgeGraph`, and `Text2SparqlRetriever` are being deprecated and will be removed from Haystack as of version 1.17. For more details about this deprecation, check out [our announcement](https://github.com/deepset-ai/haystack/discussions/4882) on Github. \n", + "> Starting from version 1.15 (`farm-haystack`), `BaseKnowledgeGraph`, `GraphDBKnowledgeGraph`, `InMemoryKnowledgeGraph`, and `Text2SparqlRetriever` are being deprecated and will be removed from Haystack as of version 1.17. For more details about this deprecation, check out [our announcement](https://github.com/deepset-ai/haystack/discussions/4882) on Github. \n", + ">\n", + "> If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook). For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", "Haystack allows storing and querying knowledge graphs with the help of pre-trained models that translate text queries to SPARQL queries.\n", "This tutorial demonstrates how to load an existing knowledge graph into haystack, load a pre-trained retriever, and execute text queries on the knowledge graph.\n", diff --git a/tutorials/11_Pipelines.ipynb b/tutorials/11_Pipelines.ipynb index 86a9f7cb..381b9064 100644 --- a/tutorials/11_Pipelines.ipynb +++ b/tutorials/11_Pipelines.ipynb @@ -11,7 +11,11 @@ } }, "source": [ - "# Tutorial: How to Use Pipelines\n", + "# How to Use Pipelines\n", + "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", "In this tutorial, you will learn how the `Pipeline` connects the different components in Haystack. Whether you are using a Reader, Summarizer\n", "or Retriever (or 2), the `Pipeline` class will help you build a Directed Acyclic Graph (DAG) that\n", diff --git a/tutorials/12_LFQA.ipynb b/tutorials/12_LFQA.ipynb index c70f96c9..787bc9b5 100644 --- a/tutorials/12_LFQA.ipynb +++ b/tutorials/12_LFQA.ipynb @@ -15,7 +15,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "> As of version 1.16, `Seq2SeqGenerator` has been deprecated in Haystack and completely removed from Haystack as of v1.18. We recommend following the tutorial on [Creating a Generative QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/22_pipeline_with_promptnode) instead. For more details about this deprecation, check out [our announcement](https://github.com/deepset-ai/haystack/discussions/4816) on Github." + "> As of version 1.16 (`farm-haystack`), `Seq2SeqGenerator` has been deprecated in Haystack and completely removed from Haystack as of v1.18. We recommend using Haystack 2.x (`haystack-ai`) and following the tutorial on [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) instead. \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release)." ] }, { diff --git a/tutorials/13_Question_generation.ipynb b/tutorials/13_Question_generation.ipynb index 1f50b5d6..0a78d8d7 100644 --- a/tutorials/13_Question_generation.ipynb +++ b/tutorials/13_Question_generation.ipynb @@ -9,6 +9,10 @@ "source": [ "# Question Generation\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "This is a bare bones tutorial showing what is possible with the QuestionGenerator Nodes and Pipelines which automatically\n", "generate questions which the question generation model thinks can be answered by a given document." ] diff --git a/tutorials/14_Query_Classifier.ipynb b/tutorials/14_Query_Classifier.ipynb index a2a4da9d..09d8798b 100644 --- a/tutorials/14_Query_Classifier.ipynb +++ b/tutorials/14_Query_Classifier.ipynb @@ -9,6 +9,10 @@ "source": [ "# Tutorial: Query Classifier\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 15 minutes\n", "- **Nodes Used**: `TransformersQueryClassifier`, `InMemoryDocumentStore`, `BM25Retriever`, `EmbeddingRetriever`, `FARMReader`\n", diff --git a/tutorials/15_TableQA.ipynb b/tutorials/15_TableQA.ipynb index ed8fea8a..77bc51f7 100644 --- a/tutorials/15_TableQA.ipynb +++ b/tutorials/15_TableQA.ipynb @@ -9,6 +9,10 @@ "source": [ "# Open-Domain QA on Tables\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "This tutorial shows you how to perform question-answering on tables using the `EmbeddingRetriever` or `BM25Retriever` as retriever node and the `TableReader` as reader node." ] }, diff --git a/tutorials/16_Document_Classifier_at_Index_Time.ipynb b/tutorials/16_Document_Classifier_at_Index_Time.ipynb index 57b23caf..6f132c4e 100644 --- a/tutorials/16_Document_Classifier_at_Index_Time.ipynb +++ b/tutorials/16_Document_Classifier_at_Index_Time.ipynb @@ -7,6 +7,10 @@ "source": [ "# Extending your Metadata using DocumentClassifiers at Index Time\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "With DocumentClassifier it's possible to automatically enrich your documents with categories, sentiments, topics or whatever metadata you like. This metadata could be used for efficient filtering or further processing. Say you have some categories your users typically filter on. If the documents are tagged manually with these categories, you could automate this process by training a model. Or you can leverage the full power and flexibility of zero shot classification. All you need to do is pass your categories to the classifier, no labels required. This tutorial shows how to integrate it in your indexing pipeline." ] }, diff --git a/tutorials/17_Audio.ipynb b/tutorials/17_Audio.ipynb index cd15a2f1..f460f808 100644 --- a/tutorials/17_Audio.ipynb +++ b/tutorials/17_Audio.ipynb @@ -9,7 +9,9 @@ "source": [ "# Tutorial: Make Your QA Pipelines Talk!\n", "\n", - ">⚠️**Update:** This tutorial is now outdated and we recommend moving to Haystack >= 2.0 and checking out the new tutorials [here](https://haystack.deepset.ai/tutorials). AnswerToSpeech lives in the [text2speech](https://github.com/deepset-ai/haystack-extras/tree/main/nodes/text2speech) package. Main [Haystack](https://github.com/deepset-ai/haystack) repository doesn't include it anymore.\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 15 minutes\n", @@ -319,7 +321,7 @@ }, "gpuClass": "standard", "kernelspec": { - "display_name": "Python 3.9.6 64-bit", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -334,11 +336,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" - }, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } } }, "nbformat": 4, diff --git a/tutorials/18_GPL.ipynb b/tutorials/18_GPL.ipynb index 9ca92467..187a757a 100644 --- a/tutorials/18_GPL.ipynb +++ b/tutorials/18_GPL.ipynb @@ -12,6 +12,10 @@ "source": [ "# Generative Pseudo Labeling for Domain Adaptation of Dense Retrievals\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "*Note: Adapted to Haystack from Nils Reimers' original [notebook](https://colab.research.google.com/gist/jamescalam/d2c888775c87f9882bb7c379a96adbc8/gpl-domain-adaptation.ipynb#scrollTo=183ff7ab)\n", "\n", "The NLP models we use every day were trained on a corpus of data that reflects the world from the past. In the meantime, we've experienced world-changing events, like the COVID pandemics, and we'd like our models to know about them. Training a model from scratch is tedious work but what if we could just update the models with new data? Generative Pseudo Labeling comes to the rescue.\n", diff --git a/tutorials/19_Text_to_Image_search_pipeline_with_MultiModal_Retriever.ipynb b/tutorials/19_Text_to_Image_search_pipeline_with_MultiModal_Retriever.ipynb index 24386806..52ef2702 100644 --- a/tutorials/19_Text_to_Image_search_pipeline_with_MultiModal_Retriever.ipynb +++ b/tutorials/19_Text_to_Image_search_pipeline_with_MultiModal_Retriever.ipynb @@ -9,18 +9,17 @@ "source": [ "# Text-To-Image Search Pipeline with Multimodal Retriever\n", "\n", - "**Level**: Intermediate\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", "\n", + "**Level**: Intermediate\n", "**Time to complete**: 20 minutes\n", - "\n", "**Prerequisites**: This tutorial assumes basic knowledge of Haystack Retrievers and Pipelines. If you want to learn about them, have a look at our tutorials on [Build Your First QA System](https://github.com/deepset-ai/haystack-tutorials/blob/main/tutorials/01_Basic_QA_Pipeline.ipynb) and [Fine-Tuning a Model on Your Own Data](https://github.com/deepset-ai/haystack-tutorials/blob/main/tutorials/02_Finetune_a_model_on_your_data.ipynb).\n", "\n", "Prepare the Colab environment (see links below).\n", - "\n", "**Nodes Used**: InMemoryDocumentStore, MultiModalRetriever\n", - "\n", "**Goal**: After completing this tutorial, you will have built a search system that retrieves images as answers to a text query.\n", - "\n", "**Description**: In this tutorial, you'll download a set of images that you'll then turn into embeddings using a transformers model, OpenAI CLIP. You'll then use the same model to embed the text query. Finally, you'll perform a nearest neighbor search to retrieve the images relevant to the text query.\n", "\n", "Let's build a text-to-image search pipeline using a small animal dataset!" diff --git a/tutorials/20_Using_Haystack_with_REST_API.ipynb b/tutorials/20_Using_Haystack_with_REST_API.ipynb index 55d66296..57a79eda 100644 --- a/tutorials/20_Using_Haystack_with_REST_API.ipynb +++ b/tutorials/20_Using_Haystack_with_REST_API.ipynb @@ -7,6 +7,10 @@ "source": [ "# Tutorial: Using Haystack with REST API\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Advanced\n", "- **Time to complete**: 30 minutes\n", "- **Prerequisites**: Basic understanding of Docker and basic knowledge of Haystack pipelines. \n", diff --git a/tutorials/21_Customizing_PromptNode.ipynb b/tutorials/21_Customizing_PromptNode.ipynb index e031a8f3..078bacbb 100644 --- a/tutorials/21_Customizing_PromptNode.ipynb +++ b/tutorials/21_Customizing_PromptNode.ipynb @@ -9,14 +9,14 @@ "source": [ "# Tutorial: Customizing PromptNode for NLP Tasks\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 20 minutes\n", "- **Nodes Used**: `PromptNode`, `PromptTemplate`\n", - "- **Goal**: After completing this tutorial, you will have learned the basics of using PromptNode and PromptTemplates and you'll have added titles to articles from The Guardian and categorized them. \n", - "\n", - "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.0 and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline). \n", - ">\n", - "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release)." + "- **Goal**: After completing this tutorial, you will have learned the basics of using PromptNode and PromptTemplates and you'll have added titles to articles from The Guardian and categorized them. " ] }, { diff --git a/tutorials/22_Pipeline_with_PromptNode.ipynb b/tutorials/22_Pipeline_with_PromptNode.ipynb index fedbf40c..d7ea329d 100644 --- a/tutorials/22_Pipeline_with_PromptNode.ipynb +++ b/tutorials/22_Pipeline_with_PromptNode.ipynb @@ -9,21 +9,16 @@ "source": [ "# Tutorial: Creating a Generative QA Pipeline with Retrieval-Augmentation\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 15 minutes\n", "- **Nodes Used**: `InMemoryDocumentStore`, `BM25Retriever`, `PromptNode`, `PromptTemplate`\n", "- **Goal**: After completing this tutorial, you'll have created a generative question answering search system that uses a large language model through PromptNode with PromptTemplate." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.0 and would like to follow the updated version of this tutorial, check out [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline). \n", - ">\n", - "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release)." - ] - }, { "attachments": {}, "cell_type": "markdown", diff --git a/tutorials/23_Answering_Multihop_Questions_with_Agents.ipynb b/tutorials/23_Answering_Multihop_Questions_with_Agents.ipynb index 12f7b5f8..86275a91 100644 --- a/tutorials/23_Answering_Multihop_Questions_with_Agents.ipynb +++ b/tutorials/23_Answering_Multihop_Questions_with_Agents.ipynb @@ -9,6 +9,10 @@ "source": [ "# Tutorial: Answering Multihop Questions with Agents\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), and would like to follow the updated version of this tutorial, check out [Cookbook: Newsletter Sending Agent](https://haystack.deepset.ai/cookbook/newsletter-agent) or refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 10 minutes\n", "- **Nodes Used**: `Agent`, `PromptNode`, `InMemoryDocumentStore`, `FARMReader` and `ExtractiveQAPipeline`\n", diff --git a/tutorials/24_Building_Chat_App.ipynb b/tutorials/24_Building_Chat_App.ipynb index ee15a6a9..b6399b30 100644 --- a/tutorials/24_Building_Chat_App.ipynb +++ b/tutorials/24_Building_Chat_App.ipynb @@ -8,6 +8,10 @@ "source": [ "# Tutorial: Building a Conversational Chat App\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`) and would like to follow the updated version of this tutorial, check out [Building a Chat Application with Function Calling](https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling).\n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 10 minutes\n", "- **Nodes Used**: `PromptNode`, `ConversationalAgent` and `ConversationSummaryMemory`\n", diff --git a/tutorials/25_Customizing_Agent.ipynb b/tutorials/25_Customizing_Agent.ipynb index cd076d69..08d592ea 100644 --- a/tutorials/25_Customizing_Agent.ipynb +++ b/tutorials/25_Customizing_Agent.ipynb @@ -9,6 +9,10 @@ "source": [ "# Tutorial: Customizing Agent to Chat with Your Documents\n", "\n", + "> This tutorial is based on Haystack 1.x (`farm-haystack`). If you're using Haystack 2.x (`haystack-ai`), refer to the [Haystack 2.x tutorials](https://haystack.deepset.ai/tutorials) or [Haystack Cookbook](https://haystack.deepset.ai/cookbook).\n", + ">\n", + "> For more information on Haystack 2.x, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Advanced\n", "- **Time to complete**: 20 minutes\n", "- **Nodes Used**: `BM25Retriever`, `PromptNode`, `Agent`, and `Memory`\n", diff --git a/tutorials/26_Hybrid_Retrieval.ipynb b/tutorials/26_Hybrid_Retrieval.ipynb index 0ef23f7c..d56fbfbb 100644 --- a/tutorials/26_Hybrid_Retrieval.ipynb +++ b/tutorials/26_Hybrid_Retrieval.ipynb @@ -8,21 +8,16 @@ "source": [ "# Tutorial: Creating a Hybrid Retrieval Pipeline\n", "\n", + "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.x and would like to follow the updated version of this tutorial, check out [Creating a Hybrid Pipeline](https://haystack.deepset.ai/tutorials/33_hybrid_retrieval). \n", + ">\n", + "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release).\n", + "\n", "- **Level**: Intermediate\n", "- **Time to complete**: 15 minutes\n", "- **Nodes Used**: `EmbeddingRetriever`, `BM25Retriever`, `JoinDocuments`, `SentenceTransformersRanker` and `InMemoryDocumentStore`\n", "- **Goal**: After completing this tutorial, you will have learned about creating your first hybrid retrieval and when it's useful." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> This tutorial is based on Haystack 1.x. If you're using Haystack 2.0 and would like to follow the updated version of this tutorial, check out [Creating a Hybrid Pipeline](https://haystack.deepset.ai/tutorials/33_hybrid_retrieval). \n", - ">\n", - "> For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release)." - ] - }, { "cell_type": "markdown", "metadata": { diff --git a/tutorials/27_First_RAG_Pipeline.ipynb b/tutorials/27_First_RAG_Pipeline.ipynb index d9467ff3..943d1c70 100644 --- a/tutorials/27_First_RAG_Pipeline.ipynb +++ b/tutorials/27_First_RAG_Pipeline.ipynb @@ -1,1446 +1,1343 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "2OvkPji9O-qX" - }, - "source": [ - "# Tutorial: Creating Your First QA Pipeline with Retrieval-Augmentation\n", - "\n", - "- **Level**: Beginner\n", - "- **Time to complete**: 10 minutes\n", - "- **Components Used**: [`InMemoryDocumentStore`](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [`SentenceTransformersDocumentEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder), [`SentenceTransformersTextEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder), [`InMemoryEmbeddingRetriever`](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [`PromptBuilder`](https://docs.haystack.deepset.ai/docs/promptbuilder), [`OpenAIGenerator`](https://docs.haystack.deepset.ai/docs/openaigenerator)\n", - "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys).\n", - "- **Goal**: After completing this tutorial, you'll have learned the new prompt syntax and how to use PromptBuilder and OpenAIGenerator to build a generative question-answering pipeline with retrieval-augmentation.\n", - "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LFqHcXYPO-qZ" - }, - "source": [ - "## Overview\n", - "\n", - "This tutorial shows you how to create a generative question-answering pipeline using the retrieval-augmentation ([RAG](https://www.deepset.ai/blog/llms-retrieval-augmentation)) approach with Haystack 2.0. The process involves four main components: [SentenceTransformersTextEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder) for creating an embedding for the user query, [InMemoryBM25Retriever](https://docs.haystack.deepset.ai/docs/inmemorybm25retriever) for fetching relevant documents, [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) for creating a template prompt, and [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) for generating responses.\n", - "\n", - "For this tutorial, you'll use the Wikipedia pages of [Seven Wonders of the Ancient World](https://en.wikipedia.org/wiki/Wonders_of_the_World) as Documents, but you can replace them with any text you want.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QXjVlbPiO-qZ" - }, - "source": [ - "## Preparing the Colab Environment\n", - "\n", - "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", - "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/logging)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Kww5B_vXO-qZ" - }, - "source": [ - "## Installing Haystack\n", - "\n", - "Install Haystack 2.0 and other required packages with `pip`:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "UQbU8GUfO-qZ", - "outputId": "c33579e9-5557-43bd-a3c5-63b8373770c7" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: haystack-ai in /usr/local/lib/python3.10/dist-packages (2.0.0b8)\n", - "Requirement already satisfied: boilerpy3 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.0.7)\n", - "Requirement already satisfied: haystack-bm25 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.0.2)\n", - "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (3.1.3)\n", - "Requirement already satisfied: lazy-imports in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (0.3.1)\n", - "Requirement already satisfied: more-itertools in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (10.1.0)\n", - "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (3.2.1)\n", - "Requirement already satisfied: openai>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.13.3)\n", - "Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.5.3)\n", - "Requirement already satisfied: posthog in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (3.5.0)\n", - "Requirement already satisfied: pyyaml in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (6.0.1)\n", - "Requirement already satisfied: tenacity in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (8.2.3)\n", - "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (4.66.2)\n", - "Requirement already satisfied: typing-extensions in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (4.10.0)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (3.7.1)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai>=1.1.0->haystack-ai) (1.7.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (0.27.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (2.6.3)\n", - "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (1.3.1)\n", - "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from haystack-bm25->haystack-ai) (1.25.2)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->haystack-ai) (2.1.5)\n", - "Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas->haystack-ai) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->haystack-ai) (2023.4)\n", - "Requirement already satisfied: requests<3.0,>=2.7 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (2.31.0)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (1.16.0)\n", - "Requirement already satisfied: monotonic>=1.5 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (1.6)\n", - "Requirement already satisfied: backoff>=1.10.0 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (2.2.1)\n", - "Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai) (3.6)\n", - "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai) (1.2.0)\n", - "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (2024.2.2)\n", - "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (1.0.4)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (0.14.0)\n", - "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai) (0.6.0)\n", - "Requirement already satisfied: pydantic-core==2.16.3 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai) (2.16.3)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3.0,>=2.7->posthog->haystack-ai) (3.3.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3.0,>=2.7->posthog->haystack-ai) (2.0.7)\n", - "Requirement already satisfied: datasets>=2.6.1 in /usr/local/lib/python3.10/dist-packages (2.18.0)\n", - "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (3.13.1)\n", - "Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (1.25.2)\n", - "Requirement already satisfied: pyarrow>=12.0.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (14.0.2)\n", - "Requirement already satisfied: pyarrow-hotfix in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (0.6)\n", - "Requirement already satisfied: dill<0.3.9,>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (0.3.8)\n", - "Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (1.5.3)\n", - "Requirement already satisfied: requests>=2.19.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (2.31.0)\n", - "Requirement already satisfied: tqdm>=4.62.1 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (4.66.2)\n", - "Requirement already satisfied: xxhash in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (3.4.1)\n", - "Requirement already satisfied: multiprocess in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (0.70.16)\n", - "Requirement already satisfied: fsspec[http]<=2024.2.0,>=2023.1.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (2023.6.0)\n", - "Requirement already satisfied: aiohttp in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (3.9.3)\n", - "Requirement already satisfied: huggingface-hub>=0.19.4 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (0.20.3)\n", - "Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (23.2)\n", - "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (6.0.1)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (4.0.3)\n", - "Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.19.4->datasets>=2.6.1) (4.10.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (2.0.7)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (2024.2.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets>=2.6.1) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets>=2.6.1) (2023.4)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.1->pandas->datasets>=2.6.1) (1.16.0)\n", - "Requirement already satisfied: sentence-transformers>=2.2.0 in /usr/local/lib/python3.10/dist-packages (2.5.1)\n", - "Requirement already satisfied: transformers<5.0.0,>=4.32.0 in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (4.38.2)\n", - "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (4.66.2)\n", - "Requirement already satisfied: torch>=1.11.0 in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (2.1.0+cu121)\n", - "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (1.25.2)\n", - "Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (1.2.2)\n", - "Requirement already satisfied: scipy in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (1.11.4)\n", - "Requirement already satisfied: huggingface-hub>=0.15.1 in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (0.20.3)\n", - "Requirement already satisfied: Pillow in /usr/local/lib/python3.10/dist-packages (from sentence-transformers>=2.2.0) (9.4.0)\n", - "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (3.13.1)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (2023.6.0)\n", - "Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (2.31.0)\n", - "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (6.0.1)\n", - "Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (4.10.0)\n", - "Requirement already satisfied: packaging>=20.9 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (23.2)\n", - "Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers>=2.2.0) (1.12)\n", - "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers>=2.2.0) (3.2.1)\n", - "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers>=2.2.0) (3.1.3)\n", - "Requirement already satisfied: triton==2.1.0 in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers>=2.2.0) (2.1.0)\n", - "Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.10/dist-packages (from transformers<5.0.0,>=4.32.0->sentence-transformers>=2.2.0) (2023.12.25)\n", - "Requirement already satisfied: tokenizers<0.19,>=0.14 in /usr/local/lib/python3.10/dist-packages (from transformers<5.0.0,>=4.32.0->sentence-transformers>=2.2.0) (0.15.2)\n", - "Requirement already satisfied: safetensors>=0.4.1 in /usr/local/lib/python3.10/dist-packages (from transformers<5.0.0,>=4.32.0->sentence-transformers>=2.2.0) (0.4.2)\n", - "Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->sentence-transformers>=2.2.0) (1.3.2)\n", - "Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->sentence-transformers>=2.2.0) (3.3.0)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->torch>=1.11.0->sentence-transformers>=2.2.0) (2.1.5)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (2.0.7)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub>=0.15.1->sentence-transformers>=2.2.0) (2024.2.2)\n", - "Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.10/dist-packages (from sympy->torch>=1.11.0->sentence-transformers>=2.2.0) (1.3.0)\n" - ] - } - ], - "source": [ - "%%bash\n", - "\n", - "pip install haystack-ai\n", - "pip install \"datasets>=2.6.1\"\n", - "pip install \"sentence-transformers>=3.0.0\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wl_jYERtO-qa" - }, - "source": [ - "### Enabling Telemetry\n", - "\n", - "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/enabling-telemetry) for more details." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "A76B4S49O-qa" - }, - "outputs": [], - "source": [ - "from haystack.telemetry import tutorial_running\n", - "\n", - "tutorial_running(27)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_lvfew16O-qa" - }, - "source": [ - "## Fetching and Indexing Documents\n", - "\n", - "You'll start creating your question answering system by downloading the data and indexing the data with its embeddings to a DocumentStore. \n", - "\n", - "In this tutorial, you will take a simple approach to writing documents and their embeddings into the DocumentStore. For a full indexing pipeline with preprocessing, cleaning and splitting, check out our tutorial on [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline).\n", - "\n", - "\n", - "### Initializing the DocumentStore\n", - "\n", - "Initialize a DocumentStore to index your documents. A DocumentStore stores the Documents that the question answering system uses to find answers to your questions. In this tutorial, you'll be using the `InMemoryDocumentStore`." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "CbVN-s5LO-qa" - }, - "outputs": [], - "source": [ - "from haystack.document_stores.in_memory import InMemoryDocumentStore\n", - "\n", - "document_store = InMemoryDocumentStore()" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "2OvkPji9O-qX" + }, + "source": [ + "# Tutorial: Creating Your First QA Pipeline with Retrieval-Augmentation\n", + "\n", + "- **Level**: Beginner\n", + "- **Time to complete**: 10 minutes\n", + "- **Components Used**: [`InMemoryDocumentStore`](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [`SentenceTransformersDocumentEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder), [`SentenceTransformersTextEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder), [`InMemoryEmbeddingRetriever`](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [`PromptBuilder`](https://docs.haystack.deepset.ai/docs/promptbuilder), [`OpenAIGenerator`](https://docs.haystack.deepset.ai/docs/openaigenerator)\n", + "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys).\n", + "- **Goal**: After completing this tutorial, you'll have learned the new prompt syntax and how to use PromptBuilder and OpenAIGenerator to build a generative question-answering pipeline with retrieval-augmentation.\n", + "\n", + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LFqHcXYPO-qZ" + }, + "source": [ + "## Overview\n", + "\n", + "This tutorial shows you how to create a generative question-answering pipeline using the retrieval-augmentation ([RAG](https://www.deepset.ai/blog/llms-retrieval-augmentation)) approach with Haystack 2.0. The process involves four main components: [SentenceTransformersTextEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder) for creating an embedding for the user query, [InMemoryBM25Retriever](https://docs.haystack.deepset.ai/docs/inmemorybm25retriever) for fetching relevant documents, [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) for creating a template prompt, and [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) for generating responses.\n", + "\n", + "For this tutorial, you'll use the Wikipedia pages of [Seven Wonders of the Ancient World](https://en.wikipedia.org/wiki/Wonders_of_the_World) as Documents, but you can replace them with any text you want.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QXjVlbPiO-qZ" + }, + "source": [ + "## Preparing the Colab Environment\n", + "\n", + "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", + "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/logging)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Kww5B_vXO-qZ" + }, + "source": [ + "## Installing Haystack\n", + "\n", + "Install Haystack and other required packages with `pip`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "markdown", - "metadata": { - "id": "yL8nuJdWO-qa" - }, - "source": [ - "> `InMemoryDocumentStore` is the simplest DocumentStore to get started with. It requires no external dependencies and it's a good option for smaller projects and debugging. But it doesn't scale up so well to larger Document collections, so it's not a good choice for production systems. To learn more about the different types of external databases that Haystack supports, see [DocumentStore Integrations](https://haystack.deepset.ai/integrations?type=Document+Store)." - ] + "id": "UQbU8GUfO-qZ", + "outputId": "c33579e9-5557-43bd-a3c5-63b8373770c7" + }, + "outputs": [], + "source": [ + "%%bash\n", + "\n", + "pip install haystack-ai\n", + "pip install \"datasets>=2.6.1\"\n", + "pip install \"sentence-transformers>=3.0.0\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wl_jYERtO-qa" + }, + "source": [ + "### Enabling Telemetry\n", + "\n", + "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/enabling-telemetry) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "A76B4S49O-qa" + }, + "outputs": [], + "source": [ + "from haystack.telemetry import tutorial_running\n", + "\n", + "tutorial_running(27)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_lvfew16O-qa" + }, + "source": [ + "## Fetching and Indexing Documents\n", + "\n", + "You'll start creating your question answering system by downloading the data and indexing the data with its embeddings to a DocumentStore. \n", + "\n", + "In this tutorial, you will take a simple approach to writing documents and their embeddings into the DocumentStore. For a full indexing pipeline with preprocessing, cleaning and splitting, check out our tutorial on [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline).\n", + "\n", + "\n", + "### Initializing the DocumentStore\n", + "\n", + "Initialize a DocumentStore to index your documents. A DocumentStore stores the Documents that the question answering system uses to find answers to your questions. In this tutorial, you'll be using the `InMemoryDocumentStore`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "CbVN-s5LO-qa" + }, + "outputs": [], + "source": [ + "from haystack.document_stores.in_memory import InMemoryDocumentStore\n", + "\n", + "document_store = InMemoryDocumentStore()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yL8nuJdWO-qa" + }, + "source": [ + "> `InMemoryDocumentStore` is the simplest DocumentStore to get started with. It requires no external dependencies and it's a good option for smaller projects and debugging. But it doesn't scale up so well to larger Document collections, so it's not a good choice for production systems. To learn more about the different types of external databases that Haystack supports, see [DocumentStore Integrations](https://haystack.deepset.ai/integrations?type=Document+Store)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XvLVaFHTO-qb" + }, + "source": [ + "The DocumentStore is now ready. Now it's time to fill it with some Documents." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HryYZP9ZO-qb" + }, + "source": [ + "### Fetch the Data\n", + "\n", + "You'll use the Wikipedia pages of [Seven Wonders of the Ancient World](https://en.wikipedia.org/wiki/Wonders_of_the_World) as Documents. We preprocessed the data and uploaded to a Hugging Face Space: [Seven Wonders](https://huggingface.co/datasets/bilgeyucel/seven-wonders). Thus, you don't need to perform any additional cleaning or splitting.\n", + "\n", + "Fetch the data and convert it into Haystack Documents:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "INdC3WvLO-qb", + "outputId": "1af43d0f-2999-4de4-d152-b3cca9fb49e6" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "XvLVaFHTO-qb" - }, - "source": [ - "The DocumentStore is now ready. Now it's time to fill it with some Documents." - ] + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_token.py:88: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from datasets import load_dataset\n", + "from haystack import Document\n", + "\n", + "dataset = load_dataset(\"bilgeyucel/seven-wonders\", split=\"train\")\n", + "docs = [Document(content=doc[\"content\"], meta=doc[\"meta\"]) for doc in dataset]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "czMjWwnxPA-3" + }, + "source": [ + "### Initalize a Document Embedder\n", + "\n", + "To store your data in the DocumentStore with embeddings, initialize a [SentenceTransformersDocumentEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder) with the model name and call `warm_up()` to download the embedding model.\n", + "\n", + "> If you'd like, you can use a different [Embedder](https://docs.haystack.deepset.ai/docs/embedders) for your documents." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "EUmAH9sEn3R7", + "outputId": "ee54b59b-4d4a-45eb-c1a9-0b7b248f1dd4" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "HryYZP9ZO-qb" - }, - "source": [ - "### Fetch the Data\n", - "\n", - "You'll use the Wikipedia pages of [Seven Wonders of the Ancient World](https://en.wikipedia.org/wiki/Wonders_of_the_World) as Documents. We preprocessed the data and uploaded to a Hugging Face Space: [Seven Wonders](https://huggingface.co/datasets/bilgeyucel/seven-wonders). Thus, you don't need to perform any additional cleaning or splitting.\n", - "\n", - "Fetch the data and convert it into Haystack Documents:" - ] + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/torch/_utils.py:831: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()\n", + " return self.fget.__get__(instance, owner)()\n" + ] + } + ], + "source": [ + "from haystack.components.embedders import SentenceTransformersDocumentEmbedder\n", + "\n", + "doc_embedder = SentenceTransformersDocumentEmbedder(model=\"sentence-transformers/all-MiniLM-L6-v2\")\n", + "doc_embedder.warm_up()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9y4iJE_SrS4K" + }, + "source": [ + "### Write Documents to the DocumentStore\n", + "\n", + "Run the `doc_embedder` with the Documents. The embedder will create embeddings for each document and save these embeddings in Document object's `embedding` field. Then, you can write the Documents to the DocumentStore with `write_documents()` method." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 66, + "referenced_widgets": [ + "7d482188c12d4a7886f20a65d3402c59", + "2a3ec74419ae4a02ac0210db66133415", + "ddeff9a822404adbbc3cad97a939bc0c", + "36d341ab3a044709b5af2e8ab97559bc", + "88fc33e1ab78405e911b5eafa512c935", + "91e5d4b0ede848319ef0d3b558d57d19", + "d2428c21707d43f2b6f07bfafbace8bb", + "7fdb2c859e454e72888709a835f7591e", + "6b8334e071a3438397ba6435aac69f58", + "5f5cfa425cac4d37b2ea29e53b4ed900", + "3c59a82dac5c476b9a3e3132094e1702" + ] }, + "id": "ETpQKftLplqh", + "outputId": "b9c8658c-90c8-497c-e765-97487c0daf8e" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "INdC3WvLO-qb", - "outputId": "1af43d0f-2999-4de4-d152-b3cca9fb49e6" + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7d482188c12d4a7886f20a65d3402c59", + "version_major": 2, + "version_minor": 0 }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_token.py:88: UserWarning: \n", - "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", - "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", - "You will be able to reuse this secret in all of your notebooks.\n", - "Please note that authentication is recommended but still optional to access public models or datasets.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "from datasets import load_dataset\n", - "from haystack import Document\n", - "\n", - "dataset = load_dataset(\"bilgeyucel/seven-wonders\", split=\"train\")\n", - "docs = [Document(content=doc[\"content\"], meta=doc[\"meta\"]) for doc in dataset]" + "text/plain": [ + "Batches: 0%| | 0/5 [00:00 If you'd like, you can use a different [Embedder](https://docs.haystack.deepset.ai/docs/embedders) for your documents." + "data": { + "text/plain": [ + "151" ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docs_with_embeddings = doc_embedder.run(docs)\n", + "document_store.write_documents(docs_with_embeddings[\"documents\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IdojTxg6uubn" + }, + "source": [ + "## Building the RAG Pipeline\n", + "\n", + "The next step is to build a [Pipeline](https://docs.haystack.deepset.ai/docs/pipelines) to generate answers for the user query following the RAG approach. To create the pipeline, you first need to initialize each component, add them to your pipeline, and connect them." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0uyV6-u-u56P" + }, + "source": [ + "### Initialize a Text Embedder\n", + "\n", + "Initialize a text embedder to create an embedding for the user query. The created embedding will later be used by the Retriever to retrieve relevant documents from the DocumentStore.\n", + "\n", + "> ⚠️ Notice that you used `sentence-transformers/all-MiniLM-L6-v2` model to create embeddings for your documents before. This is why you need to use the same model to embed the user queries." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "LyJY2yW628dl" + }, + "outputs": [], + "source": [ + "from haystack.components.embedders import SentenceTransformersTextEmbedder\n", + "\n", + "text_embedder = SentenceTransformersTextEmbedder(model=\"sentence-transformers/all-MiniLM-L6-v2\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0_cj-5m-O-qb" + }, + "source": [ + "### Initialize the Retriever\n", + "\n", + "Initialize a [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever) and make it use the InMemoryDocumentStore you initialized earlier in this tutorial. This Retriever will get the relevant documents to the query." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "id": "-uo-6fjiO-qb" + }, + "outputs": [], + "source": [ + "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", + "\n", + "retriever = InMemoryEmbeddingRetriever(document_store)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6CEuQpB7O-qb" + }, + "source": [ + "### Define a Template Prompt\n", + "\n", + "Create a custom prompt for a generative question answering task using the RAG approach. The prompt should take in two parameters: `documents`, which are retrieved from a document store, and a `question` from the user. Use the Jinja2 looping syntax to combine the content of the retrieved documents in the prompt.\n", + "\n", + "Next, initialize a [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) instance with your prompt template. The PromptBuilder, when given the necessary values, will automatically fill in the variable values and generate a complete prompt. This approach allows for a more tailored and effective question-answering experience." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "ObahTh45FqOT" + }, + "outputs": [], + "source": [ + "from haystack.components.builders import PromptBuilder\n", + "\n", + "template = \"\"\"\n", + "Given the following information, answer the question.\n", + "\n", + "Context:\n", + "{% for document in documents %}\n", + " {{ document.content }}\n", + "{% endfor %}\n", + "\n", + "Question: {{question}}\n", + "Answer:\n", + "\"\"\"\n", + "\n", + "prompt_builder = PromptBuilder(template=template)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HR14lbfcFtXj" + }, + "source": [ + "### Initialize a Generator\n", + "\n", + "\n", + "Generators are the components that interact with large language models (LLMs). Now, set `OPENAI_API_KEY` environment variable and initialize a [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/OpenAIGenerator) that can communicate with OpenAI GPT models. As you initialize, provide a model name:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "SavE_FAqfApo", + "outputId": "1afbf2e8-ae63-41ff-c37f-5123b2103356" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "EUmAH9sEn3R7", - "outputId": "ee54b59b-4d4a-45eb-c1a9-0b7b248f1dd4" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.10/dist-packages/torch/_utils.py:831: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()\n", - " return self.fget.__get__(instance, owner)()\n" - ] - } - ], - "source": [ - "from haystack.components.embedders import SentenceTransformersDocumentEmbedder\n", - "\n", - "doc_embedder = SentenceTransformersDocumentEmbedder(model=\"sentence-transformers/all-MiniLM-L6-v2\")\n", - "doc_embedder.warm_up()" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "Enter OpenAI API key: ··········\n" + ] + } + ], + "source": [ + "import os\n", + "from getpass import getpass\n", + "from haystack.components.generators import OpenAIGenerator\n", + "\n", + "if \"OPENAI_API_KEY\" not in os.environ:\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", + "generator = OpenAIGenerator(model=\"gpt-4o-mini\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nenbo2SvycHd" + }, + "source": [ + "> You can replace `OpenAIGenerator` in your pipeline with another `Generator`. Check out the full list of generators [here](https://docs.haystack.deepset.ai/docs/generators)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1bfHwOQwycHe" + }, + "source": [ + "### Build the Pipeline\n", + "\n", + "To build a pipeline, add all components to your pipeline and connect them. Create connections from `text_embedder`'s \"embedding\" output to \"query_embedding\" input of `retriever`, from `retriever` to `prompt_builder` and from `prompt_builder` to `llm`. Explicitly connect the output of `retriever` with \"documents\" input of the `prompt_builder` to make the connection obvious as `prompt_builder` has two inputs (\"documents\" and \"question\").\n", + "\n", + "For more information on pipelines and creating connections, refer to [Creating Pipelines](https://docs.haystack.deepset.ai/docs/creating-pipelines) documentation." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 }, + "id": "f6NFmpjEO-qb", + "outputId": "89fd1b48-5189-4401-9cf8-15f55c503676" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "9y4iJE_SrS4K" - }, - "source": [ - "### Write Documents to the DocumentStore\n", - "\n", - "Run the `doc_embedder` with the Documents. The embedder will create embeddings for each document and save these embeddings in Document object's `embedding` field. Then, you can write the Documents to the DocumentStore with `write_documents()` method." + "data": { + "image/jpeg": "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1KWhKglcriWW/wD7Bn9f/HH/AIo+M6WuzhSIczGpcqJIbUy8w8cZaHEKLZSVJN3YyMjMjI/HcRe5q7kWRa7wsZx96srcRg4xGya5eu6iUmcaH33W2mEpU6ycZfBhxR962pSdtjSQ6LTTtWW+UHp5NyvADxPHdQUcset49wmcg1nHXJablINppTKnGW1qTx7xPTY1EA7PBey/pnprk9NkOOadWcC1pjfOtcVcOPIhk82bbiGmnJakIQaVGXAk8S6GREZEY52OdnDTfC84ayum04kR7lh56TGWmbzjRHXd+9cjxlyDZYWrkrdTaEme59eo7qi7S2muRypbMTJ22248B608snRX4kSRDZMiekx5DzaWpDSOSeTjSlpLkRmfUcij7QWD5BHp34thYNNXFkiorlTqWdE8skLZceQTXesp5oNtlxXelu3sk/SAY1G7OencHPlZnF06nRL5c720UqNYm1GVL8fKDiplEwbpn1NZt7mfUz3HEsuy7pjb3b9rK02sFyXrRF33SbVaI7c9LpO+UtsJlk024a0kalISRr3USuRKMjya77S2nWP2LlfKu5Ltgiyk1BQ4VTMlvuy47TTrzTbbTKlOGhDzaj4EZdVERmaVEX84P2nNNdRratrqDIlS37KI7OhOvV0qMxKaa277u3nWktrU3yLmglGtHXkRbGA/ido9iFli+T47JwWwcp8ltjvLWN5eReUzO8ac73kUnkj02Gj4oMk+jttsZ79nbYDj19k1tf2eDybCztqZOPTlSnWXGpEBLjjhMLaN82zLk84Znx3PlsZmRERdLH7WGlcqHaTEZOpMCurnrdya7WS2478JpSUuyIzqmiRKbSpxBGpg1kXNP3SHByrta4JRYDneSV7tlcvYjEakyqpFVLYkum9yKLwS4yRqbeWk0peSRt9DUatiMwHAptB9MtH8ayeTAwK1g1cmmfg2Ju2rko01/A1OMtd5KWbSNi961x8C28CGurHsjHZKjPYM637fJcwhpTGPn5BKPyJCoxxjL3/1z6yZp+ucvu+PUTL2stZ8jp+zzXZFircSG3kEhqll1eSUkxqUtMlfcud2lxcdxhTaO+XycaUSiQkySRHufn1AW9S+2N2JJsnKHl1+QtlkzhvWjEduwZYedN5t83UtIeJDThutNrNxtKVGpO+/U98w019kh7J2kFA9TYl7eVUF+SuY/wAq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", + "text/plain": [ + "" ] + }, + "metadata": {}, + "output_type": "display_data" }, { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 66, - "referenced_widgets": [ - "7d482188c12d4a7886f20a65d3402c59", - "2a3ec74419ae4a02ac0210db66133415", - "ddeff9a822404adbbc3cad97a939bc0c", - "36d341ab3a044709b5af2e8ab97559bc", - "88fc33e1ab78405e911b5eafa512c935", - "91e5d4b0ede848319ef0d3b558d57d19", - "d2428c21707d43f2b6f07bfafbace8bb", - "7fdb2c859e454e72888709a835f7591e", - "6b8334e071a3438397ba6435aac69f58", - "5f5cfa425cac4d37b2ea29e53b4ed900", - "3c59a82dac5c476b9a3e3132094e1702" - ] - }, - "id": "ETpQKftLplqh", - "outputId": "b9c8658c-90c8-497c-e765-97487c0daf8e" - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7d482188c12d4a7886f20a65d3402c59", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Batches: 0%| | 0/5 [00:00 ⚠️ Notice that you used `sentence-transformers/all-MiniLM-L6-v2` model to create embeddings for your documents before. This is why you need to use the same model to embed the user queries." - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "The Rhodes Statue was a 33-meter tall statue of the Greek sun-god Helios, featuring a structure built with iron tie bars covered in brass plates to form the skin. The head of the statue was described as having curly hair with spikes of bronze or silver flame radiating, similar to contemporary Rhodian coins.\n" + ] + } + ], + "source": [ + "question = \"What does Rhodes Statue look like?\"\n", + "\n", + "response = basic_rag_pipeline.run({\"text_embedder\": {\"text\": question}, \"prompt_builder\": {\"question\": question}})\n", + "\n", + "print(response[\"llm\"][\"replies\"][0])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IWQN-aoGO-qc" + }, + "source": [ + "Here are some other example questions to test:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "id": "_OHUQ5xxO-qc" + }, + "outputs": [], + "source": [ + "examples = [\n", + " \"Where is Gardens of Babylon?\",\n", + " \"Why did people build Great Pyramid of Giza?\",\n", + " \"What does Rhodes Statue look like?\",\n", + " \"Why did people visit the Temple of Artemis?\",\n", + " \"What is the importance of Colossus of Rhodes?\",\n", + " \"What happened to the Tomb of Mausolus?\",\n", + " \"How did Colossus of Rhodes collapse?\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XueCK3y4O-qc" + }, + "source": [ + "## What's next\n", + "\n", + "🎉 Congratulations! You've learned how to create a generative QA system for your documents with the RAG approach.\n", + "\n", + "If you liked this tutorial, you may also enjoy:\n", + "- [Filtering Documents with Metadata](https://haystack.deepset.ai/tutorials/31_metadata_filtering)\n", + "- [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline)\n", + "- [Creating a Hybrid Retrieval Pipeline](https://haystack.deepset.ai/tutorials/33_hybrid_retrieval)\n", + "\n", + "To stay up to date on the latest Haystack developments, you can [subscribe to our newsletter](https://landing.deepset.ai/haystack-community-updates) and [join Haystack discord community](https://discord.gg/haystack).\n", + "\n", + "Thanks for reading!" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.9.6" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "1a820c06a7a049d8b6c9ff300284d06e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_d0cfe5dacdfc431a91b4c4741123e2d0", + "placeholder": "​", + "style": "IPY_MODEL_e7f1e1a14bb740d18827dd78bbe7b2e3", + "value": "Batches: 100%" + } }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "LyJY2yW628dl" - }, - "outputs": [], - "source": [ - "from haystack.components.embedders import SentenceTransformersTextEmbedder\n", - "\n", - "text_embedder = SentenceTransformersTextEmbedder(model=\"sentence-transformers/all-MiniLM-L6-v2\")" - ] + "2a3ec74419ae4a02ac0210db66133415": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_91e5d4b0ede848319ef0d3b558d57d19", + "placeholder": "​", + "style": "IPY_MODEL_d2428c21707d43f2b6f07bfafbace8bb", + "value": "Batches: 100%" + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "0_cj-5m-O-qb" - }, - "source": [ - "### Initialize the Retriever\n", - "\n", - "Initialize a [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever) and make it use the InMemoryDocumentStore you initialized earlier in this tutorial. This Retriever will get the relevant documents to the query." - ] + "2bc341a780f7498ba9cd475468841bb5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "-uo-6fjiO-qb" - }, - "outputs": [], - "source": [ - "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", - "\n", - "retriever = InMemoryEmbeddingRetriever(document_store)" - ] + "36d341ab3a044709b5af2e8ab97559bc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_5f5cfa425cac4d37b2ea29e53b4ed900", + "placeholder": "​", + "style": "IPY_MODEL_3c59a82dac5c476b9a3e3132094e1702", + "value": " 5/5 [00:01<00:00,  3.35it/s]" + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "6CEuQpB7O-qb" - }, - "source": [ - "### Define a Template Prompt\n", - "\n", - "Create a custom prompt for a generative question answering task using the RAG approach. The prompt should take in two parameters: `documents`, which are retrieved from a document store, and a `question` from the user. Use the Jinja2 looping syntax to combine the content of the retrieved documents in the prompt.\n", - "\n", - "Next, initialize a [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) instance with your prompt template. The PromptBuilder, when given the necessary values, will automatically fill in the variable values and generate a complete prompt. This approach allows for a more tailored and effective question-answering experience." - ] + "39a68d9a5c274e2dafaa2d1f86eea768": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "ObahTh45FqOT" - }, - "outputs": [], - "source": [ - "from haystack.components.builders import PromptBuilder\n", - "\n", - "template = \"\"\"\n", - "Given the following information, answer the question.\n", - "\n", - "Context:\n", - "{% for document in documents %}\n", - " {{ document.content }}\n", - "{% endfor %}\n", - "\n", - "Question: {{question}}\n", - "Answer:\n", - "\"\"\"\n", - "\n", - "prompt_builder = PromptBuilder(template=template)" - ] + "3c59a82dac5c476b9a3e3132094e1702": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "HR14lbfcFtXj" - }, - "source": [ - "### Initialize a Generator\n", - "\n", - "\n", - "Generators are the components that interact with large language models (LLMs). Now, set `OPENAI_API_KEY` environment variable and initialize a [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/OpenAIGenerator) that can communicate with OpenAI GPT models. As you initialize, provide a model name:" - ] + "3fda06f905b445a488efdd2dd08c0939": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "SavE_FAqfApo", - "outputId": "1afbf2e8-ae63-41ff-c37f-5123b2103356" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Enter OpenAI API key: ··········\n" - ] - } + "4e6e97b6d54f4f80bb7e8b25aba8e616": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_1a820c06a7a049d8b6c9ff300284d06e", + "IPY_MODEL_58ff4e0603a74978a134f63533859be5", + "IPY_MODEL_8bdb8bfae31d4f4cb6c3b0bf43120eed" ], - "source": [ - "import os\n", - "from getpass import getpass\n", - "from haystack.components.generators import OpenAIGenerator\n", - "\n", - "if \"OPENAI_API_KEY\" not in os.environ:\n", - " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", - "generator = OpenAIGenerator(model=\"gpt-4o-mini\")" - ] + "layout": "IPY_MODEL_39a68d9a5c274e2dafaa2d1f86eea768" + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "nenbo2SvycHd" - }, - "source": [ - "> You can replace `OpenAIGenerator` in your pipeline with another `Generator`. Check out the full list of generators [here](https://docs.haystack.deepset.ai/docs/generators)." - ] + "58ff4e0603a74978a134f63533859be5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_3fda06f905b445a488efdd2dd08c0939", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_2bc341a780f7498ba9cd475468841bb5", + "value": 1 + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "1bfHwOQwycHe" - }, - "source": [ - "### Build the Pipeline\n", - "\n", - "To build a pipeline, add all components to your pipeline and connect them. Create connections from `text_embedder`'s \"embedding\" output to \"query_embedding\" input of `retriever`, from `retriever` to `prompt_builder` and from `prompt_builder` to `llm`. Explicitly connect the output of `retriever` with \"documents\" input of the `prompt_builder` to make the connection obvious as `prompt_builder` has two inputs (\"documents\" and \"question\").\n", - "\n", - "For more information on pipelines and creating connections, refer to [Creating Pipelines](https://docs.haystack.deepset.ai/docs/creating-pipelines) documentation." - ] + "5f5cfa425cac4d37b2ea29e53b4ed900": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "f6NFmpjEO-qb", - "outputId": "89fd1b48-5189-4401-9cf8-15f55c503676" - }, - "outputs": [ - { - "data": { - "image/jpeg": "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1KWhKglcriWW/wD7Bn9f/HH/AIo+M6WuzhSIczGpcqJIbUy8w8cZaHEKLZSVJN3YyMjMjI/HcRe5q7kWRa7wsZx96srcRg4xGya5eu6iUmcaH33W2mEpU6ycZfBhxR962pSdtjSQ6LTTtWW+UHp5NyvADxPHdQUcset49wmcg1nHXJablINppTKnGW1qTx7xPTY1EA7PBey/pnprk9NkOOadWcC1pjfOtcVcOPIhk82bbiGmnJakIQaVGXAk8S6GREZEY52OdnDTfC84ayum04kR7lh56TGWmbzjRHXd+9cjxlyDZYWrkrdTaEme59eo7qi7S2muRypbMTJ22248B608snRX4kSRDZMiekx5DzaWpDSOSeTjSlpLkRmfUcij7QWD5BHp34thYNNXFkiorlTqWdE8skLZceQTXesp5oNtlxXelu3sk/SAY1G7OencHPlZnF06nRL5c720UqNYm1GVL8fKDiplEwbpn1NZt7mfUz3HEsuy7pjb3b9rK02sFyXrRF33SbVaI7c9LpO+UtsJlk024a0kalISRr3USuRKMjya77S2nWP2LlfKu5Ltgiyk1BQ4VTMlvuy47TTrzTbbTKlOGhDzaj4EZdVERmaVEX84P2nNNdRratrqDIlS37KI7OhOvV0qMxKaa277u3nWktrU3yLmglGtHXkRbGA/ido9iFli+T47JwWwcp8ltjvLWN5eReUzO8ac73kUnkj02Gj4oMk+jttsZ79nbYDj19k1tf2eDybCztqZOPTlSnWXGpEBLjjhMLaN82zLk84Znx3PlsZmRERdLH7WGlcqHaTEZOpMCurnrdya7WS2478JpSUuyIzqmiRKbSpxBGpg1kXNP3SHByrta4JRYDneSV7tlcvYjEakyqpFVLYkum9yKLwS4yRqbeWk0peSRt9DUatiMwHAptB9MtH8ayeTAwK1g1cmmfg2Ju2rko01/A1OMtd5KWbSNi961x8C28CGurHsjHZKjPYM637fJcwhpTGPn5BKPyJCoxxjL3/1z6yZp+ucvu+PUTL2stZ8jp+zzXZFircSG3kEhqll1eSUkxqUtMlfcud2lxcdxhTaO+XycaUSiQkySRHufn1AW9S+2N2JJsnKHl1+QtlkzhvWjEduwZYedN5t83UtIeJDThutNrNxtKVGpO+/U98w019kh7J2kFA9TYl7eVUF+SuY/wAq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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } + "6b8334e071a3438397ba6435aac69f58": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7d482188c12d4a7886f20a65d3402c59": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_2a3ec74419ae4a02ac0210db66133415", + "IPY_MODEL_ddeff9a822404adbbc3cad97a939bc0c", + "IPY_MODEL_36d341ab3a044709b5af2e8ab97559bc" ], - "source": [ - "from haystack import Pipeline\n", - "\n", - "basic_rag_pipeline = Pipeline()\n", - "# Add components to your pipeline\n", - "basic_rag_pipeline.add_component(\"text_embedder\", text_embedder)\n", - "basic_rag_pipeline.add_component(\"retriever\", retriever)\n", - "basic_rag_pipeline.add_component(\"prompt_builder\", prompt_builder)\n", - "basic_rag_pipeline.add_component(\"llm\", generator)\n", - "\n", - "# Now, connect the components to each other\n", - "basic_rag_pipeline.connect(\"text_embedder.embedding\", \"retriever.query_embedding\")\n", - "basic_rag_pipeline.connect(\"retriever\", \"prompt_builder.documents\")\n", - "basic_rag_pipeline.connect(\"prompt_builder\", \"llm\")" - ] + "layout": "IPY_MODEL_88fc33e1ab78405e911b5eafa512c935" + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "6NqyLhx7O-qc" - }, - "source": [ - "That's it! Your RAG pipeline is ready to generate answers to questions!" - ] + "7fdb2c859e454e72888709a835f7591e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } }, - { - "cell_type": "markdown", - "metadata": { - "id": "DBAyF5tVO-qc" - }, - "source": [ - "## Asking a Question\n", - "\n", - "When asking a question, use the `run()` method of the pipeline. Make sure to provide the question to both the `text_embedder` and the `prompt_builder`. This ensures that the `{{question}}` variable in the template prompt gets replaced with your specific question." - ] + "88fc33e1ab78405e911b5eafa512c935": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 86, - "referenced_widgets": [ - "4e6e97b6d54f4f80bb7e8b25aba8e616", - "1a820c06a7a049d8b6c9ff300284d06e", - "58ff4e0603a74978a134f63533859be5", - "8bdb8bfae31d4f4cb6c3b0bf43120eed", - "39a68d9a5c274e2dafaa2d1f86eea768", - "d0cfe5dacdfc431a91b4c4741123e2d0", - "e7f1e1a14bb740d18827dd78bbe7b2e3", - "3fda06f905b445a488efdd2dd08c0939", - "2bc341a780f7498ba9cd475468841bb5", - "d7218475e23b420a8c03d00ca4ab8718", - "a694abaf765f4d1b82fa0138e59c6793" - ] - }, - "id": "Vnt283M5O-qc", - "outputId": "d2843a73-3ad5-4daa-8d1e-a58de7aa2bb0" - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4e6e97b6d54f4f80bb7e8b25aba8e616", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Batches: 0%| | 0/1 [00:00 This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)..\n", - "\n", - "## Overview\n", - "This tutorial demonstrates how to use Haystack 2.0's advanced [looping pipelines](https://docs.haystack.deepset.ai/docs/pipelines#loops) with LLMs for more dynamic and flexible data processing. You'll learn how to extract structured data from unstructured data using an LLM, and to validate the generated output against a predefined schema.\n", - "\n", - "This tutorial uses `gpt-4o-mini` to change unstructured passages into JSON outputs that follow the [Pydantic](https://github.com/pydantic/pydantic) schema. It uses a custom OutputValidator component to validate the JSON and loop back to make corrections, if necessary." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jmiAHh1oGsKI" - }, - "source": [ - "## Preparing the Colab Environment\n", - "\n", - "Enable the debug mode of logging:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Vor9IHuNRvEh" - }, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "logging.basicConfig()\n", - "logging.getLogger(\"canals.pipeline.pipeline\").setLevel(logging.DEBUG)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ljbWiyJkKiPw" - }, - "source": [ - "## Installing Dependencies\n", - "Install Haystack and [colorama](https://pypi.org/project/colorama/) with pip:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "kcc1AlLQd_jI", - "outputId": "efc4bbab-a9fe-46ee-d8af-9d86edacaf04" - }, - "outputs": [], - "source": [ - "%%bash\n", - "\n", - "pip install haystack-ai\n", - "pip install colorama" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nTA5fdvCLMKD" - }, - "source": [ - "### Enabling Telemetry\n", - "\n", - "Enable telemetry to let us know you're using this tutorial. (You can always opt out by commenting out this line). For details, see [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Apay3QSQLKdM" - }, - "outputs": [], - "source": [ - "from haystack.telemetry import tutorial_running\n", - "\n", - "tutorial_running(28)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Cmjfa8CiCeFl" - }, - "source": [ - "## Defining a Schema to Parse the JSON Object\n", - "\n", - "Define a simple JSON schema for the data you want to extract from a text passsage using the LLM. As the first step, define two [Pydantic models](https://docs.pydantic.dev/1.10/usage/models/), `City` and `CitiesData`, with suitable fields and types." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "xwKrDOOGdaAz" - }, - "outputs": [], - "source": [ - "from typing import List\n", - "from pydantic import BaseModel\n", - "\n", - "\n", - "class City(BaseModel):\n", - " name: str\n", - " country: str\n", - " population: int\n", - "\n", - "\n", - "class CitiesData(BaseModel):\n", - " cities: List[City]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zv-6-l_PCeFl" - }, - "source": [ - "> You can change these models according to the format you wish to extract from the text." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ouk1mAOUCeFl" - }, - "source": [ - "Then, generate a JSON schema from Pydantic models using `schema_json()`. You will later on use this schema in the prompt to instruct the LLM.\n", - "\n", - "To learn more about the JSON schemas, visit [Pydantic Schema](https://docs.pydantic.dev/1.10/usage/schema/). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "8Lg9_72jCeFl" - }, - "outputs": [], - "source": [ - "json_schema = CitiesData.schema_json(indent=2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "KvNhg0bP7kfg" - }, - "source": [ - "## Creating a Custom Component: OutputValidator\n", - "\n", - "`OutputValidator` is a custom component that validates if the JSON object the LLM generates complies with the provided [Pydantic model](https://docs.pydantic.dev/1.10/usage/models/). If it doesn't, OutputValidator returns an error message along with the incorrect JSON object to get it fixed in the next loop.\n", - "\n", - "For more details about custom components, see [Creating Custom Components](https://docs.haystack.deepset.ai/docs/custom-components)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "yr6D8RN2d7Vy" - }, - "outputs": [], - "source": [ - "import json\n", - "import random\n", - "import pydantic\n", - "from pydantic import ValidationError\n", - "from typing import Optional, List\n", - "from colorama import Fore\n", - "from haystack import component\n", - "\n", - "# Define the component input parameters\n", - "@component\n", - "class OutputValidator:\n", - " def __init__(self, pydantic_model: pydantic.BaseModel):\n", - " self.pydantic_model = pydantic_model\n", - " self.iteration_counter = 0\n", - "\n", - " # Define the component output\n", - " @component.output_types(valid_replies=List[str], invalid_replies=Optional[List[str]], error_message=Optional[str])\n", - " def run(self, replies: List[str]):\n", - "\n", - " self.iteration_counter += 1\n", - "\n", - " ## Try to parse the LLM's reply ##\n", - " # If the LLM's reply is a valid object, return `\"valid_replies\"`\n", - " try:\n", - " output_dict = json.loads(replies[0])\n", - " self.pydantic_model.parse_obj(output_dict)\n", - " print(\n", - " Fore.GREEN\n", - " + f\"OutputValidator at Iteration {self.iteration_counter}: Valid JSON from LLM - No need for looping: {replies[0]}\"\n", - " )\n", - " return {\"valid_replies\": replies}\n", - "\n", - " # If the LLM's reply is corrupted or not valid, return \"invalid_replies\" and the \"error_message\" for LLM to try again\n", - " except (ValueError, ValidationError) as e:\n", - " print(\n", - " Fore.RED\n", - " + f\"OutputValidator at Iteration {self.iteration_counter}: Invalid JSON from LLM - Let's try again.\\n\"\n", - " f\"Output from LLM:\\n {replies[0]} \\n\"\n", - " f\"Error from OutputValidator: {e}\"\n", - " )\n", - " return {\"invalid_replies\": replies, \"error_message\": str(e)}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vQ_TfSBkCeFm" - }, - "source": [ - "Then, create an OutputValidator instance with `CitiesData` that you have created before." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "bhPCLCBCCeFm" - }, - "outputs": [], - "source": [ - "output_validator = OutputValidator(pydantic_model=CitiesData)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "xcIWKjW4k42r" - }, - "source": [ - "## Creating the Prompt\n", - "\n", - "Write instructions for the LLM for converting a passage into a JSON format. Ensure the instructions explain how to identify and correct errors if the JSON doesn't match the required schema. Once you create the prompt, initialize PromptBuilder to use it. \n", - "\n", - "For information about Jinja2 template and PromptBuilder, see [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ohPpNALjdVKt" - }, - "outputs": [], - "source": [ - "from haystack.components.builders import PromptBuilder\n", - "\n", - "prompt_template = \"\"\"\n", - "Create a JSON object from the information present in this passage: {{passage}}.\n", - "Only use information that is present in the passage. Follow this JSON schema, but only return the actual instances without any additional schema definition:\n", - "{{schema}}\n", - "Make sure your response is a dict and not a list.\n", - "{% if invalid_replies and error_message %}\n", - " You already created the following output in a previous attempt: {{invalid_replies}}\n", - " However, this doesn't comply with the format requirements from above and triggered this Python exception: {{error_message}}\n", - " Correct the output and try again. Just return the corrected output without any extra explanations.\n", - "{% endif %}\n", - "\"\"\"\n", - "prompt_builder = PromptBuilder(template=prompt_template)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "KM9-Zq2FL7Nn" - }, - "source": [ - "## Initalizing the Generator\n", - "\n", - "[OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) generates\n", - "text using OpenAI's `gpt-4o-mini` model by default. Set the `OPENAI_API_KEY` variable and provide a model name to the Generator." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Z4cQteIgunUR" - }, - "outputs": [], - "source": [ - "import os\n", - "from getpass import getpass\n", - "\n", - "from haystack.components.generators import OpenAIGenerator\n", - "\n", - "if \"OPENAI_API_KEY\" not in os.environ:\n", - " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", - "generator = OpenAIGenerator()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zbotIOgXHkC5" - }, - "source": [ - "## Building the Pipeline\n", - "\n", - "Add all components to your pipeline and connect them. Add connections from `output_validator` back to the `prompt_builder` for cases where the produced JSON doesn't comply with the JSON schema. Set `max_runs_per_component` to avoid infinite looping." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "eFglN9YEv-1W" - }, - "outputs": [], - "source": [ - "from haystack import Pipeline\n", - "\n", - "pipeline = Pipeline(max_runs_per_component=5)\n", - "\n", - "# Add components to your pipeline\n", - "pipeline.add_component(instance=prompt_builder, name=\"prompt_builder\")\n", - "pipeline.add_component(instance=generator, name=\"llm\")\n", - "pipeline.add_component(instance=output_validator, name=\"output_validator\")\n", - "\n", - "# Now, connect the components to each other\n", - "pipeline.connect(\"prompt_builder\", \"llm\")\n", - "pipeline.connect(\"llm\", \"output_validator\")\n", - "# If a component has more than one output or input, explicitly specify the connections:\n", - "pipeline.connect(\"output_validator.invalid_replies\", \"prompt_builder.invalid_replies\")\n", - "pipeline.connect(\"output_validator.error_message\", \"prompt_builder.error_message\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "-UKW5wtIIT7w" - }, - "source": [ - "### Visualize the Pipeline\n", - "\n", - "Draw the pipeline with the [`draw()`](https://docs.haystack.deepset.ai/docs/drawing-pipeline-graphs) method to confirm the connections are correct. You can find the diagram in the Files section of this Colab." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "RZJg6YHId300" - }, - "outputs": [], - "source": [ - "pipeline.draw(\"auto-correct-pipeline.png\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kV_kexTjImpo" - }, - "source": [ - "## Testing the Pipeline\n", - "\n", - "Run the pipeline with an example passage that you want to convert into a JSON format and the `json_schema` you have created for `CitiesData`. For the given example passage, the generated JSON object should be like:\n", - "```json\n", - "{\n", - " \"cities\": [\n", - " {\n", - " \"name\": \"Berlin\",\n", - " \"country\": \"Germany\",\n", - " \"population\": 3850809\n", - " },\n", - " {\n", - " \"name\": \"Paris\",\n", - " \"country\": \"France\",\n", - " \"population\": 2161000\n", - " },\n", - " {\n", - " \"name\": \"Lisbon\",\n", - " \"country\": \"Portugal\",\n", - " \"population\": 504718\n", - " }\n", - " ]\n", - "}\n", - "```\n", - "The output of the LLM should be compliant with the `json_schema`. If the LLM doesn't generate the correct JSON object, it will loop back and try again." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "yIoMedb6eKia", - "outputId": "4a9ef924-cf26-4908-d83f-b0bc0dc03b54" - }, - "outputs": [], - "source": [ - "passage = \"Berlin is the capital of Germany. It has a population of 3,850,809. Paris, France's capital, has 2.161 million residents. Lisbon is the capital and the largest city of Portugal with the population of 504,718.\"\n", - "result = pipeline.run({\"prompt_builder\": {\"passage\": passage, \"schema\": json_schema}})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WWxmPgADS_Fa" - }, - "source": [ - "> If you encounter `PipelineMaxLoops: Maximum loops count (5) exceeded for component 'prompt_builder'.` error, consider increasing the maximum loop count or simply rerun the pipeline." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eWPawSjgSJAM" - }, - "source": [ - "### Print the Correct JSON\n", - "If you didn't get any error, you can now print the corrected JSON." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "BVO47gXQQnDC", - "outputId": "460a10d4-a69a-49cd-bbb2-fc4980907299" - }, - "outputs": [], - "source": [ - "valid_reply = result[\"output_validator\"][\"valid_replies\"][0]\n", - "valid_json = json.loads(valid_reply)\n", - "print(valid_json)" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "AVBtOVlNJ51C" + }, + "source": [ + "# Tutorial: Generating Structured Output with Loop-Based Auto-Correction\n", + "\n", + "- **Level**: Intermediate\n", + "- **Time to complete**: 15 minutes\n", + "- **Prerequisites**: You must have an API key from an active OpenAI account as this tutorial is using the gpt-4o-mini model by OpenAI.\n", + "- **Components Used**: `PromptBuilder`, `OpenAIGenerator`, `OutputValidator` (Custom component)\n", + "- **Goal**: After completing this tutorial, you will have built a system that extracts unstructured data, puts it in a JSON schema, and automatically corrects errors in the JSON output from a large language model (LLM) to make sure it follows the specified structure.\n", + "\n", + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", + "\n", + "## Overview\n", + "This tutorial demonstrates how to use Haystack 2.0's advanced [looping pipelines](https://docs.haystack.deepset.ai/docs/pipelines#loops) with LLMs for more dynamic and flexible data processing. You'll learn how to extract structured data from unstructured data using an LLM, and to validate the generated output against a predefined schema.\n", + "\n", + "This tutorial uses `gpt-4o-mini` to change unstructured passages into JSON outputs that follow the [Pydantic](https://github.com/pydantic/pydantic) schema. It uses a custom OutputValidator component to validate the JSON and loop back to make corrections, if necessary." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jmiAHh1oGsKI" + }, + "source": [ + "## Preparing the Colab Environment\n", + "\n", + "Enable the debug mode of logging:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "Vor9IHuNRvEh" + }, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "logging.basicConfig()\n", + "logging.getLogger(\"canals.pipeline.pipeline\").setLevel(logging.DEBUG)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ljbWiyJkKiPw" + }, + "source": [ + "## Installing Dependencies\n", + "Install Haystack and [colorama](https://pypi.org/project/colorama/) with pip:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "kcc1AlLQd_jI", + "outputId": "efc4bbab-a9fe-46ee-d8af-9d86edacaf04" + }, + "outputs": [], + "source": [ + "%%bash\n", + "\n", + "pip install -U haystack-ai\n", + "pip install colorama" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nTA5fdvCLMKD" + }, + "source": [ + "### Enabling Telemetry\n", + "\n", + "Enable telemetry to let us know you're using this tutorial. (You can always opt out by commenting out this line). For details, see [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Cmjfa8CiCeFl" + }, + "source": [ + "## Defining a Schema to Parse the JSON Object\n", + "\n", + "Define a simple JSON schema for the data you want to extract from a text passsage using the LLM. As the first step, define two [Pydantic models](https://docs.pydantic.dev/1.10/usage/models/), `City` and `CitiesData`, with suitable fields and types." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "xwKrDOOGdaAz" + }, + "outputs": [], + "source": [ + "from typing import List\n", + "from pydantic import BaseModel\n", + "\n", + "\n", + "class City(BaseModel):\n", + " person: List[str]\n", + " country: List[str]\n", + " city: List[str]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zv-6-l_PCeFl" + }, + "source": [ + "> You can change these models according to the format you wish to extract from the text." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ouk1mAOUCeFl" + }, + "source": [ + "Then, generate a JSON schema from Pydantic models using `schema_json()`. You will later on use this schema in the prompt to instruct the LLM.\n", + "\n", + "To learn more about the JSON schemas, visit [Pydantic Schema](https://docs.pydantic.dev/1.10/usage/schema/). " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "8Lg9_72jCeFl" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "Egz_4h2vI_QL" - }, - "source": [ - "## What's next\n", - "\n", - "🎉 Congratulations! You've built a system that generates structured JSON out of unstructured text passages, and auto-corrects it by using the looping functionality of Haystack pipelines.\n", - "\n", - "To stay up to date on the latest Haystack developments, you can [subscribe to our newsletter](https://landing.deepset.ai/haystack-community-updates) and [join Haystack discord community](https://discord.gg/haystack).\n", - "\n", - "Thanks for reading!" - ] + "ename": "ValidationError", + "evalue": "3 validation errors for City\nperson\n Field required [type=missing, input_value={}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.6/v/missing\ncountry\n Field required [type=missing, input_value={}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.6/v/missing\ncity\n Field required [type=missing, input_value={}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.6/v/missing", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[9], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m json_schema \u001b[38;5;241m=\u001b[39m \u001b[43mCity\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241m.\u001b[39mmodel_dump_json(indent\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m2\u001b[39m)\n", + "File \u001b[0;32m~/Desktop/deepset/haystack-tutorials/.venv/lib/python3.9/site-packages/pydantic/main.py:171\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(self, **data)\u001b[0m\n\u001b[1;32m 169\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 170\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 171\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n", + "\u001b[0;31mValidationError\u001b[0m: 3 validation errors for City\nperson\n Field required [type=missing, input_value={}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.6/v/missing\ncountry\n Field required [type=missing, input_value={}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.6/v/missing\ncity\n Field required [type=missing, input_value={}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.6/v/missing" + ] } - ], - "metadata": { - "accelerator": "GPU", + ], + "source": [ + "json_schema = City().model_dump_json(indent=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KvNhg0bP7kfg" + }, + "source": [ + "## Creating a Custom Component: OutputValidator\n", + "\n", + "`OutputValidator` is a custom component that validates if the JSON object the LLM generates complies with the provided [Pydantic model](https://docs.pydantic.dev/1.10/usage/models/). If it doesn't, OutputValidator returns an error message along with the incorrect JSON object to get it fixed in the next loop.\n", + "\n", + "For more details about custom components, see [Creating Custom Components](https://docs.haystack.deepset.ai/docs/custom-components)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yr6D8RN2d7Vy" + }, + "outputs": [], + "source": [ + "import json\n", + "import random\n", + "import pydantic\n", + "from pydantic import ValidationError\n", + "from typing import Optional, List\n", + "from colorama import Fore\n", + "from haystack import component\n", + "\n", + "# Define the component input parameters\n", + "@component\n", + "class OutputValidator:\n", + " def __init__(self, pydantic_model: pydantic.BaseModel):\n", + " self.pydantic_model = pydantic_model\n", + " self.iteration_counter = 0\n", + "\n", + " # Define the component output\n", + " @component.output_types(valid_replies=List[str], invalid_replies=Optional[List[str]], error_message=Optional[str])\n", + " def run(self, replies: List[str]):\n", + "\n", + " self.iteration_counter += 1\n", + "\n", + " ## Try to parse the LLM's reply ##\n", + " # If the LLM's reply is a valid object, return `\"valid_replies\"`\n", + " try:\n", + " output_dict = json.loads(replies[0])\n", + " self.pydantic_model.parse_obj(output_dict)\n", + " print(\n", + " Fore.GREEN\n", + " + f\"OutputValidator at Iteration {self.iteration_counter}: Valid JSON from LLM - No need for looping: {replies[0]}\"\n", + " )\n", + " return {\"valid_replies\": replies}\n", + "\n", + " # If the LLM's reply is corrupted or not valid, return \"invalid_replies\" and the \"error_message\" for LLM to try again\n", + " except (ValueError, ValidationError) as e:\n", + " print(\n", + " Fore.RED\n", + " + f\"OutputValidator at Iteration {self.iteration_counter}: Invalid JSON from LLM - Let's try again.\\n\"\n", + " f\"Output from LLM:\\n {replies[0]} \\n\"\n", + " f\"Error from OutputValidator: {e}\"\n", + " )\n", + " return {\"invalid_replies\": replies, \"error_message\": str(e)}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vQ_TfSBkCeFm" + }, + "source": [ + "Then, create an OutputValidator instance with `CitiesData` that you have created before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bhPCLCBCCeFm" + }, + "outputs": [], + "source": [ + "output_validator = OutputValidator(pydantic_model=City)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xcIWKjW4k42r" + }, + "source": [ + "## Creating the Prompt\n", + "\n", + "Write instructions for the LLM for converting a passage into a JSON format. Ensure the instructions explain how to identify and correct errors if the JSON doesn't match the required schema. Once you create the prompt, initialize PromptBuilder to use it. \n", + "\n", + "For information about Jinja2 template and PromptBuilder, see [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ohPpNALjdVKt" + }, + "outputs": [], + "source": [ + "from haystack.components.builders import PromptBuilder\n", + "\n", + "prompt_template = \"\"\"\n", + "Create a JSON object from the information present in this passage: {{passage}}.\n", + "Only use information that is present in the passage. Follow this JSON schema, but only return the actual instances without any additional schema definition:\n", + "{{schema}}\n", + "Make sure your response is a dict and not a list.\n", + "{% if invalid_replies and error_message %}\n", + " You already created the following output in a previous attempt: {{invalid_replies}}\n", + " However, this doesn't comply with the format requirements from above and triggered this Python exception: {{error_message}}\n", + " Correct the output and try again. Just return the corrected output without any extra explanations.\n", + "{% endif %}\n", + "\"\"\"\n", + "prompt_builder = PromptBuilder(template=prompt_template)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KM9-Zq2FL7Nn" + }, + "source": [ + "## Initalizing the Generator\n", + "\n", + "[OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) generates\n", + "text using OpenAI's `gpt-4o-mini` model by default. Set the `OPENAI_API_KEY` variable and provide a model name to the Generator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Z4cQteIgunUR" + }, + "outputs": [], + "source": [ + "import os\n", + "from getpass import getpass\n", + "\n", + "from haystack.components.generators import OpenAIGenerator\n", + "\n", + "if \"OPENAI_API_KEY\" not in os.environ:\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", + "generator = OpenAIGenerator()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zbotIOgXHkC5" + }, + "source": [ + "## Building the Pipeline\n", + "\n", + "Add all components to your pipeline and connect them. Add connections from `output_validator` back to the `prompt_builder` for cases where the produced JSON doesn't comply with the JSON schema. Set `max_runs_per_component` to avoid infinite looping." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eFglN9YEv-1W" + }, + "outputs": [], + "source": [ + "from haystack import Pipeline\n", + "\n", + "pipeline = Pipeline(max_runs_per_component=5)\n", + "\n", + "# Add components to your pipeline\n", + "pipeline.add_component(instance=prompt_builder, name=\"prompt_builder\")\n", + "pipeline.add_component(instance=generator, name=\"llm\")\n", + "pipeline.add_component(instance=output_validator, name=\"output_validator\")\n", + "\n", + "# Now, connect the components to each other\n", + "pipeline.connect(\"prompt_builder\", \"llm\")\n", + "pipeline.connect(\"llm\", \"output_validator\")\n", + "# If a component has more than one output or input, explicitly specify the connections:\n", + "pipeline.connect(\"output_validator.invalid_replies\", \"prompt_builder.invalid_replies\")\n", + "pipeline.connect(\"output_validator.error_message\", \"prompt_builder.error_message\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-UKW5wtIIT7w" + }, + "source": [ + "### Visualize the Pipeline\n", + "\n", + "Draw the pipeline with the [`draw()`](https://docs.haystack.deepset.ai/docs/drawing-pipeline-graphs) method to confirm the connections are correct. You can find the diagram in the Files section of this Colab." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "RZJg6YHId300" + }, + "outputs": [], + "source": [ + "pipeline.draw(\"auto-correct-pipeline.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kV_kexTjImpo" + }, + "source": [ + "## Testing the Pipeline\n", + "\n", + "Run the pipeline with an example passage that you want to convert into a JSON format and the `json_schema` you have created for `CitiesData`. For the given example passage, the generated JSON object should be like:\n", + "```json\n", + "{\n", + " \"cities\": [\n", + " {\n", + " \"name\": \"Berlin\",\n", + " \"country\": \"Germany\",\n", + " \"population\": 3850809\n", + " },\n", + " {\n", + " \"name\": \"Paris\",\n", + " \"country\": \"France\",\n", + " \"population\": 2161000\n", + " },\n", + " {\n", + " \"name\": \"Lisbon\",\n", + " \"country\": \"Portugal\",\n", + " \"population\": 504718\n", + " }\n", + " ]\n", + "}\n", + "```\n", + "The output of the LLM should be compliant with the `json_schema`. If the LLM doesn't generate the correct JSON object, it will loop back and try again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { "colab": { - "gpuType": "T4", - "provenance": [] + "base_uri": "https://localhost:8080/" }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" + "id": "yIoMedb6eKia", + "outputId": "4a9ef924-cf26-4908-d83f-b0bc0dc03b54" + }, + "outputs": [], + "source": [ + "passage = \"Berlin is the capital of Germany. It has a population of 3,850,809. Paris, France's capital, has 2.161 million residents. Lisbon is the capital and the largest city of Portugal with the population of 504,718.\"\n", + "result = pipeline.run({\"prompt_builder\": {\"passage\": passage, \"schema\": json_schema}})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WWxmPgADS_Fa" + }, + "source": [ + "> If you encounter `PipelineMaxLoops: Maximum loops count (5) exceeded for component 'prompt_builder'.` error, consider increasing the maximum loop count or simply rerun the pipeline." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eWPawSjgSJAM" + }, + "source": [ + "### Print the Correct JSON\n", + "If you didn't get any error, you can now print the corrected JSON." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - "language_info": { - "name": "python" - } + "id": "BVO47gXQQnDC", + "outputId": "460a10d4-a69a-49cd-bbb2-fc4980907299" + }, + "outputs": [], + "source": [ + "valid_reply = result[\"output_validator\"][\"valid_replies\"][0]\n", + "valid_json = json.loads(valid_reply)\n", + "print(valid_json)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Egz_4h2vI_QL" + }, + "source": [ + "## What's next\n", + "\n", + "🎉 Congratulations! You've built a system that generates structured JSON out of unstructured text passages, and auto-corrects it by using the looping functionality of Haystack pipelines.\n", + "\n", + "To stay up to date on the latest Haystack developments, you can [subscribe to our newsletter](https://landing.deepset.ai/haystack-community-updates) and [join Haystack discord community](https://discord.gg/haystack).\n", + "\n", + "Thanks for reading!" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 0 + "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.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/tutorials/29_Serializing_Pipelines.ipynb b/tutorials/29_Serializing_Pipelines.ipynb index 9e6d3493..a95ebe62 100644 --- a/tutorials/29_Serializing_Pipelines.ipynb +++ b/tutorials/29_Serializing_Pipelines.ipynb @@ -14,7 +14,7 @@ "- **Prerequisites**: None\n", "- **Goal**: After completing this tutorial, you'll understand how to serialize and deserialize between YAML and Python code.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro)." ] }, { @@ -52,7 +52,7 @@ "source": [ "## Installing Haystack\n", "\n", - "Install Haystack 2.0 with `pip`:" + "Install Haystack with `pip`:" ] }, { @@ -65,48 +65,7 @@ "id": "CagzMFdkeBBp", "outputId": "e304450a-24e3-4ef8-e642-1fbb573e7d55" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: haystack-ai in /usr/local/lib/python3.10/dist-packages (2.0.0b5)\n", - "Requirement already satisfied: boilerpy3 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.0.7)\n", - "Requirement already satisfied: haystack-bm25 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.0.2)\n", - "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (3.1.3)\n", - "Requirement already satisfied: lazy-imports in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (0.3.1)\n", - "Requirement already satisfied: more-itertools in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (10.1.0)\n", - "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (3.2.1)\n", - "Requirement already satisfied: openai>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.10.0)\n", - "Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (1.5.3)\n", - "Requirement already satisfied: posthog in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (3.3.3)\n", - "Requirement already satisfied: pyyaml in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (6.0.1)\n", - "Requirement already satisfied: tenacity in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (8.2.3)\n", - "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (4.66.1)\n", - "Requirement already satisfied: typing-extensions in /usr/local/lib/python3.10/dist-packages (from haystack-ai) (4.9.0)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (3.7.1)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai>=1.1.0->haystack-ai) (1.7.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (0.26.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (1.10.14)\n", - "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai) (1.3.0)\n", - "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from haystack-bm25->haystack-ai) (1.23.5)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->haystack-ai) (2.1.4)\n", - "Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas->haystack-ai) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->haystack-ai) (2023.3.post1)\n", - "Requirement already satisfied: requests<3.0,>=2.7 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (2.31.0)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (1.16.0)\n", - "Requirement already satisfied: monotonic>=1.5 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (1.6)\n", - "Requirement already satisfied: backoff>=1.10.0 in /usr/local/lib/python3.10/dist-packages (from posthog->haystack-ai) (2.2.1)\n", - "Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai) (3.6)\n", - "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai) (1.2.0)\n", - "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (2023.11.17)\n", - "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (1.0.2)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (0.14.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3.0,>=2.7->posthog->haystack-ai) (3.3.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3.0,>=2.7->posthog->haystack-ai) (2.0.7)\n" - ] - } - ], + "outputs": [], "source": [ "%%bash\n", "\n", diff --git a/tutorials/30_File_Type_Preprocessing_Index_Pipeline.ipynb b/tutorials/30_File_Type_Preprocessing_Index_Pipeline.ipynb index 5349636c..3857962d 100644 --- a/tutorials/30_File_Type_Preprocessing_Index_Pipeline.ipynb +++ b/tutorials/30_File_Type_Preprocessing_Index_Pipeline.ipynb @@ -12,7 +12,7 @@ "- **Time to complete**: 15 minutes\n", "- **Goal**: After completing this tutorial, you'll have learned how to build an indexing pipeline that will preprocess files based on their file type, using the `FileTypeRouter`.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", "\n", "> 💡 (Optional): After creating the indexing pipeline in this tutorial, there is an optional section that shows you how to create a RAG pipeline on top of the document store you just created. You must have a [Hugging Face API Key](https://huggingface.co/settings/tokens) for this section\n", "\n", @@ -467,7 +467,8 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": ".venv", + "language": "python", "name": "python3" }, "language_info": { diff --git a/tutorials/31_Metadata_Filtering.ipynb b/tutorials/31_Metadata_Filtering.ipynb index 4c5d05f5..d46f67f6 100644 --- a/tutorials/31_Metadata_Filtering.ipynb +++ b/tutorials/31_Metadata_Filtering.ipynb @@ -14,7 +14,7 @@ "- **Prerequisites**: None\n", "- **Goal**: Filter documents in a document store based on given metadata\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro)." ] }, { @@ -50,7 +50,7 @@ "source": [ "## Installing Haystack\n", "\n", - "Install Haystack 2.0 with `pip`:" + "Install Haystack with `pip`:" ] }, { diff --git a/tutorials/32_Classifying_Documents_and_Queries_by_Language.ipynb b/tutorials/32_Classifying_Documents_and_Queries_by_Language.ipynb index c4bdb5b3..6de8b6f0 100644 --- a/tutorials/32_Classifying_Documents_and_Queries_by_Language.ipynb +++ b/tutorials/32_Classifying_Documents_and_Queries_by_Language.ipynb @@ -14,7 +14,7 @@ "- **Goal**: After completing this tutorial, you'll have learned how to build a Haystack pipeline to classify documents based on the (human) language they were written in.\n", "- Optionally, at the end you'll also incorporate language clasification and query routing into a RAG pipeline, so you can query documents based on the language a question was written in.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" ] }, { @@ -298,9 +298,11 @@ "outputs": [], "source": [ "language_classifier = DocumentLanguageClassifier(languages=[\"en\", \"fr\", \"es\"])\n", - "router_rules = {\"en\": {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"en\"}, \n", - " \"fr\": {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"fe\"}, \n", - " \"es\": {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"es\"}}\n", + "router_rules = {\n", + " \"en\": {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"en\"},\n", + " \"fr\": {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"fe\"},\n", + " \"es\": {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"es\"},\n", + "}\n", "router = MetadataRouter(rules=router_rules)" ] }, diff --git a/tutorials/33_Hybrid_Retrieval.ipynb b/tutorials/33_Hybrid_Retrieval.ipynb index 4813d3df..6c136ab5 100644 --- a/tutorials/33_Hybrid_Retrieval.ipynb +++ b/tutorials/33_Hybrid_Retrieval.ipynb @@ -14,7 +14,7 @@ "- **Prerequisites**: None\n", "- **Goal**: After completing this tutorial, you will have learned about creating a hybrid retrieval and when it's useful.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro)." ] }, { @@ -50,7 +50,7 @@ "source": [ "## Installing Haystack\n", "\n", - "Install Haystack 2.0 and other required packages with `pip`:" + "Install Haystack and other required packages with `pip`:" ] }, { diff --git a/tutorials/34_Extractive_QA_Pipeline.ipynb b/tutorials/34_Extractive_QA_Pipeline.ipynb index 7f21ce7e..7c1222aa 100644 --- a/tutorials/34_Extractive_QA_Pipeline.ipynb +++ b/tutorials/34_Extractive_QA_Pipeline.ipynb @@ -13,7 +13,7 @@ "- **Components Used**: [`ExtractiveReader`](https://docs.haystack.deepset.ai/docs/extractivereader), [`InMemoryDocumentStore`](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [`InMemoryEmbeddingRetriever`](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [`DocumentWriter`](https://docs.haystack.deepset.ai/docs/documentwriter), [`SentenceTransformersDocumentEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder), [`SentenceTransformersTextEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder)\n", "- **Goal**: After completing this tutorial, you'll have learned how to build a Haystack pipeline that uses an extractive model to display where the answer to your query is.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", "\n" ] }, diff --git a/tutorials/35_Evaluating_RAG_Pipelines.ipynb b/tutorials/35_Evaluating_RAG_Pipelines.ipynb index 0f1d860e..bcea2f3d 100644 --- a/tutorials/35_Evaluating_RAG_Pipelines.ipynb +++ b/tutorials/35_Evaluating_RAG_Pipelines.ipynb @@ -14,7 +14,7 @@ "- **Prerequisites**: You must have an API key from an active OpenAI account as this tutorial is using the gpt-4o-mini model by OpenAI: https://platform.openai.com/api-keys\n", "- **Goal**: After completing this tutorial, you'll have learned how to evaluate your RAG pipelines both with model-based, and statistical metrics available in the Haystack evaluation offering. You'll also see which other evaluation frameworks are integrated with Haystack.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro)." ] }, { @@ -75,12 +75,12 @@ "source": [ "## Installing Haystack\n", "\n", - "Install Haystack 2.0 and [datasets](https://pypi.org/project/datasets/) with `pip`:" + "Install Haystack and [datasets](https://pypi.org/project/datasets/) with `pip`:" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -88,136 +88,7 @@ "id": "UQbU8GUfO-qZ", "outputId": "80fe52ea-108b-4bb4-cb1d-fe79373c86f3" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting git+https://github.com/deepset-ai/haystack.git@main\n", - " Cloning https://github.com/deepset-ai/haystack.git (to revision main) to /tmp/pip-req-build-83hiigdl\n", - " Resolved https://github.com/deepset-ai/haystack.git to commit 2509eeea7e82ef52ef65ccce00bfdcc6c1e8c1c2\n", - " Installing build dependencies: started\n", - " Installing build dependencies: finished with status 'done'\n", - " Getting requirements to build wheel: started\n", - " Getting requirements to build wheel: finished with status 'done'\n", - " Preparing metadata (pyproject.toml): started\n", - " Preparing metadata (pyproject.toml): finished with status 'done'\n", - "Collecting boilerpy3 (from haystack-ai==2.1.0rc0)\n", - " Downloading boilerpy3-1.0.7-py3-none-any.whl (22 kB)\n", - "Collecting haystack-bm25 (from haystack-ai==2.1.0rc0)\n", - " Downloading haystack_bm25-1.0.2-py2.py3-none-any.whl (8.8 kB)\n", - "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (3.1.3)\n", - "Collecting lazy-imports (from haystack-ai==2.1.0rc0)\n", - " Downloading lazy_imports-0.3.1-py3-none-any.whl (12 kB)\n", - "Requirement already satisfied: more-itertools in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (10.1.0)\n", - "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (3.3)\n", - "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (1.25.2)\n", - "Collecting openai>=1.1.0 (from haystack-ai==2.1.0rc0)\n", - " Downloading openai-1.25.0-py3-none-any.whl (312 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 312.9/312.9 kB 9.8 MB/s eta 0:00:00\n", - "Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (2.0.3)\n", - "Collecting posthog (from haystack-ai==2.1.0rc0)\n", - " Downloading posthog-3.5.0-py2.py3-none-any.whl (41 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.3/41.3 kB 4.4 MB/s eta 0:00:00\n", - "Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (2.8.2)\n", - "Requirement already satisfied: pyyaml in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (6.0.1)\n", - "Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (2.31.0)\n", - "Requirement already satisfied: tenacity in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (8.2.3)\n", - "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (4.66.2)\n", - "Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.10/dist-packages (from haystack-ai==2.1.0rc0) (4.11.0)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai==2.1.0rc0) (3.7.1)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai>=1.1.0->haystack-ai==2.1.0rc0) (1.7.0)\n", - "Collecting httpx<1,>=0.23.0 (from openai>=1.1.0->haystack-ai==2.1.0rc0)\n", - " Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 75.6/75.6 kB 7.4 MB/s eta 0:00:00\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai==2.1.0rc0) (2.7.1)\n", - "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai>=1.1.0->haystack-ai==2.1.0rc0) (1.3.1)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->haystack-ai==2.1.0rc0) (2.1.5)\n", - "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->haystack-ai==2.1.0rc0) (2023.4)\n", - "Requirement already satisfied: tzdata>=2022.1 in /usr/local/lib/python3.10/dist-packages (from pandas->haystack-ai==2.1.0rc0) (2024.1)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil->haystack-ai==2.1.0rc0) (1.16.0)\n", - "Collecting monotonic>=1.5 (from posthog->haystack-ai==2.1.0rc0)\n", - " Downloading monotonic-1.6-py2.py3-none-any.whl (8.2 kB)\n", - "Collecting backoff>=1.10.0 (from posthog->haystack-ai==2.1.0rc0)\n", - " Downloading backoff-2.2.1-py3-none-any.whl (15 kB)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->haystack-ai==2.1.0rc0) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->haystack-ai==2.1.0rc0) (3.7)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->haystack-ai==2.1.0rc0) (2.0.7)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->haystack-ai==2.1.0rc0) (2024.2.2)\n", - "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai==2.1.0rc0) (1.2.1)\n", - "Collecting httpcore==1.* (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai==2.1.0rc0)\n", - " Downloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77.9/77.9 kB 12.3 MB/s eta 0:00:00\n", - "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai==2.1.0rc0)\n", - " Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 58.3/58.3 kB 10.2 MB/s eta 0:00:00\n", - "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai==2.1.0rc0) (0.6.0)\n", - "Requirement already satisfied: pydantic-core==2.18.2 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai==2.1.0rc0) (2.18.2)\n", - "Building wheels for collected packages: haystack-ai\n", - " Building wheel for haystack-ai (pyproject.toml): started\n", - " Building wheel for haystack-ai (pyproject.toml): finished with status 'done'\n", - " Created wheel for haystack-ai: filename=haystack_ai-2.1.0rc0-py3-none-any.whl size=316211 sha256=aee4b70fda05260e7466d477508440735cfe4d5c3b9a15a7003773a7fa01bd0c\n", - " Stored in directory: /tmp/pip-ephem-wheel-cache-faxhntm2/wheels/23/e0/55/004621325804423c8026b4b5008ddb11f337bf73284d1b9caf\n", - "Successfully built haystack-ai\n", - "Installing collected packages: monotonic, lazy-imports, haystack-bm25, h11, boilerpy3, backoff, posthog, httpcore, httpx, openai, haystack-ai\n", - "Successfully installed backoff-2.2.1 boilerpy3-1.0.7 h11-0.14.0 haystack-ai-2.1.0rc0 haystack-bm25-1.0.2 httpcore-1.0.5 httpx-0.27.0 lazy-imports-0.3.1 monotonic-1.6 openai-1.25.0 posthog-3.5.0\n", - "Collecting datasets>=2.6.1\n", - " Downloading datasets-2.19.0-py3-none-any.whl (542 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 542.0/542.0 kB 9.3 MB/s eta 0:00:00\n", - "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (3.13.4)\n", - "Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (1.25.2)\n", - "Requirement already satisfied: pyarrow>=12.0.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (14.0.2)\n", - "Requirement already satisfied: pyarrow-hotfix in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (0.6)\n", - "Collecting dill<0.3.9,>=0.3.0 (from datasets>=2.6.1)\n", - " Downloading dill-0.3.8-py3-none-any.whl (116 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 116.3/116.3 kB 11.3 MB/s eta 0:00:00\n", - "Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (2.0.3)\n", - "Requirement already satisfied: requests>=2.19.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (2.31.0)\n", - "Requirement already satisfied: tqdm>=4.62.1 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (4.66.2)\n", - "Collecting xxhash (from datasets>=2.6.1)\n", - " Downloading xxhash-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (194 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 194.1/194.1 kB 12.5 MB/s eta 0:00:00\n", - "Collecting multiprocess (from datasets>=2.6.1)\n", - " Downloading multiprocess-0.70.16-py310-none-any.whl (134 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 134.8/134.8 kB 11.9 MB/s eta 0:00:00\n", - "Requirement already satisfied: fsspec[http]<=2024.3.1,>=2023.1.0 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (2023.6.0)\n", - "Requirement already satisfied: aiohttp in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (3.9.5)\n", - "Collecting huggingface-hub>=0.21.2 (from datasets>=2.6.1)\n", - " Downloading huggingface_hub-0.22.2-py3-none-any.whl (388 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 388.9/388.9 kB 17.1 MB/s eta 0:00:00\n", - "Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (24.0)\n", - "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.10/dist-packages (from datasets>=2.6.1) (6.0.1)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (1.3.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (23.2.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (1.4.1)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (6.0.5)\n", - "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (1.9.4)\n", - "Requirement already satisfied: async-timeout<5.0,>=4.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets>=2.6.1) (4.0.3)\n", - "Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.21.2->datasets>=2.6.1) (4.11.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (3.7)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (2.0.7)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.19.0->datasets>=2.6.1) (2024.2.2)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets>=2.6.1) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets>=2.6.1) (2023.4)\n", - "Requirement already satisfied: tzdata>=2022.1 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets>=2.6.1) (2024.1)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.2->pandas->datasets>=2.6.1) (1.16.0)\n", - "Installing collected packages: xxhash, dill, multiprocess, huggingface-hub, datasets\n", - " Attempting uninstall: huggingface-hub\n", - " Found existing installation: huggingface-hub 0.20.3\n", - " Uninstalling huggingface-hub-0.20.3:\n", - " Successfully uninstalled huggingface-hub-0.20.3\n", - "Successfully installed datasets-2.19.0 dill-0.3.8 huggingface-hub-0.22.2 multiprocess-0.70.16 xxhash-3.4.1\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - " Running command git clone --filter=blob:none --quiet https://github.com/deepset-ai/haystack.git /tmp/pip-req-build-83hiigdl\n" - ] - } - ], + "outputs": [], "source": [ "%%bash\n", "\n", diff --git a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb index 9e91f81c..2bfd8b2e 100644 --- a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb +++ b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb @@ -1,551 +1,552 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "IR5wivW8THt7" - }, - "source": [ - "# Tutorial: Building Fallbacks to Websearch with Conditional Routing\n", - "\n", - "- **Level**: Intermediate\n", - "- **Time to complete**: 10 minutes\n", - "- **Components Used**: [`ConditionalRouter`](https://docs.haystack.deepset.ai/docs/conditionalrouter), [`SerperDevWebSearch`](https://docs.haystack.deepset.ai/docs/serperdevwebsearch), [`PromptBuilder`](https://docs.haystack.deepset.ai/docs/promptbuilder), [`OpenAIGenerator`](https://docs.haystack.deepset.ai/docs/openaigenerator)\n", - "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys) and a [Serper API Key](https://serper.dev/api-key) for this tutorial\n", - "- **Goal**: After completing this tutorial, you'll have learned how to create a pipeline with conditional routing that can fallback to websearch if the answer is not present in your dataset.\n", - "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "F-a-MAMVat-o" - }, - "source": [ - "## Overview\n", - "\n", - "When developing applications using **retrieval augmented generation ([RAG](https://www.deepset.ai/blog/llms-retrieval-augmentation))**, the retrieval step plays a critical role. It serves as the primary information source for **large language models (LLMs)** to generate responses. However, if your database lacks the necessary information, the retrieval step's effectiveness is limited. In such scenarios, it may be practical to use the web as a fallback data source for your RAG application. By implementing a conditional routing mechanism in your system, you gain complete control over the data flow, enabling you to design a system that can leverage the web as its data source under some conditions.\n", - "\n", - "In this tutorial, you will learn how to create a pipeline with conditional routing that directs the query to a **web-based RAG** route if the answer is not found in the initially given documents." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LSwNKkeKeq0f" - }, - "source": [ - "## Development Environment" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eGJ7GmCBas4R" - }, - "source": [ - "### Prepare the Colab Environment\n", - "\n", - "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", - "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/setting-the-log-level)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "FwIgIpE2XqpO" - }, - "source": [ - "### Install Haystack\n", - "\n", - "Install Haystack 2.0 with `pip`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "uba0mntlqs_O" - }, - "outputs": [], - "source": [ - "%%bash\n", - "\n", - "pip install haystack-ai" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WBkJ7d3hZkOJ" - }, - "source": [ - "### Enable Telemetry\n", - "\n", - "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "HvrOixzzZmMi" - }, - "outputs": [], - "source": [ - "from haystack.telemetry import tutorial_running\n", - "\n", - "tutorial_running(36)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QfECEAy2Jdqs" - }, - "source": [ - "### Enter API Keys\n", - "\n", - "Enter API keys required for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "13U7Z_k3yE-F", - "outputId": "6ec48553-12d2-4c89-ca13-fc5d34fbc625" - }, - "outputs": [], - "source": [ - "from getpass import getpass\n", - "import os\n", - "\n", - "if \"OPENAI_API_KEY\" not in os.environ:\n", - " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", - "if \"SERPERDEV_API_KEY\" not in os.environ:\n", - " os.environ[\"SERPERDEV_API_KEY\"] = getpass(\"Enter Serper Api key: \")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i_AlhPv1T-4t" - }, - "source": [ - "## Creating a Document\n", - "\n", - "Create a Document about Munich, where the answer to your question will be initially searched:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "5CHbQlLMyVbg" - }, - "outputs": [], - "source": [ - "from haystack.dataclasses import Document\n", - "\n", - "documents = [\n", - " Document(\n", - " content=\"\"\"Munich, the vibrant capital of Bavaria in southern Germany, exudes a perfect blend of rich cultural\n", - " heritage and modern urban sophistication. Nestled along the banks of the Isar River, Munich is renowned\n", - " for its splendid architecture, including the iconic Neues Rathaus (New Town Hall) at Marienplatz and\n", - " the grandeur of Nymphenburg Palace. The city is a haven for art enthusiasts, with world-class museums like the\n", - " Alte Pinakothek housing masterpieces by renowned artists. Munich is also famous for its lively beer gardens, where\n", - " locals and tourists gather to enjoy the city's famed beers and traditional Bavarian cuisine. The city's annual\n", - " Oktoberfest celebration, the world's largest beer festival, attracts millions of visitors from around the globe.\n", - " Beyond its cultural and culinary delights, Munich offers picturesque parks like the English Garden, providing a\n", - " serene escape within the heart of the bustling metropolis. Visitors are charmed by Munich's warm hospitality,\n", - " making it a must-visit destination for travelers seeking a taste of both old-world charm and contemporary allure.\"\"\"\n", - " )\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zMNy0tjtUh_L" - }, - "source": [ - "## Creating the Initial Pipeline Components\n", - "\n", - "First, define a prompt instructing the LLM to respond with the text `\"no_answer\"` if the provided documents do not offer enough context to answer the query. Next, initialize a [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) with that prompt. It's crucial that the LLM replies with `\"no_answer\"` as you will use this keyword to indicate that the query should be directed to the fallback web search route.\n", - "\n", - "As the LLM, you will use an [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) with the `gpt-4o-mini` model.\n", - "\n", - "> The provided prompt works effectively with the `gpt-4o-mini` model. If you prefer to use a different [Generator](https://docs.haystack.deepset.ai/docs/generators), you may need to update the prompt to provide clear instructions to your model." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "id": "nzhn2kDfqvbs" - }, - "outputs": [], - "source": [ - "from haystack.components.builders.prompt_builder import PromptBuilder\n", - "from haystack.components.generators import OpenAIGenerator\n", - "\n", - "prompt_template = \"\"\"\n", - "Answer the following query given the documents.\n", - "If the answer is not contained within the documents reply with 'no_answer'\n", - "Query: {{query}}\n", - "Documents:\n", - "{% for document in documents %}\n", - " {{document.content}}\n", - "{% endfor %}\n", - "\"\"\"\n", - "\n", - "prompt_builder = PromptBuilder(template=prompt_template)\n", - "llm = OpenAIGenerator(model=\"gpt-4o-mini\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LepACkkWPsBx" - }, - "source": [ - "## Initializing the Web Search Components\n", - "\n", - "Initialize the necessary components for a web-based RAG application. Along with a `PromptBuilder` and an `OpenAIGenerator`, you will need a [SerperDevWebSearch](https://docs.haystack.deepset.ai/docs/serperdevwebsearch) to retrieve relevant documents for the query from the web.\n", - "\n", - "> If desired, you can use a different [Generator](https://docs.haystack.deepset.ai/docs/generators) for the web-based RAG branch of the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "VEYchFgQPxZ_" - }, - "outputs": [], - "source": [ - "from haystack.components.builders.prompt_builder import PromptBuilder\n", - "from haystack.components.generators import OpenAIGenerator\n", - "from haystack.components.websearch.serper_dev import SerperDevWebSearch\n", - "\n", - "prompt_for_websearch = \"\"\"\n", - "Answer the following query given the documents retrieved from the web.\n", - "Your answer shoud indicate that your answer was generated from websearch.\n", - "\n", - "Query: {{query}}\n", - "Documents:\n", - "{% for document in documents %}\n", - " {{document.content}}\n", - "{% endfor %}\n", - "\"\"\"\n", - "\n", - "websearch = SerperDevWebSearch()\n", - "prompt_builder_for_websearch = PromptBuilder(template=prompt_for_websearch)\n", - "llm_for_websearch = OpenAIGenerator(model=\"gpt-4o-mini\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vnacak_tVWqv" - }, - "source": [ - "## Creating the ConditionalRouter\n", - "\n", - "[ConditionalRouter](https://docs.haystack.deepset.ai/docs/conditionalrouter) is the component that handles data routing on specific conditions. You need to define a `condition`, an `output`, an `output_name` and an `output_type` for each route. Each route that the `ConditionalRouter` creates acts as the output of this component and can be connected to other components in the same pipeline. \n", - "\n", - "In this case, you need to define two routes:\n", - "- If the LLM replies with the `\"no_answer\"` keyword, the pipeline should perform web search. It means that you will put the original `query` in the output value to pass to the next component (in this case the next component will be the `SerperDevWebSearch`) and the output name will be `go_to_websearch`.\n", - "- Otherwise, the given documents are enough for an answer and pipeline execution ends here. Return the LLM reply in the output named `answer`." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "qyE9rGcawX3F" - }, - "outputs": [], - "source": [ - "from haystack.components.routers import ConditionalRouter\n", - "\n", - "routes = [\n", - " {\n", - " \"condition\": \"{{'no_answer' in replies[0]}}\",\n", - " \"output\": \"{{query}}\",\n", - " \"output_name\": \"go_to_websearch\",\n", - " \"output_type\": str,\n", - " },\n", - " {\n", - " \"condition\": \"{{'no_answer' not in replies[0]}}\",\n", - " \"output\": \"{{replies[0]}}\",\n", - " \"output_name\": \"answer\",\n", - " \"output_type\": str,\n", - " },\n", - "]\n", - "\n", - "router = ConditionalRouter(routes)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wdyko78oXb5a" - }, - "source": [ - "## Building the Pipeline\n", - "\n", - "Add all components to your pipeline and connect them. `go_to_websearch` output of the `router` should be connected to the `websearch` to retrieve documents from the web and also to `prompt_builder_for_websearch` to use in the prompt." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "4sCyBwc0oTVs", - "outputId": "fd2347d4-9363-45e0-e734-87e4a160f741" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from haystack import Pipeline\n", - "\n", - "pipe = Pipeline()\n", - "pipe.add_component(\"prompt_builder\", prompt_builder)\n", - "pipe.add_component(\"llm\", llm)\n", - "pipe.add_component(\"router\", router)\n", - "pipe.add_component(\"websearch\", websearch)\n", - "pipe.add_component(\"prompt_builder_for_websearch\", prompt_builder_for_websearch)\n", - "pipe.add_component(\"llm_for_websearch\", llm_for_websearch)\n", - "\n", - "pipe.connect(\"prompt_builder\", \"llm\")\n", - "pipe.connect(\"llm.replies\", \"router.replies\")\n", - "pipe.connect(\"router.go_to_websearch\", \"websearch.query\")\n", - "pipe.connect(\"router.go_to_websearch\", \"prompt_builder_for_websearch.query\")\n", - "pipe.connect(\"websearch.documents\", \"prompt_builder_for_websearch.documents\")\n", - "pipe.connect(\"prompt_builder_for_websearch\", \"llm_for_websearch\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d0HmdbUJKJ_9" - }, - "source": [ - "### Visualize the Pipeline\n", - "\n", - "To understand how you formed this pipeline with conditional routing, use [draw()](https://docs.haystack.deepset.ai/docs/drawing-pipeline-graphs) method of the pipeline. If you're running this notebook on Google Colab, the generated file will be saved in \\\"Files\\\" section on the sidebar." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "svF_SUK4rFwv", - "outputId": "60894eea-2cec-4be8-d13c-83d2c81656f4" - }, - "outputs": [], - "source": [ - "pipe.draw(\"pipe.png\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jgk1z6GGYH6J" - }, - "source": [ - "## Running the Pipeline!\n", - "\n", - "In the `run()`, pass the query to the `prompt_builder` and the `router`. In real life applications, `documents` will be provided by a [Retriever](https://docs.haystack.deepset.ai/docs/retrievers) but to keep this example simple, you will provide the defined `documents` to the `prompt_builder`." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "d_l4rYmCoVki", - "outputId": "3bd7956a-7612-4bc1-c3e5-a7a51be8981f" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Munich is in southern Germany.\n" - ] - } - ], - "source": [ - "query = \"Where is Munich?\"\n", - "\n", - "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", - "\n", - "# Print the `answer` coming from the ConditionalRouter\n", - "print(result[\"router\"][\"answer\"])" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "IR5wivW8THt7" + }, + "source": [ + "# Tutorial: Building Fallbacks to Websearch with Conditional Routing\n", + "\n", + "- **Level**: Intermediate\n", + "- **Time to complete**: 10 minutes\n", + "- **Components Used**: [`ConditionalRouter`](https://docs.haystack.deepset.ai/docs/conditionalrouter), [`SerperDevWebSearch`](https://docs.haystack.deepset.ai/docs/serperdevwebsearch), [`PromptBuilder`](https://docs.haystack.deepset.ai/docs/promptbuilder), [`OpenAIGenerator`](https://docs.haystack.deepset.ai/docs/openaigenerator)\n", + "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys) and a [Serper API Key](https://serper.dev/api-key) for this tutorial\n", + "- **Goal**: After completing this tutorial, you'll have learned how to create a pipeline with conditional routing that can fallback to websearch if the answer is not present in your dataset.\n", + "\n", + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "F-a-MAMVat-o" + }, + "source": [ + "## Overview\n", + "\n", + "When developing applications using **retrieval augmented generation ([RAG](https://www.deepset.ai/blog/llms-retrieval-augmentation))**, the retrieval step plays a critical role. It serves as the primary information source for **large language models (LLMs)** to generate responses. However, if your database lacks the necessary information, the retrieval step's effectiveness is limited. In such scenarios, it may be practical to use the web as a fallback data source for your RAG application. By implementing a conditional routing mechanism in your system, you gain complete control over the data flow, enabling you to design a system that can leverage the web as its data source under some conditions.\n", + "\n", + "In this tutorial, you will learn how to create a pipeline with conditional routing that directs the query to a **web-based RAG** route if the answer is not found in the initially given documents." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LSwNKkeKeq0f" + }, + "source": [ + "## Development Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eGJ7GmCBas4R" + }, + "source": [ + "### Prepare the Colab Environment\n", + "\n", + "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", + "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/setting-the-log-level)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FwIgIpE2XqpO" + }, + "source": [ + "### Install Haystack\n", + "\n", + "Install Haystack with `pip`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "uba0mntlqs_O" + }, + "outputs": [], + "source": [ + "%%bash\n", + "\n", + "pip install haystack-ai" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WBkJ7d3hZkOJ" + }, + "source": [ + "### Enable Telemetry\n", + "\n", + "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HvrOixzzZmMi" + }, + "outputs": [], + "source": [ + "from haystack.telemetry import tutorial_running\n", + "\n", + "tutorial_running(36)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QfECEAy2Jdqs" + }, + "source": [ + "### Enter API Keys\n", + "\n", + "Enter API keys required for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "markdown", - "metadata": { - "id": "dBN8eLSKgb16" - }, - "source": [ - "✅ The answer to this query can be found in the defined document.\n", - "\n", - "Now, try a different query that doesn't have an answer in the given document and test if the web search works as expected:" - ] + "id": "13U7Z_k3yE-F", + "outputId": "6ec48553-12d2-4c89-ca13-fc5d34fbc625" + }, + "outputs": [], + "source": [ + "from getpass import getpass\n", + "import os\n", + "\n", + "if \"OPENAI_API_KEY\" not in os.environ:\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", + "if \"SERPERDEV_API_KEY\" not in os.environ:\n", + " os.environ[\"SERPERDEV_API_KEY\"] = getpass(\"Enter Serper Api key: \")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i_AlhPv1T-4t" + }, + "source": [ + "## Creating a Document\n", + "\n", + "Create a Document about Munich, where the answer to your question will be initially searched:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "5CHbQlLMyVbg" + }, + "outputs": [], + "source": [ + "from haystack.dataclasses import Document\n", + "\n", + "documents = [\n", + " Document(\n", + " content=\"\"\"Munich, the vibrant capital of Bavaria in southern Germany, exudes a perfect blend of rich cultural\n", + " heritage and modern urban sophistication. Nestled along the banks of the Isar River, Munich is renowned\n", + " for its splendid architecture, including the iconic Neues Rathaus (New Town Hall) at Marienplatz and\n", + " the grandeur of Nymphenburg Palace. The city is a haven for art enthusiasts, with world-class museums like the\n", + " Alte Pinakothek housing masterpieces by renowned artists. Munich is also famous for its lively beer gardens, where\n", + " locals and tourists gather to enjoy the city's famed beers and traditional Bavarian cuisine. The city's annual\n", + " Oktoberfest celebration, the world's largest beer festival, attracts millions of visitors from around the globe.\n", + " Beyond its cultural and culinary delights, Munich offers picturesque parks like the English Garden, providing a\n", + " serene escape within the heart of the bustling metropolis. Visitors are charmed by Munich's warm hospitality,\n", + " making it a must-visit destination for travelers seeking a taste of both old-world charm and contemporary allure.\"\"\"\n", + " )\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zMNy0tjtUh_L" + }, + "source": [ + "## Creating the Initial Pipeline Components\n", + "\n", + "First, define a prompt instructing the LLM to respond with the text `\"no_answer\"` if the provided documents do not offer enough context to answer the query. Next, initialize a [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) with that prompt. It's crucial that the LLM replies with `\"no_answer\"` as you will use this keyword to indicate that the query should be directed to the fallback web search route.\n", + "\n", + "As the LLM, you will use an [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) with the `gpt-4o-mini` model.\n", + "\n", + "> The provided prompt works effectively with the `gpt-4o-mini` model. If you prefer to use a different [Generator](https://docs.haystack.deepset.ai/docs/generators), you may need to update the prompt to provide clear instructions to your model." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "nzhn2kDfqvbs" + }, + "outputs": [], + "source": [ + "from haystack.components.builders.prompt_builder import PromptBuilder\n", + "from haystack.components.generators import OpenAIGenerator\n", + "\n", + "prompt_template = \"\"\"\n", + "Answer the following query given the documents.\n", + "If the answer is not contained within the documents reply with 'no_answer'\n", + "Query: {{query}}\n", + "Documents:\n", + "{% for document in documents %}\n", + " {{document.content}}\n", + "{% endfor %}\n", + "\"\"\"\n", + "\n", + "prompt_builder = PromptBuilder(template=prompt_template)\n", + "llm = OpenAIGenerator(model=\"gpt-4o-mini\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LepACkkWPsBx" + }, + "source": [ + "## Initializing the Web Search Components\n", + "\n", + "Initialize the necessary components for a web-based RAG application. Along with a `PromptBuilder` and an `OpenAIGenerator`, you will need a [SerperDevWebSearch](https://docs.haystack.deepset.ai/docs/serperdevwebsearch) to retrieve relevant documents for the query from the web.\n", + "\n", + "> If desired, you can use a different [Generator](https://docs.haystack.deepset.ai/docs/generators) for the web-based RAG branch of the pipeline." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "VEYchFgQPxZ_" + }, + "outputs": [], + "source": [ + "from haystack.components.builders.prompt_builder import PromptBuilder\n", + "from haystack.components.generators import OpenAIGenerator\n", + "from haystack.components.websearch.serper_dev import SerperDevWebSearch\n", + "\n", + "prompt_for_websearch = \"\"\"\n", + "Answer the following query given the documents retrieved from the web.\n", + "Your answer shoud indicate that your answer was generated from websearch.\n", + "\n", + "Query: {{query}}\n", + "Documents:\n", + "{% for document in documents %}\n", + " {{document.content}}\n", + "{% endfor %}\n", + "\"\"\"\n", + "\n", + "websearch = SerperDevWebSearch()\n", + "prompt_builder_for_websearch = PromptBuilder(template=prompt_for_websearch)\n", + "llm_for_websearch = OpenAIGenerator(model=\"gpt-4o-mini\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vnacak_tVWqv" + }, + "source": [ + "## Creating the ConditionalRouter\n", + "\n", + "[ConditionalRouter](https://docs.haystack.deepset.ai/docs/conditionalrouter) is the component that handles data routing on specific conditions. You need to define a `condition`, an `output`, an `output_name` and an `output_type` for each route. Each route that the `ConditionalRouter` creates acts as the output of this component and can be connected to other components in the same pipeline. \n", + "\n", + "In this case, you need to define two routes:\n", + "- If the LLM replies with the `\"no_answer\"` keyword, the pipeline should perform web search. It means that you will put the original `query` in the output value to pass to the next component (in this case the next component will be the `SerperDevWebSearch`) and the output name will be `go_to_websearch`.\n", + "- Otherwise, the given documents are enough for an answer and pipeline execution ends here. Return the LLM reply in the output named `answer`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "qyE9rGcawX3F" + }, + "outputs": [], + "source": [ + "from haystack.components.routers import ConditionalRouter\n", + "\n", + "routes = [\n", + " {\n", + " \"condition\": \"{{'no_answer' in replies[0]}}\",\n", + " \"output\": \"{{query}}\",\n", + " \"output_name\": \"go_to_websearch\",\n", + " \"output_type\": str,\n", + " },\n", + " {\n", + " \"condition\": \"{{'no_answer' not in replies[0]}}\",\n", + " \"output\": \"{{replies[0]}}\",\n", + " \"output_name\": \"answer\",\n", + " \"output_type\": str,\n", + " },\n", + "]\n", + "\n", + "router = ConditionalRouter(routes)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wdyko78oXb5a" + }, + "source": [ + "## Building the Pipeline\n", + "\n", + "Add all components to your pipeline and connect them. `go_to_websearch` output of the `router` should be connected to the `websearch` to retrieve documents from the web and also to `prompt_builder_for_websearch` to use in the prompt." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "4sCyBwc0oTVs", + "outputId": "fd2347d4-9363-45e0-e734-87e4a160f741" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "_v-WdlSy365M", - "outputId": "603c9346-8718-427e-d232-4cc71799a2bb" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['According to the documents retrieved from the web, the population of Munich is approximately 1.47 million as of 2019. However, the most recent estimates suggest that the population has grown to about 1.58 million as of May 31, 2022. Additionally, the current estimated population of Munich is around 1.46 million, with the urban area being much larger at 2.65 million.']\n" - ] - } - ], - "source": [ - "query = \"How many people live in Munich?\"\n", - "\n", - "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", - "\n", - "# Print the `replies` generated using the web searched Documents\n", - "print(result[\"llm_for_websearch\"][\"replies\"])" + "data": { + "text/plain": [ + "" ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from haystack import Pipeline\n", + "\n", + "pipe = Pipeline()\n", + "pipe.add_component(\"prompt_builder\", prompt_builder)\n", + "pipe.add_component(\"llm\", llm)\n", + "pipe.add_component(\"router\", router)\n", + "pipe.add_component(\"websearch\", websearch)\n", + "pipe.add_component(\"prompt_builder_for_websearch\", prompt_builder_for_websearch)\n", + "pipe.add_component(\"llm_for_websearch\", llm_for_websearch)\n", + "\n", + "pipe.connect(\"prompt_builder\", \"llm\")\n", + "pipe.connect(\"llm.replies\", \"router.replies\")\n", + "pipe.connect(\"router.go_to_websearch\", \"websearch.query\")\n", + "pipe.connect(\"router.go_to_websearch\", \"prompt_builder_for_websearch.query\")\n", + "pipe.connect(\"websearch.documents\", \"prompt_builder_for_websearch.documents\")\n", + "pipe.connect(\"prompt_builder_for_websearch\", \"llm_for_websearch\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d0HmdbUJKJ_9" + }, + "source": [ + "### Visualize the Pipeline\n", + "\n", + "To understand how you formed this pipeline with conditional routing, use [draw()](https://docs.haystack.deepset.ai/docs/drawing-pipeline-graphs) method of the pipeline. If you're running this notebook on Google Colab, the generated file will be saved in \\\"Files\\\" section on the sidebar." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 }, - { - "cell_type": "markdown", - "metadata": { - "id": "wUkuXoWnHa5c" - }, - "source": [ - "If you check the whole result, you will see that `websearch` component also provides links to Documents retrieved from the web:" - ] + "id": "svF_SUK4rFwv", + "outputId": "60894eea-2cec-4be8-d13c-83d2c81656f4" + }, + "outputs": [], + "source": [ + "pipe.draw(\"pipe.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jgk1z6GGYH6J" + }, + "source": [ + "## Running the Pipeline!\n", + "\n", + "In the `run()`, pass the query to the `prompt_builder` and the `router`. In real life applications, `documents` will be provided by a [Retriever](https://docs.haystack.deepset.ai/docs/retrievers) but to keep this example simple, you will provide the defined `documents` to the `prompt_builder`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "d_l4rYmCoVki", + "outputId": "3bd7956a-7612-4bc1-c3e5-a7a51be8981f" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "_EYLZguZGznY", - "outputId": "df49a576-9961-44b4-e89d-2c5195869360" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'llm': {'meta': [{'model': 'gpt-4o-mini-2024-07-18',\n", - " 'index': 0,\n", - " 'finish_reason': 'stop',\n", - " 'usage': {'completion_tokens': 2,\n", - " 'prompt_tokens': 271,\n", - " 'total_tokens': 273}}]},\n", - " 'websearch': {'links': ['https://en.wikipedia.org/wiki/Munich',\n", - " 'https://worldpopulationreview.com/world-cities/munich-population',\n", - " 'https://en.wikipedia.org/wiki/Demographics_of_Munich',\n", - " 'https://www.macrotrends.net/cities/204371/munich/population',\n", - " 'https://www.britannica.com/place/Munich-Bavaria-Germany',\n", - " 'https://www.statista.com/statistics/519723/munich-population-by-age-group/',\n", - " 'https://www.citypopulation.de/en/germany/bayern/m%C3%BCnchen_stadt/09162000__m%C3%BCnchen/',\n", - " 'https://www.quora.com/How-many-people-live-in-Munich',\n", - " 'https://earth.esa.int/web/earth-watching/image-of-the-week/content/-/article/munich-germany/']},\n", - " 'llm_for_websearch': {'replies': ['According to the documents retrieved from the web, the population of Munich is approximately 1.47 million as of 2019. However, the most recent estimates suggest that the population has grown to about 1.58 million as of May 31, 2022. Additionally, the current estimated population of Munich is around 1.46 million, with the urban area being much larger at 2.65 million.'],\n", - " 'meta': [{'model': 'gpt-4o-mini-2024-07-18',\n", - " 'index': 0,\n", - " 'finish_reason': 'stop',\n", - " 'usage': {'completion_tokens': 85,\n", - " 'prompt_tokens': 436,\n", - " 'total_tokens': 521}}]}}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "Munich is in southern Germany.\n" + ] + } + ], + "source": [ + "query = \"Where is Munich?\"\n", + "\n", + "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", + "\n", + "# Print the `answer` coming from the ConditionalRouter\n", + "print(result[\"router\"][\"answer\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dBN8eLSKgb16" + }, + "source": [ + "✅ The answer to this query can be found in the defined document.\n", + "\n", + "Now, try a different query that doesn't have an answer in the given document and test if the web search works as expected:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "_v-WdlSy365M", + "outputId": "603c9346-8718-427e-d232-4cc71799a2bb" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "6nhdYK-vHpNM" - }, - "source": [ - "## What's next\n", - "\n", - "🎉 Congratulations! You've built a pipeline with conditional routing! You can now customize the condition for your specific use case and create a custom Haystack 2.0 pipeline to meet your needs.\n", - "\n", - "If you liked this tutorial, there's more to learn about Haystack 2.0:\n", - "- [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline)\n", - "- [Model-Based Evaluation of RAG Pipelines](https://haystack.deepset.ai/tutorials/35_model_based_evaluation_of_rag_pipelines)\n", - "\n", - "To stay up to date on the latest Haystack developments, you can [sign up for our newsletter](https://landing.deepset.ai/haystack-community-updates) or [join Haystack discord community](https://discord.gg/haystack).\n", - "\n", - "Thanks for reading!" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "['According to the documents retrieved from the web, the population of Munich is approximately 1.47 million as of 2019. However, the most recent estimates suggest that the population has grown to about 1.58 million as of May 31, 2022. Additionally, the current estimated population of Munich is around 1.46 million, with the urban area being much larger at 2.65 million.']\n" + ] } - ], - "metadata": { + ], + "source": [ + "query = \"How many people live in Munich?\"\n", + "\n", + "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", + "\n", + "# Print the `replies` generated using the web searched Documents\n", + "print(result[\"llm_for_websearch\"][\"replies\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wUkuXoWnHa5c" + }, + "source": [ + "If you check the whole result, you will see that `websearch` component also provides links to Documents retrieved from the web:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { "colab": { - "provenance": [] + "base_uri": "https://localhost:8080/" }, - "kernelspec": { - "display_name": "Python 3", - "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.9.6" + "id": "_EYLZguZGznY", + "outputId": "df49a576-9961-44b4-e89d-2c5195869360" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'llm': {'meta': [{'model': 'gpt-4o-mini-2024-07-18',\n", + " 'index': 0,\n", + " 'finish_reason': 'stop',\n", + " 'usage': {'completion_tokens': 2,\n", + " 'prompt_tokens': 271,\n", + " 'total_tokens': 273}}]},\n", + " 'websearch': {'links': ['https://en.wikipedia.org/wiki/Munich',\n", + " 'https://worldpopulationreview.com/world-cities/munich-population',\n", + " 'https://en.wikipedia.org/wiki/Demographics_of_Munich',\n", + " 'https://www.macrotrends.net/cities/204371/munich/population',\n", + " 'https://www.britannica.com/place/Munich-Bavaria-Germany',\n", + " 'https://www.statista.com/statistics/519723/munich-population-by-age-group/',\n", + " 'https://www.citypopulation.de/en/germany/bayern/m%C3%BCnchen_stadt/09162000__m%C3%BCnchen/',\n", + " 'https://www.quora.com/How-many-people-live-in-Munich',\n", + " 'https://earth.esa.int/web/earth-watching/image-of-the-week/content/-/article/munich-germany/']},\n", + " 'llm_for_websearch': {'replies': ['According to the documents retrieved from the web, the population of Munich is approximately 1.47 million as of 2019. However, the most recent estimates suggest that the population has grown to about 1.58 million as of May 31, 2022. Additionally, the current estimated population of Munich is around 1.46 million, with the urban area being much larger at 2.65 million.'],\n", + " 'meta': [{'model': 'gpt-4o-mini-2024-07-18',\n", + " 'index': 0,\n", + " 'finish_reason': 'stop',\n", + " 'usage': {'completion_tokens': 85,\n", + " 'prompt_tokens': 436,\n", + " 'total_tokens': 521}}]}}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" } + ], + "source": [ + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6nhdYK-vHpNM" + }, + "source": [ + "## What's next\n", + "\n", + "🎉 Congratulations! You've built a pipeline with conditional routing! You can now customize the condition for your specific use case and create a custom Haystack 2.0 pipeline to meet your needs.\n", + "\n", + "If you liked this tutorial, there's more to learn about Haystack 2.0:\n", + "- [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline)\n", + "- [Model-Based Evaluation of RAG Pipelines](https://haystack.deepset.ai/tutorials/35_model_based_evaluation_of_rag_pipelines)\n", + "\n", + "To stay up to date on the latest Haystack developments, you can [sign up for our newsletter](https://landing.deepset.ai/haystack-community-updates) or [join Haystack discord community](https://discord.gg/haystack).\n", + "\n", + "Thanks for reading!" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 0 + "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.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb b/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb index 1f7f04e4..91552ee8 100644 --- a/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb +++ b/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb @@ -17,7 +17,7 @@ "\n", "> As of version 2.2.0, `Multiplexer` has been deprecated in Haystack and will be completely removed from Haystack as of v2.4.0. We recommend using [BranchJoiner](https://docs.haystack.deepset.ai/docs/branchjoiner) instead. For more details about this deprecation, check out [Haystack 2.2.0 release notes](https://github.com/deepset-ai/haystack/releases/tag/v2.2.0) on Github.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro)." ] }, { @@ -56,7 +56,7 @@ "source": [ "### Install Haystack\n", "\n", - "Install Haystack 2.0 with `pip`:" + "Install Haystack with `pip`:" ] }, { diff --git a/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb b/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb index c591c1e4..7b48edc0 100644 --- a/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb +++ b/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb @@ -12,7 +12,7 @@ "- **Components Used**: [`InMemoryDocumentStore`](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [`InMemoryEmbeddingRetriever`](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [`SentenceTransformersDocumentEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder), [`SentenceTransformersTextEmbedder`](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder)\n", "- **Goal**: After completing this tutorial, you'll have learned how to embed metadata information while indexing documents, to improve retrieval.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n", "\n", "> ⚠️ Note of caution: The method showcased in this tutorial is not always the right approach for all types of metadata. This method works best when the embedded metadata is meaningful. For example, here we're showcasing embedding the \"title\" meta field, which can also provide good context for the embedding model." ] @@ -45,73 +45,14 @@ "source": [ "### Install Haystack\n", "\n", - "Install Haystack 2.0 and other required packages with `pip`:" + "Install Haystack and other required packages with `pip`:" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: haystack-ai in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (2.0.0b7)\n", - "Collecting wikipedia\n", - " Downloading wikipedia-1.4.0.tar.gz (27 kB)\n", - " Preparing metadata (setup.py): started\n", - " Preparing metadata (setup.py): finished with status 'done'\n", - "Requirement already satisfied: boilerpy3 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (1.0.7)\n", - "Requirement already satisfied: haystack-bm25 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (1.0.2)\n", - "Requirement already satisfied: jinja2 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (3.1.3)\n", - "Requirement already satisfied: lazy-imports in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (0.3.1)\n", - "Requirement already satisfied: more-itertools in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (10.2.0)\n", - "Requirement already satisfied: networkx in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (3.2.1)\n", - "Requirement already satisfied: openai>=1.1.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (1.12.0)\n", - "Requirement already satisfied: pandas in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (2.2.0)\n", - "Requirement already satisfied: posthog in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (3.4.1)\n", - "Requirement already satisfied: pyyaml in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (6.0.1)\n", - "Requirement already satisfied: tenacity in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (8.2.3)\n", - "Requirement already satisfied: tqdm in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (4.66.2)\n", - "Requirement already satisfied: typing-extensions in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-ai) (4.9.0)\n", - "Collecting beautifulsoup4 (from wikipedia)\n", - " Using cached beautifulsoup4-4.12.3-py3-none-any.whl.metadata (3.8 kB)\n", - "Requirement already satisfied: requests<3.0.0,>=2.0.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from wikipedia) (2.31.0)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from openai>=1.1.0->haystack-ai) (4.2.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from openai>=1.1.0->haystack-ai) (1.9.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from openai>=1.1.0->haystack-ai) (0.26.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from openai>=1.1.0->haystack-ai) (1.10.9)\n", - "Requirement already satisfied: sniffio in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from openai>=1.1.0->haystack-ai) (1.3.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from requests<3.0.0,>=2.0.0->wikipedia) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from requests<3.0.0,>=2.0.0->wikipedia) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from requests<3.0.0,>=2.0.0->wikipedia) (2.2.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from requests<3.0.0,>=2.0.0->wikipedia) (2024.2.2)\n", - "Collecting soupsieve>1.2 (from beautifulsoup4->wikipedia)\n", - " Using cached soupsieve-2.5-py3-none-any.whl.metadata (4.7 kB)\n", - "Requirement already satisfied: numpy in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from haystack-bm25->haystack-ai) (1.26.4)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from jinja2->haystack-ai) (2.1.5)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from pandas->haystack-ai) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from pandas->haystack-ai) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from pandas->haystack-ai) (2024.1)\n", - "Requirement already satisfied: six>=1.5 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from posthog->haystack-ai) (1.16.0)\n", - "Requirement already satisfied: monotonic>=1.5 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from posthog->haystack-ai) (1.6)\n", - "Requirement already satisfied: backoff>=1.10.0 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from posthog->haystack-ai) (2.2.1)\n", - "Requirement already satisfied: httpcore==1.* in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (1.0.2)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /Users/tuanacelik/opt/anaconda3/envs/mistral/lib/python3.12/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (0.14.0)\n", - "Using cached beautifulsoup4-4.12.3-py3-none-any.whl (147 kB)\n", - "Using cached soupsieve-2.5-py3-none-any.whl (36 kB)\n", - "Building wheels for collected packages: wikipedia\n", - " Building wheel for wikipedia (setup.py): started\n", - " Building wheel for wikipedia (setup.py): finished with status 'done'\n", - " Created wheel for wikipedia: filename=wikipedia-1.4.0-py3-none-any.whl size=11678 sha256=17926b00d77f1d294e927f20b0e52e7a137fcd6b219ca85e63570f3b5c7d58f3\n", - " Stored in directory: /Users/tuanacelik/Library/Caches/pip/wheels/63/47/7c/a9688349aa74d228ce0a9023229c6c0ac52ca2a40fe87679b8\n", - "Successfully built wikipedia\n", - "Installing collected packages: soupsieve, beautifulsoup4, wikipedia\n", - "Successfully installed beautifulsoup4-4.12.3 soupsieve-2.5 wikipedia-1.4.0\n" - ] - } - ], + "outputs": [], "source": [ "%%bash\n", "\n", diff --git a/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb b/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb index 08d87ba7..1febda65 100644 --- a/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb +++ b/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb @@ -14,7 +14,7 @@ "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys) and be familiar with [creating pipelines](https://docs.haystack.deepset.ai/docs/creating-pipelines)\n", "- **Goal**: After completing this tutorial, you will have learned how to build chat applications that demonstrate agent-like behavior using OpenAI's function calling feature.\n", "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" + "> This tutorial uses the latest version of Haystack 2.x (`haystack-ai`). For more information on Haystack 2.0, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" ] }, { @@ -45,7 +45,7 @@ "source": [ "## Setting up the Development Environment\n", "\n", - "Install Haystack 2.0 and [sentence-transformers](https://pypi.org/project/sentence-transformers/) using pip:" + "Install Haystack and [sentence-transformers](https://pypi.org/project/sentence-transformers/) using pip:" ] }, {