{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Feedback Analysis Agent with Azure AI Search and Haystack\n",
    "*by Amna Mubashar (Haystack), and Khye Wei (Azure AI Search)*\n",
    "\n",
    "This notebook demonstrates how you can build indexing and querying pipelines using Azure AI Search-Haystack integration. Additionally, you'll develop an interactive feedback review agent leveraging Haystack Tools."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install the required dependencies"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install the required dependencies\n",
    "\n",
    "%pip install \"haystack-ai>=2.13.0\"\n",
    "%pip install \"azure-ai-search-haystack\n",
    "!pip install jq\n",
    "!pip install nltk==\"3.9.1\"\n",
    "!pip install jsonschema\n",
    "!pip install kagglehub"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loading and Preparing the Dataset\n",
    "We will use an open dataset consisting of approx. 28000 customer reviews for a clothing store. The dataset is available at [Shopper Sentiments](https://www.kaggle.com/datasets/nelgiriyewithana/shoppersentiments).\n",
    "\n",
    "We will load the dataset and convert it into a JSON format that can be used by Haystack.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import kagglehub\n",
    "path = kagglehub.dataset_download(\"nelgiriyewithana/shoppersentiments\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass, os\n",
    "\n",
    "os.environ[\"AZURE_AI_SEARCH_API_KEY\"] = getpass.getpass(\"Your AZURE_AI_SEARCH_API_KEY: \")\n",
    "os.environ[\"AZURE_AI_SEARCH_ENDPOINT\"] = getpass.getpass(\"Your AZURE_AI_SEARCH_ENDPOINT: \")\n",
    "os.environ[\"AZURE_OPENAI_ENDPOINT\"] = getpass.getpass(\"Your AZURE_OPENAI_ENDPOINT: \")\n",
    "os.environ[\"AZURE_OPENAI_API_KEY\"] = getpass.getpass(\"Your AZURE_OPENAI_API_KEY: \")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from json import loads, dumps\n",
    "\n",
    "path = \"<Path to the CSV file>\"\n",
    "\n",
    "df = pd.read_csv(path, encoding='latin1', nrows=200) # We are using 200 rows for testing purposes\n",
    "\n",
    "df.rename(columns={'review-label': 'rating'}, inplace=True)\n",
    "df['year'] = pd.to_datetime(df['year'], format='%Y %H:%M:%S').dt.year\n",
    "\n",
    "# Convert DataFrame to JSON\n",
    "json_data = {\"reviews\": loads(df.to_json(orient=\"records\"))}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once we have the JSON data, we can convert it into a Haystack Document format using the `JSONConverter` component. Its important to remove any documents with no content as they will not be indexed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.components.converters import JSONConverter\n",
    "from haystack.dataclasses import ByteStream\n",
    "converter = JSONConverter(\n",
    "  jq_schema=\".reviews[]\", content_key=\"review\", extra_meta_fields={\"store_location\", \"date\", \"month\", \"year\", \"rating\"}\n",
    ")\n",
    "source = ByteStream.from_string(dumps(json_data))\n",
    "\n",
    "documents = converter.run(sources=[source])['documents']\n",
    "documents = [doc for doc in documents if doc.content is not None] # remove documents with no content\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remove any non-ASCII characters and any regex patterns that are not alphanumeric using the `DocumentCleaner` component."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.components.preprocessors import DocumentCleaner\n",
    "cleaner = DocumentCleaner(ascii_only=True, remove_regex=\"i12i12i12\")\n",
    "cleaned_documents=cleaner.run(documents=documents)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting up Azure AI Search and Indexing Pipeline\n",
    "\n",
    "We set up an indexing pipeline with `AzureAISearchDocumentStore` by following these steps:\n",
    "1. Configure semantic search for the index\n",
    "2. Initialize the document store with custom metadata fields and semantic search configuration\n",
    "3. Create an indexing pipeline that:\n",
    "   - Generates embeddings for the documents using `AzureOpenAIDocumentEmbedder`\n",
    "   - Writes the documents and their embeddings to the search index\n",
    "\n",
    "The semantic configuration allows for more intelligent searching beyond simple keyword matching. Note, the metadata fields need to be declared while creating the index as the API does not allow modifying them after index creation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Embedding Texts: 100%|██████████| 6/6 [00:29<00:00,  4.91s/it]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'document_embedder': {'meta': {'model': 'text-embedding-ada-002',\n",
       "   'usage': {'prompt_tokens': 4283, 'total_tokens': 4283}}},\n",
       " 'doc_writer': {'documents_written': 175}}"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from haystack import Pipeline\n",
    "from haystack.components.embedders import AzureOpenAIDocumentEmbedder\n",
    "from haystack.components.writers import DocumentWriter\n",
    "from azure.search.documents.indexes.models import (\n",
    "    SemanticConfiguration,\n",
    "    SemanticField,\n",
    "    SemanticPrioritizedFields,\n",
    "    SemanticSearch\n",
    ")\n",
    "\n",
    "from haystack_integrations.document_stores.azure_ai_search import AzureAISearchDocumentStore\n",
    "\n",
    "\n",
    "semantic_config = SemanticConfiguration(\n",
    "    name=\"my-semantic-config\",\n",
    "    prioritized_fields=SemanticPrioritizedFields(\n",
    "        content_fields=[SemanticField(field_name=\"content\")]\n",
    "    )\n",
    ")\n",
    "\n",
    "# Create the semantic settings with the configuration\n",
    "semantic_search = SemanticSearch(configurations=[semantic_config])\n",
    "\n",
    "document_store = AzureAISearchDocumentStore(index_name=\"customer-reviews-analysis\",\n",
    "    embedding_dimension=1536, metadata_fields = {\"month\": int, \"year\": int, \"rating\": int, \"store_location\": str}, semantic_search=semantic_search)\n",
    "\n",
    "# Indexing Pipeline\n",
    "indexing_pipeline = Pipeline()\n",
    "indexing_pipeline.add_component(instance=AzureOpenAIDocumentEmbedder(), name=\"document_embedder\")\n",
    "indexing_pipeline.add_component(instance=DocumentWriter(document_store=document_store), name=\"doc_writer\")\n",
    "indexing_pipeline.connect(\"document_embedder\", \"doc_writer\")\n",
    "\n",
    "indexing_pipeline.run({\"document_embedder\": {\"documents\": cleaned_documents[\"documents\"]}})\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating the Query Pipeline\n",
    "\n",
    "Here we set up the query pipeline that will retrieve relevant reviews based on user queries. The pipeline consists of:\n",
    "\n",
    "1. A text embedder (`AzureOpenAITextEmbedder`) that converts user queries into embeddings.\n",
    "2. A hybrid retriever (`AzureAISearchHybridRetriever`) that uses vector and semantic search to retrieve the most relevant reviews.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<haystack.core.pipeline.pipeline.Pipeline object at 0x10fe27610>\n",
       "🚅 Components\n",
       "  - text_embedder: AzureOpenAITextEmbedder\n",
       "  - retriever: AzureAISearchHybridRetriever\n",
       "🛤️ Connections\n",
       "  - text_embedder.embedding -> retriever.query_embedding (List[float])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from haystack_integrations.components.retrievers.azure_ai_search import AzureAISearchHybridRetriever\n",
    "from haystack.components.embedders import AzureOpenAITextEmbedder\n",
    "\n",
    "\n",
    "# Query Pipeline\n",
    "query_pipeline = Pipeline()\n",
    "query_pipeline.add_component(\"text_embedder\", AzureOpenAITextEmbedder())\n",
    "query_pipeline.add_component(\"retriever\", AzureAISearchHybridRetriever(document_store=document_store, query_type=\"semantic\", semantic_configuration_name=\"my-semantic-config\", top_k=10))\n",
    "query_pipeline.connect(\"text_embedder.embedding\", \"retriever.query_embedding\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<iterator object azure.core.paging.ItemPaged at 0x17f829880>\n",
      "[Document(id=e9f8a141855701896441cbf9fd29ad326ec5250e9263f4ea1f74a5b389d1c90c, content: 'You did everything right! Shipping was quick and reasonable, and the shirts are awesome! Colorful an...', meta: {'store_location': 'US', 'year': 2018, 'rating': 5, 'month': 6}, embedding: vector of size 1536), Document(id=a841f950a433d05857933fb7cd46d54a6a04066f1373875359c90f096ce0bf9a, content: 'I love the shirts that I bought.Prices were great and shipping didnt take any lon', meta: {'store_location': 'US', 'year': 2024, 'rating': 5, 'month': 6}, embedding: vector of size 1536), Document(id=0ee4d9627e9085936973126762a0aa746b5e235cf253a9f759af84fbfdd9cdae, content: 'Product was great. Love the options. anything I could imagine was available. my only issue was I pai...', meta: {'store_location': 'US', 'year': 2023, 'rating': 4, 'month': 6}, embedding: vector of size 1536), Document(id=31bb2754ad0ebf5084c90260dd45f7c9ea8f5bf5759d3caaf7e97420f8c9610b, content: 'Great shipping time. The shirts look amazing.', meta: {'store_location': 'US', 'year': 2024, 'rating': 5, 'month': 6}, embedding: vector of size 1536), Document(id=eb580826a63a596f312f5e2757b25f53d4622fc700cb158b8f6a6b307644d61d, content: 'I love the design, quality of the shirt was great. the print was high quality, shipping was on time ...', meta: {'store_location': 'US', 'year': 2019, 'rating': 4, 'month': 6}, embedding: vector of size 1536), Document(id=351df76a4a52548725cb61803ccb0602379e465a2c0a79e05061f7d7c729054b, content: 'Great designs and quality. Items shipped quickly and correctly.', meta: {'store_location': 'US', 'year': 2024, 'rating': 5, 'month': 6}, embedding: vector of size 1536), Document(id=b18855210319bc9b6bf3066a644114425900e95979ad77c1c1dd85e20cbac8b2, content: 'Awesome shirts ,great quality material, fantastic designs,good shipping speed and carefully packed a...', meta: {'store_location': 'US', 'year': 2024, 'rating': 5, 'month': 6}, embedding: vector of size 1536), Document(id=6d2ad6c2991516f5c1f7793414996e763a2702ffeceb01b18a61c3225a61bc46, content: 'Once I figured out the sizing, everything was great. FYI, I am a size 10 in womens tops but prefer a...', meta: {'store_location': 'US', 'year': 2018, 'rating': 5, 'month': 6}, embedding: vector of size 1536), Document(id=8798ccd5cde690479c764e8118f1d5423352d13b9d2b8849bc8aed801ae9a9be, content: 'A bit pricey for a tee shirt. Childs size cost $18.00 and outrageous shipping $9.99. No way this cos...', meta: {'store_location': 'US', 'year': 2024, 'rating': 3, 'month': 6}, embedding: vector of size 1536), Document(id=d58901f69050e781ca6f5c78bff61474294e9f0a4b65549bab7b9764f8eed81e, content: 'Delivered ON TIME and shirt is EXTREMELY COMFORTABLE!! You guys are THE BEST!', meta: {'store_location': 'US', 'year': 2018, 'rating': 5, 'month': 6}, embedding: vector of size 1536)]\n"
     ]
    }
   ],
   "source": [
    "query = \"Which reviews are about shipping?\"\n",
    "\n",
    "# Retrieve reviews based on the query\n",
    "result = query_pipeline.run({\"text_embedder\": {\"text\": query}, \"retriever\": {\"query\": query}})\n",
    "retrieved_reviews = result[\"retriever\"][\"documents\"]\n",
    "print(retrieved_reviews)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create Tools for Sentiment Analysis and Summarization\n",
    "Install the required dependencies.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install vaderSentiment\n",
    "!pip install matplotlib\n",
    "!pip install sumy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Create a function that will be used by `review_analysis` tool to visualize the sentiment distribution across customer review aspects (e.g., product quality, shipping). It compares VADER-based sentiment scores with customer ratings using color-coded bars (positive, neutral, negative). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to visualize the sentiment distribution\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "def plot_sentiment_distribution(aspects):\n",
    "    # Create DataFrame from aspects data\n",
    "    data = [(topic, review['sentiment']['analyzer_rating'], \n",
    "             review['review']['rating'], review['sentiment']['label'])\n",
    "            for topic, reviews in aspects.items()\n",
    "            for review in reviews]\n",
    "    \n",
    "    df = pd.DataFrame(data, columns=['Topic', 'Normalized Score', 'Original Rating', 'Sentiment'])\n",
    "    \n",
    "    # Calculate means\n",
    "    df_means = df.groupby('Topic').agg({\n",
    "        'Normalized Score': 'mean',\n",
    "        'Original Rating': 'mean'\n",
    "    }).reset_index()\n",
    "    \n",
    "    fig, ax = plt.subplots(figsize=(8, 4))  \n",
    "    x = np.arange(len(df_means))\n",
    "    bar_width = 0.3  \n",
    "    \n",
    "    # Colors for sentiment\n",
    "    colors = {\n",
    "        'positive': '#2ecc71',\n",
    "        'neutral': '#f1c40f',\n",
    "        'negative': '#e74c3c'\n",
    "    }\n",
    "    \n",
    "    # Create bars\n",
    "    sentiment_colors = [colors[df.groupby('Topic')['Sentiment'].agg(lambda x: x.mode()[0])[topic]] \n",
    "                       for topic in df_means['Topic']]\n",
    "    \n",
    "    bars1 = ax.bar(x - bar_width/2, df_means['Normalized Score'], \n",
    "                   bar_width, label='Normalized Score', color=sentiment_colors)\n",
    "    bars2 = ax.bar(x + bar_width/2, df_means['Original Rating'], \n",
    "                   bar_width, label='Original Rating', color='gray', alpha=0.7)\n",
    "    \n",
    "    # Customize plot with smaller font sizes\n",
    "    ax.set_ylabel('Score', fontsize=9)\n",
    "    ax.set_title('Average Sentiment Scores by Topic', fontsize=10)\n",
    "    ax.set_xticks(x)\n",
    "    ax.set_xticklabels(df_means['Topic'], rotation=45, ha='right', fontsize=8)\n",
    "    ax.tick_params(axis='y', labelsize=8)\n",
    "    \n",
    "    # Add value labels with smaller font size\n",
    "    for bars in [bars1, bars2]:\n",
    "        ax.bar_label(bars, fmt='%.2f', padding=3, fontsize=8)\n",
    "    \n",
    "    # Smaller legend\n",
    "    ax.legend(handles=[plt.Rectangle((0,0),1,1, color=c) for c in colors.values()] + \n",
    "             [plt.Rectangle((0,0),1,1, color='gray', alpha=0.7)],\n",
    "             labels=list(colors.keys()) + ['Original Rating'],\n",
    "             loc='upper right',\n",
    "             fontsize=8)\n",
    "    \n",
    "    plt.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Create a tool to perform aspect-based sentiment analysis on customer reviews using the VADER sentiment analyzer. It involves:\n",
    "\n",
    "- Identifying specific aspects within reviews (e.g., product quality, shipping, customer service, pricing) using predefined keywords\n",
    "- Calculating sentiment scores for each review mentioning these aspects\n",
    "- Categorizing sentiment as 'positive', 'negative', or 'neutral' \n",
    "- Normalizing sentiment scores to a scale of 1 to 5 for comparison with customer ratings\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.tools import Tool\n",
    "from haystack.components.tools import ToolInvoker\n",
    "\n",
    "from typing import Dict, List\n",
    "from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer\n",
    "\n",
    "\n",
    "def analyze_sentiment(reviews: List[Dict]) -> Dict:\n",
    "    \"\"\"\n",
    "    Perform aspect-based sentiment analysis.\n",
    "    \n",
    "    For each review that mentions keywords related to a specific topic, the function computes \n",
    "    sentiment scores using VADER and categorizes the sentiment as 'positive', 'negative', or 'neutral'.\n",
    "    \n",
    "    \"\"\"\n",
    "    aspects = {\n",
    "        \"product_quality\": [],\n",
    "        \"shipping\": [],\n",
    "        \"customer_service\": [],\n",
    "        \"pricing\": []\n",
    "    }\n",
    "    \n",
    "    # Define keywords for each topic\n",
    "    keywords = {\n",
    "        \"product_quality\": [\"quality\", \"material\", \"design\", \"fit\", \"size\", \"color\", \"style\"],\n",
    "        \"shipping\": [\"shipping\", \"delivery\", \"arrived\"],\n",
    "        \"customer_service\": [\"service\", \"support\", \"help\"],\n",
    "        \"pricing\": [\"price\", \"cost\", \"expensive\", \"cheap\"]\n",
    "    }\n",
    "\n",
    "    \n",
    "    # Initialize the VADER sentiment analyzer\n",
    "    analyzer = SentimentIntensityAnalyzer()\n",
    "    \n",
    "    for review in reviews:\n",
    "        text = review.get(\"review\", \"\").lower()\n",
    "        for topic, words in keywords.items():\n",
    "            if any(word in text for word in words):\n",
    "                # Compute sentiment scores using VADER\n",
    "                sentiment_scores = analyzer.polarity_scores(text)\n",
    "               \n",
    "                compound = sentiment_scores['compound']\n",
    "                # Normalize compound score from [-1, 1] to [1, 5]\n",
    "                normalized_score = (compound + 1) * 2 + 1\n",
    "                \n",
    "                if compound >= 0.03:\n",
    "                    sentiment_label = 'positive'\n",
    "                elif compound <= -0.03:\n",
    "                    sentiment_label = 'negative'\n",
    "                else:\n",
    "                    sentiment_label = 'neutral'\n",
    "                \n",
    "                # Append the review along with its sentiment analysis result\n",
    "                aspects[topic].append({\n",
    "                    \"review\": review,\n",
    "                    \"sentiment\": {\n",
    "                        \"analyzer_rating\": normalized_score,\n",
    "                        \"label\": sentiment_label\n",
    "                    }\n",
    "                })\n",
    "    plot_sentiment_distribution(aspects)\n",
    "\n",
    "    return {\n",
    "        \"total_reviews\": len(reviews),\n",
    "        \"sentiment_analysis\": aspects,\n",
    "        \"average_rating\": sum(r.get(\"rating\", 3) for r in reviews) / len(reviews)\n",
    "    }\n",
    "\n",
    "# Use the `analyze_sentiment` function to create a tool for sentiment analysis\n",
    "sentiment_tool = Tool(\n",
    "    name=\"review_analysis\",\n",
    "    description=\"Aspect based sentiment analysis tool that compares the sentiment of reviews by analyzer and rating\",\n",
    "    function=analyze_sentiment,\n",
    "    parameters={\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "            \"reviews\": {\n",
    "                \"type\": \"array\",\n",
    "                \"items\": {\n",
    "                    \"type\": \"object\",\n",
    "                    \"properties\": {\n",
    "                        \"review\": {\"type\": \"string\"},\n",
    "                        \"rating\": {\"type\": \"integer\"},\n",
    "                        \"date\": {\"type\": \"string\"}\n",
    "                    }\n",
    "                }\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"reviews\"]\n",
    "    }\n",
    ")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Create a tool for summarizing customer reviews. The process involves:\n",
    "\n",
    "- Using the LSA (Latent Semantic Analysis) summarizer to identify and extract the most important sentences from each review\n",
    "- Creating concise summaries that capture the essence of the reviews\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sumy.parsers.plaintext import PlaintextParser\n",
    "from sumy.nlp.tokenizers import Tokenizer\n",
    "from sumy.summarizers.lsa import LsaSummarizer\n",
    "\n",
    "\n",
    "def summarize_reviews(reviews: List[Dict]) -> Dict:\n",
    "    \"\"\"\n",
    "    Summarize the reviews by extracting key sentences.\n",
    "    \"\"\"\n",
    "    summaries = []\n",
    "    summarizer = LsaSummarizer()\n",
    "    for review in reviews:\n",
    "        text = review.get(\"review\", \"\")\n",
    "        parser = PlaintextParser.from_string(text, Tokenizer(\"english\"))\n",
    "        summary = summarizer(parser.document, 2)  # Adjust the number of sentences as needed\n",
    "        summary_text = \" \".join(str(sentence) for sentence in summary)\n",
    "        summaries.append({\"review\": text, \"summary\": summary_text})\n",
    "\n",
    "    return {\"summaries\": summaries}\n",
    "\n",
    "# Create the tool from the `summarize_reviews` function\n",
    "summarization_tool = Tool(\n",
    "    name=\"review_summarization\",\n",
    "    description=\"Tool to summarize customer reviews by extracting key sentences.\",\n",
    "    function=summarize_reviews,\n",
    "    parameters={\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "            \"reviews\": {\n",
    "                \"type\": \"array\",\n",
    "                \"items\": {\n",
    "                    \"type\": \"object\",\n",
    "                    \"properties\": {\n",
    "                        \"review\": {\"type\": \"string\"},\n",
    "                        \"rating\": {\"type\": \"integer\"},\n",
    "                        \"date\": {\"type\": \"string\"}\n",
    "                    }\n",
    "                }\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"reviews\"]\n",
    "    }\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating an Interactive Feedback Review Agent\n",
    "\n",
    "We now have the tools to build an interactive agent for customer feedback analysis. The agent dynamically selects the appropriate tool based on user queries, gathers insights based on tool response. The agent then uses the `AzureOpenAIChatGenerator` to combine the query, retrieved reviews, and tool responses into a comprehensive review analysis.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.dataclasses import ChatMessage\n",
    "from haystack.components.generators.chat import AzureOpenAIChatGenerator\n",
    "\n",
    "def create_review_agent():\n",
    "    \"\"\"Creates an interactive review analysis agent\"\"\"\n",
    "    \n",
    "    chat_generator = AzureOpenAIChatGenerator(\n",
    "        tools=[sentiment_tool, summarization_tool]\n",
    "    )\n",
    "    \n",
    "    system_message = ChatMessage.from_system(\n",
    "        \"\"\"\n",
    "        You are a customer review analysis expert. Your task is to perform aspect based sentiment analysis on customer reviews.\n",
    "        You can use two tools to get insights:\n",
    "        - review_analysis: to get the sentiment of reviews by analyzer and rating\n",
    "        - review_summarization: to get the summary of reviews.\n",
    "\n",
    "        Depending on the user's question, use the appropriate tool to get insights and explain them in a helpful way. \n",
    "        \n",
    "        \"\"\"\n",
    "    )\n",
    "    \n",
    "    return chat_generator, system_message\n",
    "\n",
    "tool_invoker = ToolInvoker(tools=[sentiment_tool, summarization_tool])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's put our agent to the test with a sample query and see it in action! 🚀\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "🧑: Whats the overall sentiment distribution?\n",
      "⌛ iterating...\n",
      "\n",
      " TOOL CALL:\n",
      "\treview_analysis\n"
     ]
    },
    {
     "data": {
      "image/png": "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0U8//SR/f38VKlTots85uwYNGqTPPvtMPXr0sNz16fjx41q2bJnmz59vOUi+n3LlymnDhg1q0qSJnJ2dM3QzuyUzn9eoUaNUs2ZNvfLKKxowYICcnJy0ceNGde3aVT4+Pho4cKDeeustFS1aVGXKlNEHH3yg69ev6+WXX75rbZn5bo0bN06PPvqoqlevruTkZIWHh6tq1ar3fM+nT5/WG2+8of/85z/au3evZs6cqY8++ijDOv369bO0s23btkx9lv/Ws2dPjR8/Xn369NGECRN06dIlvfrqq3rhhRcsXdskKSUlRS+//LLGjh2r6OhojR8/XoMHD5ad3e29psuVK6c+ffropZde0ieffKJatWrp1KlTunjxop577rls1Qkg/2OMBQCb+/zzz9WyZcs7Hux27txZu3fv1u+//y7p5kHSwYMHVapUqQx95SVp0qRJevvttzVlyhRVrVpVbdq00Y8//mi53erd/Pe//5WXl5caN26sDh06qHXr1hnGU0hSaGioSpQooaCgIHXq1En9+/eXu7u7ChUqJOlm156IiAgFBQXpxRdfVJUqVdS9e3edOnUqw8HbPz322GNKTEzUgAEDVL16dTVr1kw7duxQWFiY5cx4YGCgNm3apKNHj+rxxx9XnTp1NG7cuAxdxu7HwcFBn3zyiebOnSs/Pz8988wzmd72fvz8/LRt2zalp6frySefVM2aNfXaa6+pSJEidzwgvZuPPvpI69atU+nSpVWnTp07rpOZz6tKlSpau3at9u3bp8cee0yNGjXS999/b7ma8v7776tz58564YUXVLduXR0/flxr1qy5Y5D5p/t9t5ycnDRq1CgFBgYqKChI9vb2WrZs2T3b7N27t27cuKHHHntMgwYN0tChQxUSEpJhncqVK6tx48YKCAhQgwYNMvVZ/purq6vWrFmjK1euqH79+urSpYueeOKJ24LpE088ocqVKysoKEjdunXT008/neG2wP82e/ZsdenSRa+88ooCAgLUv3//DLdBBvDwMRnG/79pNgAg02JiYlS6dGmtX7/+roOFAWsZhqHKlSvrlVde0RtvvPHA9tO3b1/FxsZmavZzALgbukIBQCb8/PPPSkxMVM2aNXXu3DkNHz5c5cqVU1BQkK1LQwF16dIlLVu2TOfPn9eLL75o63IA4L4IFgCQCampqRo9erT+/PNPubu7q3Hj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",
      "text/plain": [
       "<Figure size 800x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "⌛ iterating...\n",
      "🤖: The overall sentiment analysis of the customer reviews reveals a predominantly positive sentiment, as evidenced by the following key insights:\n",
      "\n",
      "### Sentiment Distribution:\n",
      "1. **Total Reviews Analyzed**: 10\n",
      "2. **Average Rating**: 4.6 out of 5\n",
      "\n",
      "### Breakdown by Aspects:\n",
      "- **Product Quality**: All reviews regarding product quality are rated positively, with an average sentiment analyzer rating of approximately 4.37. Customers expressed satisfaction with shirt designs, quality, and options.\n",
      "  \n",
      "- **Shipping**: Shipping also received positive feedback, with reviews indicating quick and efficient shipping times. The sentiment analyzer rating for shipping-related comments is around 4.65, with a majority of users finding the shipping service excellent.\n",
      "\n",
      "- **Pricing**: Pricing feedback is mixed, with some customers finding the prices reasonable, while others viewed them as somewhat high. The sentiment analyzer rating for pricing stands at approximately 1.73 for the negative feedback (related to concern over the price of children's shirts and shipping costs), indicating that while some customers are satisfied, there is room for improvement.\n",
      "\n",
      "### Positive Feedback Highlights:\n",
      "- Many reviews celebrated the quality of the shirts, the variety of options available, and the speed of shipping, with expressions like “awesome,” “fantastic designs,” and “great quality material.”\n",
      "\n",
      "### Negative Feedback Highlights:\n",
      "- The only notable negative sentiment arises around pricing complaints, particularly regarding perceived high costs for t-shirts and shipping fees.\n",
      "\n",
      "In summary, the sentiment distribution indicates a strong overall positive reception among customers, especially regarding product quality and shipping, with a slight negative sentiment regarding pricing.\n",
      "\n",
      "🧑: Give the summary of reviews as a paragraph.\n",
      "⌛ iterating...\n",
      "\n",
      " TOOL CALL:\n",
      "\treview_summarization\n",
      "⌛ iterating...\n",
      "🤖: The customer reviews highlight an overwhelmingly positive experience with the shirts purchased, praising aspects such as quick and reasonable shipping, amazing designs, and high product quality. Customers express their love for the shirts, noting great material and design options, while emphasizing that the delivery is timely. Most reviews reflect satisfaction, with one shopper mentioning that everything was great once they figured out the sizing. However, there are some concerns regarding pricing, as one reviewer found the cost of a child's shirt and shipping to be excessive. Overall, the feedback reflects excellent service and product quality, marking a strong recommendation for others.\n"
     ]
    }
   ],
   "source": [
    "# Create the review assistant\n",
    "chat_generator, system_message = create_review_agent()\n",
    "\n",
    "# Initialize messages with the system message\n",
    "messages = [system_message]\n",
    "\n",
    "# Interactive loop for user input\n",
    "while True:\n",
    "    user_input = input(\"\\n\\nwaiting for input (type 'exit' or 'quit' to stop)\\n: \")\n",
    "    if user_input.lower() == \"exit\" or user_input.lower() == \"quit\":\n",
    "        break\n",
    "    messages.append(ChatMessage.from_user(user_input))\n",
    "\n",
    "    print (f\"\\n🧑: {user_input}\")\n",
    "    # Build the prompt with user input and reviews\n",
    "    user_prompt = ChatMessage.from_user(f\"\"\"\n",
    "    {user_input}\n",
    "    Here are the reviews with analysis:\n",
    "    {retrieved_reviews}\n",
    "    \"\"\")\n",
    "    messages.append(user_prompt)\n",
    "\n",
    "    while True:\n",
    "        print(\"⌛ iterating...\")\n",
    "\n",
    "        replies = chat_generator.run(messages=messages)[\"replies\"]\n",
    "        messages.extend(replies)\n",
    "\n",
    "        # Check for tool calls and handle them\n",
    "        if not replies[0].tool_calls:\n",
    "            break\n",
    "        tool_calls = replies[0].tool_calls\n",
    "\n",
    "        # Print tool calls for debugging\n",
    "        for tc in tool_calls:\n",
    "            print(\"\\n TOOL CALL:\")\n",
    "            print(f\"\\t{tc.tool_name}\")\n",
    "\n",
    "        tool_messages = tool_invoker.run(messages=replies)[\"tool_messages\"]\n",
    "        messages.extend(tool_messages)\n",
    "\n",
    "    # Print the final AI response after all tool calls are resolved\n",
    "    print(f\"🤖: {messages[-1].text}\")\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
