diff --git a/index.toml b/index.toml index 534005c..6609fe1 100644 --- a/index.toml +++ b/index.toml @@ -276,7 +276,6 @@ topics = ["Web-QA", "Data Scraping", "RAG"] [[cookbook]] title = "Build a GitHub Issue Resolver Agent" notebook = "github_issue_resolver_agent.ipynb" -new = true topics = ["Function Calling", "Agents"] [[cookbook]] @@ -287,31 +286,26 @@ topics = ["Metadata"] [[cookbook]] title = "Building an Interactive Feedback Review Agent with Azure AI Search and Haystack" notebook = "feedback-analysis-agent-with-AzureAISearch.ipynb" -new = true topics = ["AzureAISearch", "Agents", "RAG"] [[cookbook]] title = "Hybrid RAG Pipeline with Breakpoints" notebook = "hybrid_rag_pipeline_with_breakpoints.ipynb" -new = true topics = ["Advanced Retrieval", "RAG"] [[cookbook]] title = "Breakpoint on Agent in a Pipeline" notebook = "agent-breakpoints.ipynb" -new = true topics = ["Agents"] [[cookbook]] title = "Simple Keyword Extraction using OpenAIChatGenerator" notebook = "keyword-extraction.ipynb" -new = true topics = ["Keyword Extraction"] [[cookbook]] title = "DevOps Support Agent with Human in the Loop" notebook = "agent_with_human_in_the_loop.ipynb" -new = true topics = ["Function Calling", "Agents"] [[cookbook]] @@ -323,14 +317,12 @@ topics = ["Multimodal"] [[cookbook]] title = "Build a GitHub PR Creator Agent" notebook = "github_pr_creator_agent.ipynb" -new = true topics = ["Function Calling", "Agents"] [[cookbook]] title = "Trace and Evaluate RAG with Arize Phoenix" notebook = "arize_phoenix_evaluate_haystack_rag.ipynb" topics = ["Observability", "Evaluation", "RAG"] -new = true [[cookbook]] title = "AI Guardrails: Content Moderation and Safety with Open Language Models" @@ -349,3 +341,9 @@ title = "Calculating a Hallucination Score with the OpenAIChatGenerator" notebook = "hallucination_score_calculator.ipynb" topics = ["RAG"] new = true + +[[cookbook]] +title = "Agent-Powered Retrieval with Haystack" +notebook = "agent_powered_retrieval.ipynb" +topics = ["Agents", "Advanced Retrieval", "Metadata"] +new = true diff --git a/notebooks/agent_powered_retrieval.ipynb b/notebooks/agent_powered_retrieval.ipynb new file mode 100644 index 0000000..eae590f --- /dev/null +++ b/notebooks/agent_powered_retrieval.ipynb @@ -0,0 +1,1200 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "auBd8vew6kg_" + }, + "source": [ + "# Agent-Powered Retrieval with Haystack\n", + "\n", + "*Notebook by [Bilge Yücel](https://www.linkedin.com/in/bilge-yucel/)*\n", + "\n", + "In this notebook, you'll build an intelligent **movie recommendation assistant** powered by **Haystack** and [**Qdrant**](https://haystack.deepset.ai/integrations/qdrant-document-store). You'll learn how to combine **sparse vector search**, **metadata filtering (payload)**, and **LLM-based agents** to create a system that can understand natural language queries and recommend relevant movies from a curated dataset.\n", + "\n", + "By the end of this notebook, you’ll have a fully working assistant that can answer queries like:\n", + "\n", + "* “Find me a highly-rated action movie about car racing.”\n", + "* “Can you recommend five japanese thrillers?”\n", + "* “What can I watch with my kids, about animals?”\n", + "\n", + "This assistant will be implemented as a **tool-calling agent** and have access to a `retrieval_tool` that can retrieve the information from the database based on the **generated query** and [**filters**](https://docs.haystack.deepset.ai/docs/metadata-filtering)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "H4Mh3QIl67ap" + }, + "source": [ + "## 🧱 Setting up the Development Environment\n", + "\n", + "Install required dependencies:\n", + "* `haystack-ai`: For building pipelines and agents\n", + "* `datasets`: For loading the movie dataset from Hugging Face\n", + "* `qdrant-haystack`: For vector database access\n", + "* `fastembed-haystack`: For using FastEmbed library for sparse embedding generation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XxDJWup50JkH" + }, + "outputs": [], + "source": [ + "!pip install -q -U haystack-ai qdrant-haystack fastembed-haystack datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xOHuV-xEC2v4" + }, + "source": [ + "Add credentials for [OpenAI API](https://platform.openai.com/api-keys) and [Qdrant Cloud](https://qdrant.tech/documentation/cloud/authentication/)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "TwHqi6oHCiCN" + }, + "outputs": [], + "source": [ + "import os\n", + "from google.colab import userdata\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n", + "os.environ[\"QDRANT_API_KEY\"] = userdata.get('QDRANT_API_KEY')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Tbycmsz2nQo3" + }, + "source": [ + "## 🎬 Loading Movie Dataset\n", + "\n", + "Load the [Pablinho/movies-dataset](https://huggingface.co/datasets/Pablinho/movies-dataset) from Hugging Face" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "G3aReUfzRI03" + }, + "outputs": [], + "source": [ + "from datasets import load_dataset\n", + "\n", + "data = load_dataset('Pablinho/movies-dataset', split=\"train\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "aW53hMzDjyS1", + "outputId": "02fa09ef-4245-4b81-8def-7196b3bf2c9b" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Release_Date': '2022-01-28',\n", + " 'Title': 'The Ice Age Adventures of Buck Wild',\n", + " 'Overview': \"The fearless one-eyed weasel Buck teams up with mischievous possum brothers Crash & Eddie as they head off on a new adventure into Buck's home: The Dinosaur World.\",\n", + " 'Popularity': 1431.307,\n", + " 'Vote_Count': '737',\n", + " 'Vote_Average': '7.1',\n", + " 'Original_Language': 'en',\n", + " 'Genre': 'Animation, Comedy, Adventure, Family',\n", + " 'Poster_Url': 'https://image.tmdb.org/t/p/original/zzXFM4FKDG7l1ufrAkwQYv2xvnh.jpg'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data[14]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "N8pKIiETnMfu" + }, + "source": [ + "### Converting to Haystack Documents\n", + "\n", + "Convert all movies into Haystack [Document](https://docs.haystack.deepset.ai/docs/data-classes#document) objects. `Document` dataclass holds text, tables, and binary data as content alongside with metadata." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "oBGncAz9K3Lf" + }, + "outputs": [], + "source": [ + "from haystack.dataclasses import Document\n", + "\n", + "def movie_to_document(movie_dataset):\n", + " docs = []\n", + " for movie in movie_dataset:\n", + " # Process votes\n", + " vote_avg_raw = movie.get('Vote_Average')\n", + " try:\n", + " rating = float(vote_avg_raw)\n", + " except (TypeError, ValueError):\n", + " rating = 5.0 # average value\n", + "\n", + " # Process genre\n", + " genre_raw = movie.get(\"Genre\")\n", + " if isinstance(genre_raw, str):\n", + " genre_list = [g.lower() for g in genre_raw.split(\", \")]\n", + " else:\n", + " genre_list = []\n", + "\n", + " # Create Document\n", + " doc = Document(\n", + " content=movie.get('Overview', ''),\n", + " meta={\n", + " 'title': movie.get('Title', ''),\n", + " 'rating': rating,\n", + " 'language': movie.get('Original_Language', ''),\n", + " 'genre': genre_list,\n", + " }\n", + " )\n", + " docs.append(doc)\n", + " return docs" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "NOJ_MLVaTodK" + }, + "outputs": [], + "source": [ + "documents = movie_to_document(data)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1aCW550hJTi_", + "outputId": "3c6e6f75-e74d-44bc-9b96-8219f3812e53" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Document(id=1f5260fdf65717876901d115502f2d3286cc76ba582a05ca38249b16fb7423d7, content: 'The fearless one-eyed weasel Buck teams up with mischievous possum brothers Crash & Eddie as they he...', meta: {'title': 'The Ice Age Adventures of Buck Wild', 'rating': 7.1, 'language': 'en', 'genre': ['animation', 'comedy', 'adventure', 'family']})" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "documents[14]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "H1XO5VEAXw6T" + }, + "source": [ + "## 📄 Creating Sparse Embeddings\n", + "\n", + "Use [Qdrant/minicoil-v1](https://huggingface.co/Qdrant/minicoil-v1) sparse neural embedding model with [FastembedSparseDocumentEmbedder](https://docs.haystack.deepset.ai/docs/fastembedsparsedocumentembedder) component. We'll use `meta_fields_to_embed` parameter to include `title` metadata into the content.\n", + "After initializing `FastembedSparseDocumentEmbedder`, run `.warm_up()` to load the embedding model and get it ready to run. Then, run the component with documents to create embedding for each document." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Qyb-z1sYYN1u" + }, + "outputs": [], + "source": [ + "from haystack_integrations.components.embedders.fastembed import FastembedSparseDocumentEmbedder\n", + "\n", + "sparse_doc_embedder = FastembedSparseDocumentEmbedder(model=\"Qdrant/minicoil-v1\", meta_fields_to_embed=[\"title\"])\n", + "sparse_doc_embedder.warm_up()\n", + "\n", + "sparse_embedder_result = sparse_doc_embedder.run(documents=documents)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "m7zOH21DBFU4", + "outputId": "fd83a991-9956-4c6d-9d8f-7df6f5956abb" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Document(id=1f5260fdf65717876901d115502f2d3286cc76ba582a05ca38249b16fb7423d7, content: 'The fearless one-eyed weasel Buck teams up with mischievous possum brothers Crash & Eddie as they he...', meta: {'title': 'The Ice Age Adventures of Buck Wild', 'rating': 7.1, 'language': 'en', 'genre': ['animation', 'comedy', 'adventure', 'family']}, sparse_embedding: vector with 71 non-zero elements)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sparse_embedder_result[\"documents\"][14]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-VpfiOIoXaFS" + }, + "source": [ + "## ☁️ Writing Movies into Qdrant Cloud\n", + "\n", + "\n", + "Initialize the [QdrantDocumentStore](https://docs.haystack.deepset.ai/docs/qdrant-document-store) to connect to your Qdrant Cloud cluster.\n", + "\n", + "You'll need to provide the following:\n", + "\n", + "* `url`: The endpoint of your Qdrant Cloud instance\n", + "* `index`: The name of the index where documents will be stored\n", + "* `api_key`: Your Qdrant API key (set this securely via environment variables)\n", + "\n", + "Make sure to set:\n", + "\n", + "* `use_sparse_embeddings`=True to enable sparse vector support\n", + "* `payload_fields_to_index` to define which metadata fields (like genre, rating, language) are indexed for filtering\n", + "\n", + "This setup ensures your vector store supports both sparse retrieval and metadata filtering, which are essential for advanced search scenarios.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Y7Rqhs2FeSgL" + }, + "outputs": [], + "source": [ + "from haystack_integrations.document_stores.qdrant import QdrantDocumentStore\n", + "from haystack.utils import Secret\n", + "\n", + "document_store = QdrantDocumentStore(\n", + " url=\"https://*******.us-west-1-0.aws.cloud.qdrant.io:6333\",\n", + " index=\"movie-assistant\",\n", + " api_key=Secret.from_env_var(\"QDRANT_API_KEY\"),\n", + " use_sparse_embeddings=True,\n", + " recreate_index=True,\n", + " payload_fields_to_index=[\n", + " {\"field_name\":\"meta.language\", \"field_schema\":\"keyword\"},\n", + " {\"field_name\":\"meta.rating\", \"field_schema\":\"float\"},\n", + " {\"field_name\":\"meta.genre\", \"field_schema\":\"keyword\"},\n", + " {\"field_name\":\"meta.title\", \"field_schema\":\"keyword\"}\n", + " ]\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UfM5ay807Tqc" + }, + "source": [ + "Now, call `.write_documents()` with the `Document` list." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "H8QfSbi-KRvf" + }, + "outputs": [], + "source": [ + "document_store.write_documents(documents=sparse_embedder_result[\"documents\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GhPOdK9kIhuf" + }, + "source": [ + "## 🔍 Creating a Sparse Retrieval Pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TMXgVkLFX24n" + }, + "source": [ + "Build a sparse retrieval pipeline that accepts a user query about what type of movie that they're looking for and retrieves relevant movie from our database.\n", + "\n", + "First, initialize and add a [FastembedSparseTextEmbedder](https://docs.haystack.deepset.ai/docs/fastembedsparsetextembedder) with the `Qdrant/minicoil-v1` model to our pipeline. This component will convert user queries into sparse vector representations. We then add a [QdrantSparseEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/qdrantsparseembeddingretriever), which connects to our `QdrantDocumentStore` and uses those vectors to retrieve the top 5 most relevant movies.\n", + "\n", + "Finally, we connect the two components by passing the output from the embedder into the retriever's input. This creates a functional query pipeline that transforms a natural language input into a sparse embedding and returns matching results from Qdrant." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "RBdmBshEZSJT", + "outputId": "e077e520-040e-4d6b-b1bb-00f5fd7594bd" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + "🚅 Components\n", + " - sparse_text_embedder: FastembedSparseTextEmbedder\n", + " - sparse_retriever: QdrantSparseEmbeddingRetriever\n", + "🛤️ Connections\n", + " - sparse_text_embedder.sparse_embedding -> sparse_retriever.query_sparse_embedding (SparseEmbedding)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from haystack_integrations.components.retrievers.qdrant import QdrantSparseEmbeddingRetriever\n", + "from haystack_integrations.components.embedders.fastembed import FastembedSparseTextEmbedder\n", + "from haystack import Pipeline\n", + "\n", + "sparse_query_pipeline = Pipeline()\n", + "sparse_query_pipeline.add_component(\"sparse_text_embedder\", FastembedSparseTextEmbedder(model=\"Qdrant/minicoil-v1\"))\n", + "sparse_query_pipeline.add_component(\"sparse_retriever\", QdrantSparseEmbeddingRetriever(document_store=document_store, top_k=5))\n", + "sparse_query_pipeline.connect(\"sparse_text_embedder.sparse_embedding\", \"sparse_retriever.query_sparse_embedding\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NN5kQqxp8oFZ" + }, + "source": [ + "Run the pipeline with a query" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "6vswaaLfXS1W", + "outputId": "f967e728-8165-4f28-c30d-c349b8fbfb10" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Calculating sparse embeddings: 100%|██████████| 1/1 [00:00<00:00, 172.81it/s]\n" + ] + } + ], + "source": [ + "sparse_docs = sparse_query_pipeline.run({\"text\":\"A movie about car race\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pvbXEaUS8lmf" + }, + "source": [ + "See the retrieved movies:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "f8TNs8JPXAul", + "outputId": "0000b062-71d2-46fd-e335-684b52e9a694" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Title: Cannonball Run II\n", + "Content: The original characters from the first Cannonball Run movie compete in an illegal race across the country once more in various cars and trucks.\n", + "Rating: 5.5\n", + "Genres: ['action', 'comedy']\n", + "---\n", + "Title: Ford v Ferrari\n", + "Content: American car designer Carroll Shelby and the British-born driver Ken Miles work together to battle corporate interference, the laws of physics, and their own personal demons to build a revolutionary race car for Ford Motor Company and take on the dominating race cars of Enzo Ferrari at the 24 Hours of Le Mans in France in 1966.\n", + "Rating: 8.0\n", + "Genres: ['drama', 'action', 'history']\n", + "---\n", + "Title: Driven\n", + "Content: Talented rookie race-car driver Jimmy Bly has started losing his focus and begins to slip in the race rankings. It's no wonder, with the immense pressure being shoveled on him by his overly ambitious promoter brother as well as Bly's romance with his arch rival's girlfriend Sophia. With much riding on Bly, car owner Carl Henry brings former racing star Joe Tanto on board to help Bly. To drive Bly back to the top of the rankings, Tanto must first deal with the emotional scars left over from a tragic racing accident which nearly took his life.\n", + "Rating: 5.1\n", + "Genres: ['action']\n", + "---\n", + "Title: New Initial D the Movie - Legend 2: Racer\n", + "Content: The planned film trilogy retells the beginning of the story from Shuuichi Shigeno's original car-racing manga. High school student Takumi Fujiwara works as a gas station attendant during the day and a delivery boy for his father's tofu shop during late nights. Little does he know that his precise driving skills and his father's modified Toyota Sprinter AE86 Trueno make him the best amateur road racer on Mt. Akina's highway. Because of this, racing groups from all over the Gunma prefecture issue challenges to Takumi to see if he really has what it takes to be a road legend.\n", + "Rating: 8.1\n", + "Genres: ['animation', 'drama', 'action']\n", + "---\n", + "Title: The Art of Racing in the Rain\n", + "Content: A family dog - with a near-human soul and a philosopher's mind - evaluates his life through the lessons learned by his human owner, a race-car driver.\n", + "Rating: 8.3\n", + "Genres: ['comedy', 'drama', 'romance']\n", + "---\n" + ] + } + ], + "source": [ + "for d in sparse_docs[\"sparse_retriever\"][\"documents\"]:\n", + " print(\"Title:\", d.meta[\"title\"])\n", + " print(\"Content:\", d.content)\n", + " print(\"Rating:\", d.meta[\"rating\"])\n", + " print(\"Genres:\", d.meta[\"genre\"])\n", + " print(\"---\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "K4pDqocM4l1A" + }, + "source": [ + "### 🗑️ Adding Filters\n", + "\n", + "Let's enrich the user query by combining semantic search with filters.\n", + "\n", + "The text query \"A movie about car race\" is first converted into a sparse embedding to make a search in movie descriptions. In addition, we apply two filters: the movie must have a rating of at least 7, and its genre must include \"action\". By combining both semantic similarity and structured filtering, we ensure that the results are not only topically relevant but also meet specific quality and genre constraints." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Cv4YK_aTWofQ", + "outputId": "b28507cd-15a7-4815-a6e0-3e06a74a1913" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Calculating sparse embeddings: 100%|██████████| 1/1 [00:00<00:00, 236.82it/s]\n" + ] + } + ], + "source": [ + "sparse_docs = sparse_query_pipeline.run({\n", + " \"sparse_text_embedder\": {\"text\": \"A movie about car race\"},\n", + " \"sparse_retriever\": {\"filters\":{\n", + " \"operator\": \"AND\",\n", + " \"conditions\": [\n", + " {\"field\":\"meta.rating\", \"operator\":\">=\", \"value\":7},\n", + " {\"field\":\"meta.genre\", \"operator\": \"==\", \"value\": \"action\"},\n", + " ],\n", + " }}\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "collapsed": true, + "id": "dtZ2gXzyXTP4", + "outputId": "9590365b-2064-4bb4-f06f-43d7e7d1b9dc" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Title: Ford v Ferrari\n", + "Content: American car designer Carroll Shelby and the British-born driver Ken Miles work together to battle corporate interference, the laws of physics, and their own personal demons to build a revolutionary race car for Ford Motor Company and take on the dominating race cars of Enzo Ferrari at the 24 Hours of Le Mans in France in 1966.\n", + "Rating: 8.0\n", + "Genres: ['drama', 'action', 'history']\n", + "---\n", + "Title: New Initial D the Movie - Legend 2: Racer\n", + "Content: The planned film trilogy retells the beginning of the story from Shuuichi Shigeno's original car-racing manga. High school student Takumi Fujiwara works as a gas station attendant during the day and a delivery boy for his father's tofu shop during late nights. Little does he know that his precise driving skills and his father's modified Toyota Sprinter AE86 Trueno make him the best amateur road racer on Mt. Akina's highway. Because of this, racing groups from all over the Gunma prefecture issue challenges to Takumi to see if he really has what it takes to be a road legend.\n", + "Rating: 8.1\n", + "Genres: ['animation', 'drama', 'action']\n", + "---\n", + "Title: Watch Out, We're Mad\n", + "Content: After a tied 1st place in a local stunt race, two drivers start a contest to decide who of them will own the prize, a dune buggy. But when a mobster destroys the car, they are determined to get it back.\n", + "Rating: 7.5\n", + "Genres: ['action', 'comedy']\n", + "---\n", + "Title: New Initial D the Movie - Legend 1: Awakening\n", + "Content: The first movie in a trilogy, focusing on the battle against the Takahashi brothers. High school student Takumi Fujiwara works as a gas station attendant during the day and a delivery boy for his father's tofu shop during late nights. Little does he know that his precise driving skills and his father's modified Toyota Sprinter AE86 Trueno make him the best amateur road racer on Mt. Akina's highway. Because of this, racing groups from all over the Gunma prefecture issue challenges to Takumi to see if he really has what it takes to be a road legend.\n", + "Rating: 8.3\n", + "Genres: ['animation', 'action']\n", + "---\n", + "Title: New Initial D the Movie - Legend 3: Dream\n", + "Content: Mt. Akina's new downhill racing hero Fujiwara Takumi prepares for the final showdown against Red Sun's unbeaten leader and Akagi's fastest driver, Takahashi Ryosuke.\n", + "Rating: 7.4\n", + "Genres: ['action', 'animation', 'drama']\n", + "---\n" + ] + } + ], + "source": [ + "for d in sparse_docs[\"sparse_retriever\"][\"documents\"]:\n", + " print(\"Title:\", d.meta[\"title\"])\n", + " print(\"Content:\", d.content)\n", + " print(\"Rating:\", d.meta[\"rating\"])\n", + " print(\"Genres:\", d.meta[\"genre\"])\n", + " print(\"---\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JeBALnyqBm0L" + }, + "source": [ + "## 📐 Filtering without Query\n", + "\n", + "Initialize a metadata-only retriever ([FilterRetriever](https://docs.haystack.deepset.ai/docs/filterretriever)) that allows filtering movies based on fields like rating, language, or genre without semantic matching." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "id": "AnLKVd-4CTWM" + }, + "outputs": [], + "source": [ + "from haystack.components.retrievers.filter_retriever import FilterRetriever\n", + "\n", + "filter_retriever = FilterRetriever(document_store=document_store)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Xqvjg2uUDK9i" + }, + "source": [ + "Run `FilterRetriever` to get all movies rated 9.0 and above:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "uYFgx0wzhJt7" + }, + "outputs": [], + "source": [ + "filter_retriever.run({\n", + " \"field\":\"meta.rating\",\"operator\":\">=\",\"value\":9.0\n", + " })" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1zOCtUbfY4Hj" + }, + "source": [ + "## 🤖 Building the Movie Recommendation Agent\n", + "In this section, we bring everything together by building a movie recommendation agent that leverages tools, a system prompt, and an LLM.\n", + "\n", + "### 🛠️ Retrieval Tool\n", + "To interact with the data, the agent uses a retrieval tool. Every tool in Haystack requires a **name**, a **description**, a **function to call**, and a **parameter schema**.\n", + "\n", + "We start by defining a retrieval function that dynamically chooses between semantic search and metadata-based filtering, depending on the input. If the user provides a query, the tool uses the sparse retrieval pipeline. If only filters are provided, it falls back to a metadata filter retriever. This logic gives the agent the flexibility to handle a wide range of user queries—from simple genre filters to more nuanced, semantic prompts." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "id": "ptPCVVRKghQY" + }, + "outputs": [], + "source": [ + "from typing import Optional, Dict, Any, List\n", + "\n", + "def retrieval_function(\n", + " query: Optional[str] = None,\n", + " metadata_filters: Optional[Dict[str, Any]] = None,\n", + " top_k: Optional[int] = 5\n", + ") -> List[Document]:\n", + "\n", + " retrieved_documents = []\n", + " if not query and not metadata_filters:\n", + " raise ValueError(\"At least one of 'query' or 'metadata_filters' must be provided.\")\n", + "\n", + " # Retrieve documents based on the input provided\n", + " if query:\n", + " retrieved_documents = sparse_query_pipeline.run({\n", + " \"text\": query,\n", + " \"filters\": metadata_filters, # Can include metadata filters in semantic query\n", + " \"top_k\": top_k\n", + " })[\"sparse_retriever\"]\n", + " # If there's only `metadata_filters`\n", + " else:\n", + " retrieved_documents = filter_retriever.run(filters=metadata_filters)\n", + " if len(retrieved_documents.get(\"documents\")) > top_k:\n", + " retrieved_documents[\"documents\"] = retrieved_documents[\"documents\"][:top_k]\n", + "\n", + " return retrieved_documents" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bcCbVu9PDWTG" + }, + "source": [ + "Define the JSON schema for tool parameters, including `query`, `top_k`, and `metadata_filters`, to validate and structure tool calls." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "id": "bkYbSp1g8FQ3" + }, + "outputs": [], + "source": [ + "parameters = {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"top_k\": {\n", + " \"type\": \"integer\",\n", + " \"description\": \"number of movies to get\",\n", + " \"default\": 5,\n", + " },\n", + " \"query\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"query to retrieve movies based on semantic similarity of the description\"\n", + " },\n", + " \"metadata_filters\": {\n", + " \"type\": \"object\",\n", + " \"description\": \"metadata filters to filter the movies based on user's preference\",\n", + " \"properties\": {\n", + " \"operator\": {\n", + " \"type\": \"string\",\n", + " \"enum\": [\"AND\", \"OR\", \"NOT\"],\n", + " \"default\": \"AND\",\n", + " \"description\": \"operator to combine the conditions\"\n", + " },\n", + " \"conditions\": {\n", + " \"type\": \"array\",\n", + " \"items\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"field\": {\n", + " \"type\": \"string\",\n", + " \"enum\": [\"meta.title\", \"meta.rating\", \"meta.genre\", \"meta.language\"]\n", + " },\n", + " \"operator\": {\n", + " \"type\": \"string\",\n", + " \"enum\": [\"==\", \"!=\", \">\", \"<\", \">=\", \"<=\", \"not in\"]\n", + " },\n", + " \"value\": {\n", + " \"oneOf\": [\n", + " { \"type\": \"string\" },\n", + " { \"type\": \"integer\" },\n", + " {\n", + " \"type\": \"array\",\n", + " \"items\": {\"type\": \"string\"}\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " \"required\": [\"field\", \"operator\", \"value\"],\n", + " \"additionalProperties\": False\n", + " }\n", + " }\n", + " },\n", + " \"required\": [\"operator\", \"conditions\"],\n", + " \"additionalProperties\": False\n", + " }\n", + " },\n", + " \"additionalProperties\": False,\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "okKDWurBDco_" + }, + "source": [ + "Format the output of retrieved movies into readable text, including the title, content, rating, genre, and language. Then, wrap the retrieval function as a Haystack [`Tool`](https://docs.haystack.deepset.ai/docs/tool), specifying how it should be invoked by the agent and how outputs should be displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "id": "WMPjBjiX7N6a" + }, + "outputs": [], + "source": [ + "from haystack.tools import Tool\n", + "\n", + "def movie_to_string(documents) -> str:\n", + " result_str = \"\"\n", + " for document in documents:\n", + " result_str += f\"Movie details for {document.meta['title']}:\\n{document.content}\\nRating:{document.meta['rating']}\\nGenres:{document.meta['genre']}\\nLanguage:{document.meta['language']}\\n---\\n\"\n", + " return result_str\n", + "\n", + "retrieval_tool = Tool(\n", + " name=\"retrieval_tool\",\n", + " description=\"Use this tool to get movies fitting to the user criteria\",\n", + " function=retrieval_function,\n", + " parameters=parameters,\n", + " outputs_to_string={\"source\": \"documents\", \"handler\": movie_to_string}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qRyLFvXi9rnV" + }, + "source": [ + "### 🧠 Agent\n", + "\n", + "First, you need to define a system prompt for the agent. This prompt instructs how to interpret user queries, generate tool inputs, and handle fallback strategies." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "id": "4wQQnww8Y_Hl" + }, + "outputs": [], + "source": [ + "prompt = \"\"\"\n", + "You are a movie retrieval assistant that helps users find movies from a database using `retrieval_tool`. Users describe what they are looking for in natural language.\n", + "\n", + "Each movie entry in the database contains the following fields:\n", + "\n", + "* `content`: *str* — movie description\n", + "* `meta.title`: *str* — movie title\n", + "* `meta.rating`: *float* — rating score (e.g., 8.3)\n", + "* `meta.genre`: *List[str]* — genres (e.g., `[\"drama\", \"thriller\"]`)\n", + "* `meta.language`: *str* — original language (options are: 'bn', 'te', 'pt', 'ml', 'ja', 'it', 'lv', 'ar', 'da', 'no', 'hu', 'la', 'el', 'he', 'fi', 'ta', 'sv', 'ms', 'sr', 'ro', 'ru', 'nb', 'nl', 'id', 'cs', 'fr', 'th', 'tl', 'de', 'uk', 'et', 'es', 'pl', 'fa', 'is', 'eu', 'tr', 'cn', 'zh', 'en', 'ko', 'ca', 'hi'\n", + "\n", + "### Task\n", + "\n", + "Your goal is to generate an input for the `retrieval_tool` using the user's query, which may include:\n", + "\n", + "* `query`: a natural language string to match against the `content` field (movie description) — optional\n", + "* `metadata_filters`: dictionary of filtering conditions using fields in the `meta.*` namespace — optional\n", + "* `top_k`: number of movies to retrieve — default is 5 unless the user specifies otherwise\n", + "\n", + "**Important:** At least one of `query` or `metadata_filters` **must** be included. Use the most appropriate one based on the user request.\n", + "\n", + "### Generation Guidelines\n", + "\n", + "- Use `metadata_filters` when the user specifies in the query:\n", + " * **Genres** (e.g., \"I'd like to watch an action movie\", \"A romance movie...\")\n", + " * **Rating thresholds** (e.g., \"at least 7.5\", \"above 6\", \"a good movie\", \"quality movie\")\n", + " * **Language constraints** (e.g., \"only English movies\")\n", + "\n", + "- Use `query` when the user describes:\n", + " * **Plot or story themes** (e.g., \"a movie about friendship and betrayal\")\n", + " * **Emotions or tone** (e.g., \"feel-good\", \"dark comedy\")\n", + " * **Character types or settings** (e.g., \"set in space\", \"female-led action\")\n", + "\n", + "- Use both `query` and `metadata_filters` if there is enough information in the user query for both `query` and `metadata_filters`.\n", + "- If the user asks for a specific number of movies, set that value as `top_k`.\n", + "- If the user only specifies a genre, it's acceptable to return only `metadata_filters` — no query needed.\n", + "- All metadata values must be lowercase\n", + "- If required, you can combine the conditions with \"AND\", \"NOT\" or \"OR\" operators.\n", + "\n", + "### Examples\n", + "\n", + "Here are the tool call parameters for user queries:\n", + "\n", + "* User: \"Can you recommned horror movies with bad ratings?\"\n", + "```json\n", + "{\n", + " \"metadata_filters\": {\n", + " \"operator\": \"AND\",\n", + " \"conditions\": [\n", + " {\"field\": \"meta.genre\", \"operator\": \"==\", \"value\": \"horror\"},\n", + " {\"field\": \"meta.rating\", \"operator\": \"<\", \"value\": 5}\n", + " ]\n", + " }\n", + "}\n", + "```\n", + "\n", + "* User: \"I want a powerful courtroom drama, something emotionally intense, high rated\"\n", + "```json\n", + "{\n", + " \"query\": \"powerful courtroom drama, emotionally intense\",\n", + " \"metadata_filters\": {\n", + " \"operator\": \"AND\",\n", + " \"conditions\": [\n", + " {\"field\": \"meta.genre\", \"operator\": \"==\", \"value\": \"drama\"},\n", + " {\"field\": \"meta.rating\", \"operator\": \">=\", \"value\": 7}\n", + " ]\n", + " }\n", + "}\n", + "```\n", + "\n", + "* User: \"Give me five Japanese thrillers.\"\n", + "```json\n", + "{\n", + " \"top_k\": 5,\n", + " \"metadata_filters\": {\n", + " \"operator\": \"AND\",\n", + " \"conditions\": [\n", + " {\"field\": \"meta.language\", \"operator\": \"==\", \"value\": \"ja\"},\n", + " {\"field\": \"meta.genre\", \"operator\": \"==\", \"value\": \"thriller\"}\n", + " ]\n", + " }\n", + "}\n", + "```\n", + "\n", + "* User: \"I'd like to watch an action movie that is not drama\"\n", + "```json\n", + "{\n", + " \"metadata_filters\": {\n", + " \"operator\": \"AND\",\n", + " \"conditions\": [\n", + " {\"field\": \"meta.genre\", \"operator\": \"==\", \"value\": \"action\"},\n", + " {\"field\": \"meta.genre\", \"operator\": \"not in\", \"value\": [\"drama\"]}\n", + " ]\n", + " }\n", + "}\n", + "```\n", + "\n", + "#### Fallback Strategy\n", + "If the search space is too narrow (i.e., few or no results), relax the filters and/or simplify the query to allow broader but still relevant results. Aim to preserve the user’s intent while increasing recall.\n", + "Don't ask for confirmation from the user. Just tell that you broadened the search with the result and return the latest list.\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nzRtazGR-3zO" + }, + "source": [ + "Initialize the Haystack [`Agent`](https://docs.haystack.deepset.ai/docs/agent) with the retrieval tool and the system prompt. This Agent can understand user queries and respond with movie recommendations." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "id": "v87YvhFa7KKo" + }, + "outputs": [], + "source": [ + "from haystack.components.agents import Agent\n", + "from haystack.components.generators.chat import OpenAIChatGenerator\n", + "from haystack.dataclasses import ChatMessage\n", + "\n", + "movie_agent = Agent(\n", + " chat_generator=OpenAIChatGenerator(model=\"gpt-4o-mini\"),\n", + " system_prompt=prompt,\n", + " tools=[retrieval_tool]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "O1y69h1EAKEs" + }, + "source": [ + "Send a sample query to the Agent. The Agent interprets the intent, invokes the retrieval tool, and returns suitable movie recommendations." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "-iruqbYkZJ2J", + "outputId": "ed487dd6-4405-414b-ce71-e52e67a52f59" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Calculating sparse embeddings: 100%|██████████| 1/1 [00:00<00:00, 198.07it/s]\n" + ] + } + ], + "source": [ + "query = \"Find me a highly-rated action movie about car racing.\"\n", + "# query = \"What can I watch with my kids, about animals?\"\n", + "# query = \"Can you recommend five japanese thrillers?\"\n", + "# query = \"I'd like to watch a german action movie but I don't like thriller and horror\"\n", + "# query = \"I love Titanic. Which movie should I watch next? It should be a highly rated movie though\"\n", + "\n", + "messages = movie_agent.run([ChatMessage.from_user(query)])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "SqCtbr1rD6tl" + }, + "source": [ + "Print `messages[\"last_message\"].text` to see the Agent's final response" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "9K6LfLICZHi_", + "outputId": "2aec125f-6c95-406c-c55d-1334f312ed4c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Here are some highly-rated action movies about car racing:\n", + "\n", + "1. **Ford v Ferrari**\n", + " - **Rating:** 8.0\n", + " - **Genres:** Drama, Action, History\n", + " - **Description:** American car designer Carroll Shelby and the British-born driver Ken Miles work together to build a revolutionary race car for Ford Motor Company and take on the dominating race cars of Enzo Ferrari at the 24 Hours of Le Mans in France in 1966.\n", + " - **Language:** English\n", + "\n", + "2. **New Initial D the Movie - Legend 2: Racer**\n", + " - **Rating:** 8.1\n", + " - **Genres:** Animation, Drama, Action\n", + " - **Description:** High school student Takumi Fujiwara discovers his precise driving skills while working as a gas station attendant and becomes the best amateur road racer on Mt. Akina's highway.\n", + " - **Language:** Japanese\n", + "\n", + "3. **Watch Out, We're Mad**\n", + " - **Rating:** 7.5\n", + " - **Genres:** Action, Comedy\n", + " - **Description:** After a tied 1st place in a local stunt race, two drivers start a contest to decide who will own a dune buggy. When a mobster destroys the car, they are determined to get it back.\n", + " - **Language:** Italian\n", + "\n", + "4. **New Initial D the Movie - Legend 1: Awakening**\n", + " - **Rating:** 8.3\n", + " - **Genres:** Animation, Action\n", + " - **Description:** Focusing on the battle against the Takahashi brothers, this movie follows Takumi Fujiwara as he rises as the best amateur road racer on Mt. Akina's highway.\n", + " - **Language:** Japanese\n", + "\n", + "5. **Italian Spiderman**\n", + " - **Rating:** 7.1\n", + " - **Genres:** Comedy, Action\n", + " - **Description:** A parody of Italian action–adventure films with an Italian take on the comic book superhero Spider-Man.\n", + " - **Language:** Italian\n", + "\n", + "Let me know if you need more information or recommendations!\n" + ] + } + ], + "source": [ + "print(messages[\"last_message\"].text)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FR57nEn7zyQ6" + }, + "source": [ + "### 🔉 Enable Streaming\n", + "\n", + "Enable streaming output by passing `print_streaming_chunk` as the `streaming_callback`. This shows real-time updates inlcuding all tool invocations as the agent processes the query." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "m6kGJbttzsfd", + "outputId": "9c5b86e6-f49a-463c-d064-52e2099c8fb2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[TOOL CALL]\n", + "Tool: retrieval_tool \n", + "Arguments: {\"query\":\"highly-rated action movie about car racing\",\"metadata_filters\":{\"operator\":\"AND\",\"conditions\":[{\"field\":\"meta.genre\",\"operator\":\"==\",\"value\":\"action\"},{\"field\":\"meta.rating\",\"operator\":\">=\",\"value\":7}]}}\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Calculating sparse embeddings: 100%|██████████| 1/1 [00:00<00:00, 190.17it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[TOOL RESULT]\n", + "Movie details for New Initial D the Movie - Legend 2: Racer:\n", + "The planned film trilogy retells the beginning of the story from Shuuichi Shigeno's original car-racing manga. High school student Takumi Fujiwara works as a gas station attendant during the day and a delivery boy for his father's tofu shop during late nights. Little does he know that his precise driving skills and his father's modified Toyota Sprinter AE86 Trueno make him the best amateur road racer on Mt. Akina's highway. Because of this, racing groups from all over the Gunma prefecture issue challenges to Takumi to see if he really has what it takes to be a road legend.\n", + "Rating:8.1\n", + "Genres:['animation', 'drama', 'action']\n", + "Language:ja\n", + "---\n", + "Movie details for New Initial D the Movie - Legend 1: Awakening:\n", + "The first movie in a trilogy, focusing on the battle against the Takahashi brothers. High school student Takumi Fujiwara works as a gas station attendant during the day and a delivery boy for his father's tofu shop during late nights. Little does he know that his precise driving skills and his father's modified Toyota Sprinter AE86 Trueno make him the best amateur road racer on Mt. Akina's highway. Because of this, racing groups from all over the Gunma prefecture issue challenges to Takumi to see if he really has what it takes to be a road legend.\n", + "Rating:8.3\n", + "Genres:['animation', 'action']\n", + "Language:ja\n", + "---\n", + "Movie details for Ford v Ferrari:\n", + "American car designer Carroll Shelby and the British-born driver Ken Miles work together to battle corporate interference, the laws of physics, and their own personal demons to build a revolutionary race car for Ford Motor Company and take on the dominating race cars of Enzo Ferrari at the 24 Hours of Le Mans in France in 1966.\n", + "Rating:8.0\n", + "Genres:['drama', 'action', 'history']\n", + "Language:en\n", + "---\n", + "Movie details for Watch Out, We're Mad:\n", + "After a tied 1st place in a local stunt race, two drivers start a contest to decide who of them will own the prize, a dune buggy. But when a mobster destroys the car, they are determined to get it back.\n", + "Rating:7.5\n", + "Genres:['action', 'comedy']\n", + "Language:it\n", + "---\n", + "Movie details for Italian Spiderman:\n", + "This is an Australian-made parody of Italian action–adventure films of the 60s and 70s. and foreign movies that misappropriated popular American superheroes such as the Japanese TV series “Spider-Man”. (It should be noted that the Japanese Spider-Man was officially sanctioned by Marvel and is considered canon in the Marvel universe) This is an Italian take on the comic book superhero Spider-Man.\n", + "Rating:7.1\n", + "Genres:['comedy', 'action']\n", + "Language:it\n", + "---\n", + "\n", + "\n", + "[ASSISTANT]\n", + "Here are some highly-rated action movies related to car racing:\n", + "\n", + "1. **New Initial D the Movie - Legend 1: Awakening**\n", + " - **Rating:** 8.3\n", + " - **Genres:** Animation, Action\n", + " - **Language:** Japanese\n", + " - **Description:** The first movie in a trilogy, focusing on the battle against the Takahashi brothers. High school student Takumi Fujiwara discovers his precise driving skills make him the best amateur road racer on Mt. Akina's highway.\n", + "\n", + "2. **New Initial D the Movie - Legend 2: Racer**\n", + " - **Rating:** 8.1\n", + " - **Genres:** Animation, Drama, Action\n", + " - **Language:** Japanese\n", + " - **Description:** Continuing from the first film, Takumi Fujiwara's journey in car racing challenges from groups all over the Gunma prefecture unfolds.\n", + "\n", + "3. **Ford v Ferrari**\n", + " - **Rating:** 8.0\n", + " - **Genres:** Drama, Action, History\n", + " - **Language:** English\n", + " - **Description:** Carroll Shelby and driver Ken Miles work to build a revolutionary race car for Ford to compete against Ferrari's dominating race cars at the 1966 24 Hours of Le Mans.\n", + "\n", + "4. **Watch Out, We're Mad**\n", + " - **Rating:** 7.5\n", + " - **Genres:** Action, Comedy\n", + " - **Language:** Italian\n", + " - **Description:** After a tie in a local stunt race, two drivers contest for ownership of a dune buggy, but when a mobster destroys the car, they are determined to retrieve it.\n", + "\n", + "5. **Italian Spiderman**\n", + " - **Rating:** 7.1\n", + " - **Genres:** Comedy, Action\n", + " - **Language:** Italian\n", + " - **Description:** A parody of Italian action/adventure films, this takes a humorous spin on superheroes and foreign adaptations.\n", + "\n", + "Let me know if you need more information or further assistance!\n", + "\n" + ] + } + ], + "source": [ + "from haystack.components.generators.utils import print_streaming_chunk\n", + "\n", + "query = \"Find me a highly-rated action movie about car racing.\"\n", + "# query = \"What can I watch with my kids, about animals?\"\n", + "# query = \"Can you recommend five japanese thrillers?\"\n", + "# query = \"I'd like to watch a german action movie but I don't like thriller and horror\"\n", + "# query = \"I love Titanic. Which movie should I watch next? It should be a highly rated movie though\"\n", + "\n", + "messages = movie_agent.run([ChatMessage.from_user(query)], streaming_callback=print_streaming_chunk)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "L4", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}