{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OsXAs2gcIpbC"
      },
      "outputs": [],
      "source": [
        "# Copyright 2024 Google LLC\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7ZX50cNFOFBt"
      },
      "source": [
        " # Evaluate LangChain"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "98a27a25a5f9"
      },
      "source": [
        " <table align=\"left\">\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://www.gstatic.com/pantheon/images/bigquery/welcome_page/colab-logo.svg\" alt=\"Google Colaboratory logo\"><br> Open in Colab\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://console.cloud.google.com/vertex-ai/colab/import/https:%2F%2Fraw.githubusercontent.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fmain%2Fgemini%2Fevaluation%2Fevaltask_approach%2Fevaluate_langchain_chains.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://lh3.googleusercontent.com/JmcxdQi-qOpctIvWKgPtrzZdJJK-J3sWE1RsfjZNwshCFgE_9fULcNpuXYTilIR2hjwN\" alt=\"Google Cloud Colab Enterprise logo\"><br> Open in Colab Enterprise\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/generative-ai/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\">\n",
        "      <img src=\"https://www.gstatic.com/images/branding/gcpiconscolors/vertexai/v1/32px.svg\" alt=\"Vertex AI logo\"><br> Open in Vertex AI Workbench\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://raw.githubusercontent.com/primer/octicons/refs/heads/main/icons/mark-github-24.svg\" alt=\"GitHub logo\"><br> View on GitHub\n",
        "    </a>\n",
        "  </td>\n",
        "</table>\n",
        "\n",
        "<div style=\"clear: both;\"></div>\n",
        "\n",
        "<b>Share to:</b>\n",
        "\n",
        "<a href=\"https://www.linkedin.com/sharing/share-offsite/?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/8/81/LinkedIn_icon.svg\" alt=\"LinkedIn logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://bsky.app/intent/compose?text=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/7/7a/Bluesky_Logo.svg\" alt=\"Bluesky logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://twitter.com/intent/tweet?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/5a/X_icon_2.svg\" alt=\"X logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://reddit.com/submit?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://redditinc.com/hubfs/Reddit%20Inc/Brand/Reddit_Logo.png\" alt=\"Reddit logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://www.facebook.com/sharer/sharer.php?u=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/evaluate_langchain_chains.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg\" alt=\"Facebook logo\">\n",
        "</a>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "usd0d_LiOFBt"
      },
      "source": [
        "| | |\n",
        "|-|-|\n",
        "|Author(s) | [Elia Secchi](https://github.com/eliasecchig) |"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MjDmmmDaOFBt"
      },
      "source": [
        "## Overview\n",
        "\n",
        "With this tutorial, you learn how to evaluate the performance of a conversational LangChain chain using the *Vertex AI Python SDK for Gen AI Evaluation Service*. The notebook utilizes a dummy chatbot designed to provide recipe suggestions.\n",
        "\n",
        "The tutorial goes trough:\n",
        "1. Data preparation\n",
        "2. Setting up the LangChain chain\n",
        "3. Set-up a custom metric\n",
        "4. Run evaluation with a combination of custom metrics and built-in metrics.\n",
        "5. Log results into an experiment run and analyze different runs.\n",
        "\n",
        "### Costs\n",
        "\n",
        "This tutorial uses billable components of Google Cloud:\n",
        "\n",
        "- Vertex AI\n",
        "\n",
        "Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "w-OcPSC8_FUX"
      },
      "source": [
        "## Getting Started"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-7Jso8-FO4N8"
      },
      "source": [
        "### Install Vertex AI SDK for Rapid Evaluation"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tUat7NRq5JDC"
      },
      "outputs": [],
      "source": [
        "%pip install --upgrade --user --quiet langchain-core langchain-google-vertexai langchain\n",
        "%pip install --upgrade --user --quiet \"google-cloud-aiplatform[evaluation]\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R5Xep4W9lq-Z"
      },
      "source": [
        "### Restart runtime\n",
        "\n",
        "To use the newly installed packages in this Jupyter runtime, you must restart the runtime. You can do this by running the cell below, which restarts the current kernel.\n",
        "\n",
        "The restart might take a minute or longer. After it's restarted, continue to the next step."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XRvKdaPDTznN"
      },
      "outputs": [],
      "source": [
        "import IPython\n",
        "\n",
        "app = IPython.Application.instance()\n",
        "app.kernel.do_shutdown(True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SbmM4z7FOBpM"
      },
      "source": [
        "<div class=\"alert alert-block alert-warning\">\n",
        "<b>⚠️ The kernel is going to restart. Wait until it's finished before continuing to the next step. ⚠️</b>\n",
        "</div>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dmWOrTJ3gx13"
      },
      "source": [
        "### Authenticate your notebook environment (Colab only)\n",
        "\n",
        "If you're running this notebook on Google Colab, run the cell below to authenticate your environment."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NyKGtVQjgx13"
      },
      "outputs": [],
      "source": [
        "# import sys\n",
        "\n",
        "# if \"google.colab\" in sys.modules:\n",
        "#     from google.colab import auth\n",
        "\n",
        "#     auth.authenticate_user()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DF4l8DTdWgPY"
      },
      "source": [
        "### Set Google Cloud project information and initialize Vertex AI SDK\n",
        "\n",
        "To get started using Vertex AI, you must have an existing Google Cloud project and [enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
        "\n",
        "Learn more about [setting up a project and a development environment](https://cloud.google.com/vertex-ai/docs/start/cloud-environment)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Nqwi-5ufWp_B"
      },
      "outputs": [],
      "source": [
        "PROJECT_ID = \"[your-project-id]\"  # @param {type:\"string\"}\n",
        "LOCATION = \"us-central1\"  # @param {type:\"string\"}\n",
        "\n",
        "\n",
        "import vertexai\n",
        "\n",
        "vertexai.init(project=PROJECT_ID, location=LOCATION)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dvhI92xhQTzk"
      },
      "source": [
        "### Import libraries"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "qP4ihOCkEBje"
      },
      "outputs": [],
      "source": [
        "import json\n",
        "import logging\n",
        "from concurrent.futures import ThreadPoolExecutor\n",
        "from functools import partial\n",
        "from typing import Any\n",
        "\n",
        "# Main\n",
        "import pandas as pd\n",
        "import vertexai\n",
        "\n",
        "# General\n",
        "import yaml\n",
        "from google.cloud import aiplatform\n",
        "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
        "from langchain_google_vertexai import ChatVertexAI\n",
        "from tqdm import tqdm\n",
        "from vertexai.evaluation import CustomMetric, EvalTask"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "l26gX-cHOFBu"
      },
      "source": [
        "### Helper functions"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gT_OJBHfCg4Q"
      },
      "outputs": [],
      "source": [
        "def generate_multiturn_history(df: pd.DataFrame):\n",
        "    \"\"\"Processes a DataFrame of messages to add conversation history for each message.\n",
        "\n",
        "    This function takes a DataFrame containing message data and iterates through each row.\n",
        "    For each message in a row, it constructs the conversation history up to that point by\n",
        "    accumulating previous user and AI messages. This conversation history is then added\n",
        "    to the message data, and the processed messages are returned as a new DataFrame.\n",
        "\n",
        "    Args:\n",
        "        df: A DataFrame containing message data. It is expected to have a column named\n",
        "            \"messages\" where each entry is a list of dictionaries representing messages in\n",
        "            a conversation. Each message dictionary should have \"user\" and \"reference\" keys.\n",
        "\n",
        "    Returns:\n",
        "        A DataFrame with the processed messages. Each message dictionary will now have an\n",
        "        additional \"conversation_history\" key containing a list of tuples representing the\n",
        "        conversation history leading up to that message. The tuples are of the form\n",
        "        (\"user\", message_text) or (\"ai\", message_text).\n",
        "    \"\"\"\n",
        "    processed_messages = []\n",
        "    for i, row in df.iterrows():\n",
        "        conversation_history = []\n",
        "        for message in row[\"messages\"]:\n",
        "            message[\"conversation_history\"] = conversation_history\n",
        "            processed_messages.append(message)\n",
        "            conversation_history = conversation_history + [\n",
        "                (\"user\", message[\"user\"]),\n",
        "                (\"ai\", message[\"reference\"]),\n",
        "            ]\n",
        "    return pd.DataFrame(processed_messages)\n",
        "\n",
        "\n",
        "def pairwise(iterable):\n",
        "    \"\"\"Creates an iterable with tuples paired together\n",
        "    e.g s -> (s0, s1), (s2, s3), (s4, s5), ...\n",
        "    \"\"\"\n",
        "    a = iter(iterable)\n",
        "    return zip(a, a, strict=False)\n",
        "\n",
        "\n",
        "def batch_generate_message(row: dict, callable: Any) -> dict:\n",
        "    \"\"\"Predicts a response from a chat agent.\n",
        "\n",
        "    Args:\n",
        "        callable (ChatAgent): A chat agent.\n",
        "        row (dict): A message.\n",
        "\n",
        "    Returns:\n",
        "        dict: The predicted response.\n",
        "    \"\"\"\n",
        "    index, message = row\n",
        "\n",
        "    messages = []\n",
        "    for user_message, ground_truth in pairwise(message.get(\"conversation_history\", [])):\n",
        "        messages.append((\"user\", user_message))\n",
        "        messages.append((\"ai\", ground_truth))\n",
        "    messages.append((\"user\", message[\"user\"]))\n",
        "    input_callable = {\"messages\": messages, **message.get(\"callable_kwargs\", {})}\n",
        "    response = callable.invoke(input_callable)\n",
        "    message[\"response\"] = response.content\n",
        "    message[\"response_obj\"] = response\n",
        "    return message\n",
        "\n",
        "\n",
        "def batch_generate_messages(\n",
        "    messages: pd.DataFrame, callable: Any, max_workers: int = 4\n",
        ") -> pd.DataFrame:\n",
        "    \"\"\"Generates AI-powered responses to a series of user messages using a provided callable.\n",
        "\n",
        "    This function efficiently processes a Pandas DataFrame containing user-AI message pairs,\n",
        "     utilizing the specified callable (either a LangChain Chain or a custom class with an\n",
        "     `invoke` method) to predict AI responses in parallel.\n",
        "\n",
        "    Args:\n",
        "        callable (callable): A callable object (e.g., LangChain Chain, custom class) used\n",
        "            for response generation. Must have an `invoke(messages: dict) ->\n",
        "            langchain_core.messages.ai.AIMessage` method.\n",
        "            The `messages` dict follows this structure:\n",
        "            {\"messages\" [(\"user\", \"first\"),(\"ai\", \"a response\"), (\"user\", \"a follow-up\")]}\n",
        "\n",
        "        messages (pd.DataFrame): A DataFrame with one column named 'messages' containing\n",
        "            the list of user-AI message pairs as described above.\n",
        "\n",
        "        max_workers (int, optional): The number of worker processes to use for parallel\n",
        "            prediction. Defaults to the number of available CPU cores.\n",
        "\n",
        "    Returns:\n",
        "        pd.DataFrame: A DataFrame containing the original messages and a new column with the predicted AI responses.\n",
        "\n",
        "    Example:\n",
        "        ```python\n",
        "        messages_df = pd.DataFrame({\n",
        "            \"messages\": [\n",
        "                [{\"user\": \"What's the weather today?\", \"reference\": \"It's sunny.\"}],\n",
        "                [{\"user\": \"Tell me a joke.\", \"reference\": \"Why did the scarecrow win an award?...\n",
        "                    Because he was outstanding in his field!\"}]\n",
        "            ]\n",
        "        })\n",
        "\n",
        "        responses_df = batch_generate_messages(my_callable, messages_df)\n",
        "        ```\n",
        "    \"\"\"\n",
        "    logging.info(\"Executing batch scoring\")\n",
        "    predicted_messages = []\n",
        "    with ThreadPoolExecutor(max_workers) as pool:\n",
        "        partial_func = partial(batch_generate_message, callable=callable)\n",
        "        for message in tqdm(\n",
        "            pool.map(partial_func, messages.iterrows()), total=len(messages)\n",
        "        ):\n",
        "            predicted_messages.append(message)\n",
        "    return pd.DataFrame(predicted_messages)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SZMXiJhKD2TY"
      },
      "source": [
        "## Import ground truth data for evaluation\n",
        "\n",
        "In this sample, we will use 2 conversations as a ground truth. Every message in the conversations, along with the relative history, will be used to produce a response with a foundational model.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jwqlusT7OJ4J"
      },
      "outputs": [],
      "source": [
        "%%writefile chats.yaml\n",
        "- messages:\n",
        "  - user: Hi\n",
        "    reference: Hi, how can I help you?\n",
        "  - user: I'm looking for a recipe for a healthy dinner. Do you have any recommendations?\n",
        "    reference: Sure, I can help you with that. What are your dietary restrictions? Are you vegetarian, vegan, gluten-free, or anything else?\n",
        "  - user: I'm not vegetarian or vegan, but I am gluten-free.\n",
        "    reference: 'Okay, I ll keep that in mind. Here are a few recipes that I think you might like:\n",
        "  * **Grilled Salmon with Roasted Vegetables:** This is a delicious and healthy recipe that is perfect for a weeknight meal. The salmon is grilled to perfection and the roasted vegetables add a touch of sweetness.\n",
        "  * **Chicken Stir-Fry:** This is a quick and easy stir-fry that is perfect for busy weeknights. The chicken is cooked with vegetables and a light sauce.\n",
        "  * **Lentil Soup:** This is a hearty and healthy soup that is perfect for a cold winter day. The lentils are packed with protein and fiber, and the soup is also gluten-free.'\n",
        "  - user: Those all sound great! I think I'm going to try the grilled salmon with roasted vegetables.\n",
        "    reference: That's a great choice! I hope you enjoy it.\n",
        "  - user: Thanks for your help!\n",
        "    reference: You're welcome! Is there anything else I can help you with today?\n",
        "  - user: No, that's all. Thanks again!\n",
        "    reference: You're welcome! Have a great day!\n",
        "- messages:\n",
        "  - user: Hi\n",
        "    reference: Hi, how can I help you?\n",
        "  - user: I'm looking for a recipe for a romantic dinner. Do you have any recommendations?\n",
        "    reference: Sure, I can help you with that. What are your dietary restrictions? Are you vegetarian, vegan, gluten-free, or anything else?\n",
        "  - user: I'm vegetarian.\n",
        "    reference: 'Sure, I can help you find a healthy vegetarian dinner recipe. Here are a few ideas:\n",
        "    * **Burnt aubergine veggie chilli:** This is a hearty and flavorful dish that is packed with nutrients. The roasted aubergine gives it a smoky flavor, and the lentils and beans add protein and fiber.\n",
        "  * **Simple mushroom curry:** This is a quick and easy curry that is perfect for a weeknight meal. The mushrooms are cooked in a creamy sauce with spices, and the whole dish is ready in under 30 minutes.\n",
        "  * **Vegetarian enchiladas:** This is a classic Mexican dish that is easy to make vegetarian. The enchiladas are filled with a variety of vegetables, and they are topped with a delicious sauce.\n",
        "  * **Braised sesame tofu:** This is a flavorful and satisfying dish that is perfect for a cold night. The tofu is braised in a sauce with sesame, ginger, and garlic, and it is served over rice or noodles.\n",
        "  * **Roast garlic & tahini spinach:** This is a light and healthy dish that is perfect for a spring or summer meal. The spinach is roasted with garlic and tahini, and it is served with a side of pita bread.\n",
        "\n",
        "  These are just a few ideas to get you started. There are many other great vegetarian dinner recipes out there, so you are sure to find something that you will enjoy.'\n",
        "  - user: Those all sound great! I like the Burnt aubergine veggie chilli\n",
        "    reference: That's a great choice! I hope you enjoy it."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ie7KTd_OjwdE"
      },
      "source": [
        "Let's now load all the messages into a Pandas DataFrame:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "j0eSy_avGCW7"
      },
      "outputs": [],
      "source": [
        "y = yaml.safe_load(open(\"chats.yaml\"))\n",
        "df = pd.DataFrame(y)\n",
        "df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xA7G0bDTj84z"
      },
      "source": [
        "**Decomposing the chat message input/output pairs**\n",
        "\n",
        "We are now ready for decomposing multi-turn history. This is essential to enable batch prediction.\n",
        "\n",
        "We decompose the `messages` list into single input/output pairs. The input is always composed by `user message`, `reference message` and `conversation_history`.\n",
        "\n",
        "**Example:**\n",
        "\n",
        "Given the following chat:\n",
        "\n",
        "```yaml\n",
        "- messages:\n",
        "- user: Hi\n",
        "reference: Hi, how can I help you?\n",
        "- user: I'm looking for a recipe for a healthy dinner. Do you have any recommendations?\n",
        "reference: Sure, I can help you with that. What are your dietary restrictions? Are you vegetarian, vegan, gluten-free, or anything else?\n",
        "```\n",
        "\n",
        "We can generate these two input/output samples:\n",
        "\n",
        "```yaml\n",
        "- user: Hi\n",
        "  reference: Hi, how can I help you?\n",
        "  conversation_history: []\n",
        "\n",
        "- user: I'm looking for a recipe for a healthy dinner....\n",
        "  reference: Sure, I can help you with that. What are your ...\n",
        "  conversation_history:\n",
        "  - user: Hi\n",
        "    reference: Hi, how can I help you?\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1FL0R3YXPj3e"
      },
      "outputs": [],
      "source": [
        "df_processed = generate_multiturn_history(df)\n",
        "df_processed"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8OhQLR1lGsEq"
      },
      "source": [
        "## Let's define our LangChain chain!\n",
        "\n",
        "We now need to define our LangChain Chain. For this tutorial, we will create a simple conversational chain capable of producing cooking recipes for users."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "u5B0ufczjfA_"
      },
      "outputs": [],
      "source": [
        "llm = ChatVertexAI(model_name=\"gemini-2.0-flash\", temperature=0)\n",
        "\n",
        "template = ChatPromptTemplate.from_messages(\n",
        "    [\n",
        "        (\n",
        "            \"system\",\n",
        "            \"\"\"You are a conversational bot that produce nice recipes for users based on a question.\"\"\",\n",
        "        ),\n",
        "        MessagesPlaceholder(variable_name=\"messages\"),\n",
        "    ]\n",
        ")\n",
        "\n",
        "chain = template | llm"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MS9rlK6eq5qY"
      },
      "source": [
        "We can test our chain:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "qzMotN92qrqv"
      },
      "outputs": [],
      "source": [
        "chain.invoke([(\"human\", \"Hi there!\")])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NqdVpTMkq_pc"
      },
      "source": [
        "## Batch scoring\n",
        "\n",
        "We are now ready to perform batch scoring. To perform batch scoring we will leverage the utility function `batch_generate_messages`\n",
        "\n",
        "Have a look at the definition to see the expected input format."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Qc4DiVYOKk6H"
      },
      "outputs": [],
      "source": [
        "help(batch_generate_messages)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Pip0WjopXMsu"
      },
      "outputs": [],
      "source": [
        "scored_data = batch_generate_messages(messages=df_processed, callable=chain)\n",
        "scored_data"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7Tf1bssiSBk7"
      },
      "source": [
        "## Evaluation\n",
        "\n",
        "We'll utilize [Vertex AI Rapid Evaluation](https://cloud.google.com/vertex-ai/generative-ai/docs/models/rapid-evaluation) to assess our generative AI model's performance. This service within Vertex AI streamlines the evaluation process, integrates with [Vertex AI Experiments](https://cloud.google.com/vertex-ai/docs/experiments/intro-vertex-ai-experiments) for tracking, and offers a range of [pre-built metrics](https://cloud.google.com/vertex-ai/generative-ai/docs/models/determine-eval#task-and-metrics) and the capability to define custom ones.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5ZSzjLI_rBSp"
      },
      "source": [
        "#### Define a CustomMetric using Gemini model\n",
        "\n",
        "Define a customized Gemini model-based metric function, with explanations for the score. The registered custom metrics are computed on the client side, without using online evaluation service APIs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aT0uclHrSBlC"
      },
      "outputs": [],
      "source": [
        "evaluator_llm = ChatVertexAI(\n",
        "    model_name=\"gemini-2.0-flash\",\n",
        "    temperature=0,\n",
        "    response_mime_type=\"application/json\",\n",
        ")\n",
        "\n",
        "\n",
        "def custom_faithfulness(instance):\n",
        "    prompt = f\"\"\"You are examining written text content. Here is the text:\n",
        "************\n",
        "Written content: {instance[\"response\"]}\n",
        "************\n",
        "Original source data: {instance[\"reference\"]}\n",
        "\n",
        "Examine the text and determine whether the text is faithful or not.\n",
        "Faithfulness refers to how accurately a generated summary reflects the essential information and key concepts present in the original source document.\n",
        "A faithful summary stays true to the facts and meaning of the source text, without introducing distortions, hallucinations, or information that wasn't originally there.\n",
        "\n",
        "Your response must be an explanation of your thinking along with single integer number on a scale of 0-5, 0\n",
        "the least faithful and 5 being the most faithful.\n",
        "\n",
        "Produce results in JSON\n",
        "\n",
        "Expected format:\n",
        "\n",
        "```json\n",
        "{{\n",
        "    \"explanation\": \"< your explanation>\",\n",
        "    \"custom_faithfulness\": <your score>\n",
        "}}\n",
        "```\n",
        "\"\"\"\n",
        "\n",
        "    result = evaluator_llm.invoke([(\"human\", prompt)])\n",
        "    result = json.loads(result.content)\n",
        "    return result\n",
        "\n",
        "\n",
        "# Register Custom Metric\n",
        "custom_faithfulness_metric = CustomMetric(\n",
        "    name=\"custom_faithfulness\",\n",
        "    metric_function=custom_faithfulness,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JpdRqYlLq53t"
      },
      "source": [
        "### Run Evaluation with CustomMetric"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "w4iU_mIhoY93"
      },
      "outputs": [],
      "source": [
        "experiment_name = \"rapid-eval-langchain-eval\"  # @param {type:\"string\"}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "U3r3NiKNqn3u"
      },
      "source": [
        "We are now ready to run the evaluation. We will use different metrics, combining the custom metric we defined above with some pre-built metrics.\n",
        "\n",
        "Results of the evaluation will be automatically tagged into the experiment_name we defined.\n",
        "\n",
        "You can click `View Experiment`, to see the experiment in Google Cloud Console."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zeVra-g1rAFV"
      },
      "outputs": [],
      "source": [
        "metrics = [\"fluency\", \"coherence\", \"safety\", custom_faithfulness_metric]\n",
        "\n",
        "eval_task = EvalTask(\n",
        "    dataset=scored_data,\n",
        "    metrics=metrics,\n",
        "    experiment=experiment_name,\n",
        "    metric_column_mapping={\"prompt\": \"user\"},\n",
        ")\n",
        "eval_result = eval_task.evaluate()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ka3tZCL_uurD"
      },
      "source": [
        "Once an eval result is produced, we are able to display summary metrics:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KheOvIvtiRlz"
      },
      "outputs": [],
      "source": [
        "eval_result.summary_metrics"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JcALGGlwu0p_"
      },
      "source": [
        "We are also able to display a pandas dataframe containing a detailed summary of how our eval dataset performed and relative granular metrics."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9zJ686YYiWJC"
      },
      "outputs": [],
      "source": [
        "eval_result.metrics_table"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L1NsUKA3vEu8"
      },
      "source": [
        "## Iterating over the prompt\n",
        "\n",
        "Let's perform some simple changes to our chain to see how our evaluation results change."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O_8PnvLSv7Nu"
      },
      "outputs": [],
      "source": [
        "template = ChatPromptTemplate.from_messages(\n",
        "    [\n",
        "        (\n",
        "            \"system\",\n",
        "            \"\"\"You are a conversational bot that produce nice recipes for users based on a question.\n",
        "Before suggesting a recipe, you should ask for the dietary requirements..\"\"\",\n",
        "        ),\n",
        "        MessagesPlaceholder(variable_name=\"messages\"),\n",
        "    ]\n",
        ")\n",
        "\n",
        "new_chain = template | llm"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JF_Po81twPbr"
      },
      "outputs": [],
      "source": [
        "scored_data = batch_generate_messages(messages=df_processed, callable=new_chain)\n",
        "scored_data.rename(columns={\"text\": \"response\"}, inplace=True)\n",
        "scored_data"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "snIQ0itfwUZa"
      },
      "outputs": [],
      "source": [
        "metrics = [\"fluency\", \"coherence\", \"safety\", custom_faithfulness_metric]\n",
        "eval_task = EvalTask(\n",
        "    dataset=scored_data,\n",
        "    metrics=metrics,\n",
        "    experiment=experiment_name,\n",
        "    metric_column_mapping={\"prompt\": \"user\"},\n",
        ")\n",
        "eval_result = eval_task.evaluate()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CT6Ma5FLwfbF"
      },
      "outputs": [],
      "source": [
        "eval_result.summary_metrics"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b42l5juJsyyY"
      },
      "source": [
        "#### Let's compare both eval results\n",
        "\n",
        "We can do that by using the method `display_runs` for a given `eval task` object to see which prompt template performed best on our dataset."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HP0Zcm1yvh95"
      },
      "outputs": [],
      "source": [
        "df = vertexai.preview.get_experiment_df(experiment_name).T\n",
        "df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TpV-iwP9qw9c"
      },
      "source": [
        "## Cleaning up\n",
        "\n",
        "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
        "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
        "\n",
        "Otherwise, you can delete the individual resources you created in this tutorial."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sx_vKniMq9ZX"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "# Delete Experiments\n",
        "delete_experiments = True\n",
        "if delete_experiments or os.getenv(\"IS_TESTING\"):\n",
        "    experiments_list = aiplatform.Experiment.list()\n",
        "    for experiment in experiments_list:\n",
        "        experiment.delete()"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "name": "evaluate_langchain_chains.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
