{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ur8xi4C7S06n"
      },
      "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": "JAPoU8Sm5E6e"
      },
      "source": [
        "# From API to Report: Building a Currency Analysis Agent with LangGraph and Gemini\n",
        "\n",
        "<table align=\"left\">\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/generative-ai/blob/main/gemini/orchestration/intro_langgraph_gemini.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%2Forchestration%2Fintro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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/orchestration/intro_langgraph_gemini.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>            "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "84f0f73a0f76"
      },
      "source": [
        "| Author |\n",
        "| --- |\n",
        "| [Kristopher Overholt](https://github.com/koverholt/) |"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tvgnzT1CKxrO"
      },
      "source": [
        "## Overview\n",
        "\n",
        "This notebook demonstrates how to build an AI-powered currency analysis agent using [LangGraph](https://langchain-ai.github.io/langgraph/), an orchestration framework for language models, and the [Gemini API in Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models). This example showcases the potential of combining LangGraph's workflow capabilities with Gemini's language understanding and generation skills to streamline and automate complex financial analysis tasks. To learn more about the motivation and benefits of using LangGraph to build AI agents, refer to the [Why LangGraph?](https://langchain-ai.github.io/langgraph/concepts/high_level/) documentation.\n",
        "\n",
        "The agent automates the process of retrieving real-time and historical exchange rates from a currency API, verifying the accuracy of the data, and generating a report summarizing the findings. By defining a structured workflow with distinct nodes for API interaction, data validation, and report generation, the agent efficiently handles each stage of the currency analysis process:\n",
        "\n",
        "<img src=\"https://storage.googleapis.com/github-repo/generative-ai/gemini/orchestration/langgraph/gemini-currency-agent.jpg\" width=\"200px\">\n",
        "\n",
        "In this notebook tutorial, you'll learn how to:\n",
        "\n",
        "- Design and implement a multi-stage LangGraph workflow for interacting with external APIs and processing data\n",
        "- Integrate the Gemini API to enable natural language understanding and report generation\n",
        "- Use LangChain tools to access and query external data sources\n",
        "- Validate data accuracy using Gemini's reasoning capabilities\n",
        "- Generate clear and informative reports from the analyzed data"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "61RBz8LLbxCR"
      },
      "source": [
        "## Get started"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "No17Cw5hgx12"
      },
      "source": [
        "### Install Vertex AI SDK and other required packages\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "tFy3H3aPgx12"
      },
      "outputs": [],
      "source": [
        "%pip install -q -U \\\n",
        "    langgraph \\\n",
        "    langchain-google-vertexai \\\n",
        "    requests"
      ]
    },
    {
      "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": 1,
      "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": 2,
      "metadata": {
        "id": "Nqwi-5ufWp_B"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "import vertexai\n",
        "\n",
        "PROJECT_ID = \"[your-project-id]\"  # @param {type:\"string\", isTemplate: true}\n",
        "if PROJECT_ID == \"[your-project-id]\":\n",
        "    PROJECT_ID = str(os.environ.get(\"GOOGLE_CLOUD_PROJECT\"))\n",
        "\n",
        "LOCATION = os.environ.get(\"GOOGLE_CLOUD_REGION\", \"us-central1\")\n",
        "\n",
        "vertexai.init(project=PROJECT_ID, location=LOCATION)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EdvJRUWRNGHE"
      },
      "source": [
        "## Building and running the currency analysis agent"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5303c05f7aa6"
      },
      "source": [
        "### Import libraries\n",
        "\n",
        "This section imports the necessary libraries for LangGraph, LangChain, Vertex AI, and other utilities needed for your agent's functionality.\n",
        "\n",
        "This includes tools for interacting with the Gemini API, defining custom tools, managing agent state, and displaying results."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "6fc324893334"
      },
      "outputs": [],
      "source": [
        "# Standard libraries\n",
        "import logging\n",
        "from typing import Annotated, TypedDict\n",
        "\n",
        "# IPython display utilities\n",
        "from IPython.display import Image, Markdown, display\n",
        "\n",
        "# LangChain core components\n",
        "from langchain_core.messages import HumanMessage, SystemMessage\n",
        "from langchain_core.tools import tool\n",
        "\n",
        "# Vertex AI and Gemini integration\n",
        "from langchain_google_vertexai import ChatVertexAI\n",
        "\n",
        "# LangGraph components\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import END, START, StateGraph\n",
        "from langgraph.graph.message import add_messages\n",
        "from langgraph.prebuilt import ToolNode\n",
        "import requests"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e43229f3ad4f"
      },
      "source": [
        "### Initialize agent state and agent memory\n",
        "\n",
        "Here, you initialize your agent's [memory](https://langchain-ai.github.io/langgraph/how-tos/memory/manage-conversation-history/) to store information during the workflow.\n",
        "\n",
        "You also define the schema for your [agent's state](https://langchain-ai.github.io/langgraph/how-tos/state-model/), which includes the task description and conversation history."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "id": "cf93d5f0ce00"
      },
      "outputs": [],
      "source": [
        "# Define the agent's state\n",
        "\n",
        "\n",
        "class AgentState(TypedDict):\n",
        "    task: str\n",
        "    messages: Annotated[list, add_messages]\n",
        "\n",
        "\n",
        "# Initialize agent memory\n",
        "memory = MemorySaver()\n",
        "\n",
        "# Set logging level to ERROR to filter warnings\n",
        "logger = logging.getLogger()\n",
        "logger.setLevel(logging.ERROR)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "27b61a7e7ef6"
      },
      "source": [
        "### Initialize Gemini model\n",
        "\n",
        "Initialize the Gemini model from Vertex AI, specifying the model version and temperature settings.\n",
        "\n",
        "This sets up the core language model that will power your agent's actions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "id": "ca1c01bb3175"
      },
      "outputs": [],
      "source": [
        "model = ChatVertexAI(model=\"gemini-2.0-flash\", temperature=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a91e085ac90e"
      },
      "source": [
        "### Define tools\n",
        "\n",
        "This section defines a custom tool that will be used by your agent to retrieve currency exchange rates from an external API. The tool fetches exchange rates for a specified currency pair and date.\n",
        "\n",
        "Your LangGraph application will use this [tool node to call functions](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/) and obtain information from the outside world."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "id": "4b5cff5eeb89"
      },
      "outputs": [],
      "source": [
        "@tool\n",
        "def get_exchange_rate(\n",
        "    currency_from: str = \"USD\",\n",
        "    currency_to: str = \"EUR\",\n",
        "    currency_date: str = \"latest\",\n",
        "):\n",
        "    \"\"\"Retrieves the exchange rate between two currencies on a specified date.\n",
        "\n",
        "    Args:\n",
        "        currency_from (str, optional): The base currency. Defaults to \"USD\".\n",
        "        currency_to (str, optional): The target currency. Defaults to \"EUR\".\n",
        "        currency_date (str, optional): The date for the exchange rate (YYYY-MM-DD).\n",
        "                                      Use \"latest\" for the most recent rate. Defaults to \"latest\".\n",
        "    \"\"\"\n",
        "    response = requests.get(\n",
        "        f\"https://api.frankfurter.app/{currency_date}\",\n",
        "        params={\"from\": currency_from, \"to\": currency_to},\n",
        "    )\n",
        "    return response.json()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6cf6b70b7bb6"
      },
      "source": [
        "### Define prompts and functions for each node\n",
        "\n",
        "Here, you define the prompts and functions that will be used for each node in the LangGraph workflow. These prompts guide the Gemini model in performing specific actions, such as retrieving exchange rates, verifying their accuracy, and generating a report.\n",
        "\n",
        "By separating out our agent's functionality into distinct nodes, you can choose exactly which actions that node should perform and how much to defer to the LLM vs. application code in a given node. Notice how each node uses information from the global state, then updates the global state variables in the `return` statement."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "id": "c94afec80030"
      },
      "outputs": [],
      "source": [
        "#######################################\n",
        "# Agent Node: Look up the exchange rate\n",
        "#######################################\n",
        "\n",
        "\n",
        "def agent_node(state: AgentState):\n",
        "    messages = state[\"messages\"]\n",
        "    model_with_tools = model.bind_tools(tools)\n",
        "    response = model_with_tools.invoke(messages)\n",
        "    return {\"messages\": [response]}\n",
        "\n",
        "\n",
        "#########################################\n",
        "# Tool Node: Define based on API function\n",
        "#########################################\n",
        "\n",
        "tools = [get_exchange_rate]\n",
        "tool_node = ToolNode(tools)\n",
        "\n",
        "\n",
        "# Determine if additional tool calls are needed\n",
        "def should_continue(state: AgentState):\n",
        "    messages = state[\"messages\"]\n",
        "    last_message = messages[-1]\n",
        "    if last_message.tool_calls:\n",
        "        return \"tools\"\n",
        "    return \"review\"\n",
        "\n",
        "\n",
        "#########################################\n",
        "# Review Node: Analyze and review results\n",
        "#########################################\n",
        "\n",
        "REVIEW_PROMPT = \"\"\"You are a financial expert. Analyze the converted\n",
        "rates and verify the correctness of the figures.\n",
        "This is the conversation history so far:\n",
        "{messages}\"\"\"\n",
        "\n",
        "\n",
        "def review_node(state: AgentState):\n",
        "    messages = [\n",
        "        HumanMessage(content=REVIEW_PROMPT.format(messages=state[\"messages\"])),\n",
        "    ]\n",
        "    response = model.invoke(messages)\n",
        "    return {\"messages\": response}\n",
        "\n",
        "\n",
        "########################################\n",
        "# Report Node: Generate a summary report\n",
        "########################################\n",
        "\n",
        "\n",
        "REPORT_PROMPT = \"\"\"Construct a summary report for a financial news\n",
        "reporter covering current exchange rates. Include a table of results\n",
        "comparing the rates over time. Do not use currency symbols in the\n",
        "output since they might break the rendering of the output.\n",
        "This is the conversation history so far:\n",
        "{messages}\"\"\"\n",
        "\n",
        "\n",
        "def report_node(state: AgentState):\n",
        "    messages = [\n",
        "        HumanMessage(content=REPORT_PROMPT.format(messages=state[\"messages\"])),\n",
        "    ]\n",
        "    response = model.invoke(messages)\n",
        "    return {\"messages\": response}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3994d1b07f65"
      },
      "source": [
        "### Define and compile the graph\n",
        "\n",
        "This section defines the structure of the LangGraph workflow, connecting the nodes in a logical sequence.\n",
        "\n",
        "The graph is then compiled, incorporating your agent's memory for state management."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "id": "65732c3a628d"
      },
      "outputs": [],
      "source": [
        "# Initialize the LangGraph workflow, specifying the agent's state schema\n",
        "workflow = StateGraph(AgentState)\n",
        "\n",
        "# Add nodes to the workflow, associating each node with its corresponding function\n",
        "workflow.add_node(\"agent\", agent_node)\n",
        "workflow.add_node(\"tools\", tool_node)\n",
        "workflow.add_node(\"review\", review_node)\n",
        "workflow.add_node(\"report\", report_node)\n",
        "\n",
        "# Define the flow of execution between nodes, creating the workflow's logic\n",
        "workflow.add_edge(START, \"agent\")\n",
        "workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", \"review\"])\n",
        "workflow.add_edge(\"tools\", \"agent\")\n",
        "workflow.add_edge(\"review\", \"report\")\n",
        "workflow.add_edge(\"report\", END)\n",
        "\n",
        "# Compile the LangGraph workflow, enabling memory-based state management\n",
        "graph = workflow.compile(checkpointer=memory)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1f02a671723e"
      },
      "source": [
        "### Show the compiled graph\n",
        "\n",
        "You can visualize the compiled LangGraph workflow using a diagram, which provides a clear overview of your agent's execution flow."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "27513ba58a26"
      },
      "outputs": [],
      "source": [
        "Image(graph.get_graph().draw_mermaid_png())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a7c3daaeee31"
      },
      "source": [
        "### Run the agent!\n",
        "\n",
        "Now you're ready to initiate the workflow with a system prompt and user prompt to retrieve current and historical currency exchange rates.\n",
        "\n",
        "Feel free to modify these prompts and see how the agent responds! Try switching countries, currencies, time periods, or other changes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "id": "a551812eb672"
      },
      "outputs": [],
      "source": [
        "# Define the system prompt instructing the agent on how to answer the user's question.\n",
        "SYSTEM_PROMPT = \"\"\"Use the tools provided to answer the user's question.\n",
        "Also look up the exchange rate for all currencies from three months prior\n",
        "and compare the values.\"\"\"\n",
        "\n",
        "# Define the user's initial question about exchange rates.\n",
        "USER_PROMPT = \"\"\"What's the exchange rate from USD to (SEK, EUR, and JPY)\n",
        "as of today? And how much would $100 USD be worth in those currencies?\"\"\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ec077a30c366"
      },
      "source": [
        "Now you're ready to execute your LangGraph agent!\n",
        "\n",
        "Now your agent will work through the nodes in the graph step-by-step to interact with the API, validate the data, and prepare a report."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "id": "65d16afb3bba"
      },
      "outputs": [],
      "source": [
        "# Initialize a LangGraph thread with a unique ID for state management.\n",
        "thread_config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "\n",
        "# Execute the LangGraph workflow, streaming the results of each node.\n",
        "for state in graph.stream(\n",
        "    {\n",
        "        \"messages\": [\n",
        "            SystemMessage(content=SYSTEM_PROMPT),\n",
        "            HumanMessage(content=USER_PROMPT),\n",
        "        ],\n",
        "    },\n",
        "    thread_config,\n",
        "):\n",
        "    # Print the name of the current node and its output for each step.\n",
        "    for node_name, node_output in state.items():\n",
        "        print(f\"Agent Node: {node_name}\\n\")\n",
        "        print(\"Agent Result:\")\n",
        "        print(str(node_output)[:1000])  # Truncate output for display\n",
        "    print(\"\\n====================\\n\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5d7d371f4fe5"
      },
      "source": [
        "### Render the output\n",
        "\n",
        "Finally, you render the final report generated by your agent, displaying the results of the currency analysis in a human-readable way."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "id": "dc4ce48f0964"
      },
      "outputs": [],
      "source": [
        "display(Markdown(state[\"report\"][\"messages\"].content))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "493869e2bc88"
      },
      "source": [
        "## Conclusion\n",
        "\n",
        "This notebook demonstrates the successful implementation of a currency analysis agent using LangGraph and the Gemini API. By leveraging LangGraph's orchestration framework, you were able to build a multi-stage workflow that integrates and automates API interactions, data validation, and report generation.\n",
        "\n",
        "Feel free to customize the tools and prompts in this example, or adapt it to your use case and desired graph workflow. You can learn more about [LangGraph](https://langchain-ai.github.io/langgraph/), the [Gemini API in Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models), or the [chat model provider for Vertex AI in LangChain](https://python.langchain.com/docs/integrations/chat/google_vertex_ai_palm/) in their respective documentation pages."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "name": "intro_langgraph_gemini.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
