-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add Human-in-the-Loop Research Agent tutorial #1063
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ashitpatel001
wants to merge
3
commits into
google-gemini:main
Choose a base branch
from
Ashitpatel001:langgraph-research-agent
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+372
−0
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,370 @@ | ||
| { | ||
| "nbformat": 4, | ||
| "nbformat_minor": 0, | ||
| "metadata": { | ||
| "colab": { | ||
| "provenance": [], | ||
| "authorship_tag": "ABX9TyO/6zbuo1wFgDiiwgwdKhjr", | ||
| "include_colab_link": true | ||
| }, | ||
| "kernelspec": { | ||
| "name": "python3", | ||
| "display_name": "Python 3" | ||
| }, | ||
| "language_info": { | ||
| "name": "python" | ||
| } | ||
| }, | ||
| "cells": [ | ||
| { | ||
| "cell_type": "markdown", | ||
| "metadata": { | ||
| "id": "view-in-github", | ||
| "colab_type": "text" | ||
| }, | ||
| "source": [ | ||
| "<a href=\"https://colab.research.google.com/github/Ashitpatel001/cookbook/blob/langgraph-research-agent/examples/Agents/Gemini_LangGraph_Research_Agent.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "metadata": { | ||
| "id": "ELvSMCIlo8-V" | ||
| }, | ||
| "outputs": [], | ||
| "source": [ | ||
| "# @title Copyright 2025 Google LLC {\"display-mode\":\"form\"}\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", | ||
| "source": [ | ||
| "# Build a Research Agent with Gemini and LangGraph\n", | ||
| "\n", | ||
| "<table align=\"left\">\n", | ||
| " <td>\n", | ||
| " <a target=\"_blank\" href=\"https://colab.research.google.com/github/google-gemini/cookbook/blob/main/examples/Agents/Gemini_LangGraph_Research_Agent.ipynb\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" /></a>\n", | ||
| " </td>\n", | ||
| "</table>\n", | ||
Ashitpatel001 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "\n", | ||
| "This tutorial demonstrates how to use Gemini 1.5 Flash and LangGraph to create a Research Agent that can plan, search the web, and write reports.\n", | ||
| "\n", | ||
| "Basic chains, such as those found in LangChain, carry out steps in a linear order (A --> B --> C), but they frequently break down when real-world data is incomplete or messy. This is resolved by LangGraph's introduction of cycles, which let your agent \"loop back,\" retry steps that didn't work, or adjust its strategy in light of fresh data.\n", | ||
| "\n", | ||
| "\n", | ||
| "Note: The Research Agent pattern is implemented in an interactive, simplified manner in this tutorial. Check out the official Gemini Fullstack LangGraph Quickstart for a full-stack implementation that is ready for production, complete with React frontend and Docker support.\n", | ||
| "## Prerequisites\n", | ||
| "\n", | ||
| "To run this tutorial, you will need:\n", | ||
| "- A **Google AI Studio API Key** (Get one [here](https://aistudio.google.com/)).\n", | ||
| "- A **Tavily API Key** for web search (Get one [here](https://tavily.com/))." | ||
| ], | ||
| "metadata": { | ||
| "id": "hGRP9lpmEYoL" | ||
| } | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "%pip install -U -q langgraph langchain-google-genai langchain-community tavily-python" | ||
| ], | ||
| "metadata": { | ||
| "id": "PVgq6xzA8PMO" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "import os\n", | ||
| "from google.colab import userdata\n", | ||
| "\n", | ||
| "# Retrieve keys from Colab Secrets\n", | ||
| "try:\n", | ||
| " os.environ[\"GOOGLE_API_KEY\"] = userdata.get('Langgraph')\n", | ||
| " os.environ[\"TAVILY_API_KEY\"] = userdata.get('Tavily')\n", | ||
| "except Exception as e:\n", | ||
| " print(\" Error: Please set 'GOOGLE_API_KEY' and 'TAVILY_API_KEY' in Colab Secrets.\")" | ||
Ashitpatel001 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ], | ||
| "metadata": { | ||
| "id": "5icyWncJ9iiV" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "source": [ | ||
| "## Step 1: Define the Agent State and Tools\n", | ||
| "\n", | ||
| "We start by defining the **State** (the memory structure passed between nodes) and initializing our tools.\n", | ||
Ashitpatel001 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "\n", | ||
| "We use **Gemini 2.5 Flash** for its speed and **Tavily** for optimized search results." | ||
| ], | ||
| "metadata": { | ||
| "id": "zkBHQmWW9OCv" | ||
| } | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "from typing import Annotated, List, TypedDict\n", | ||
| "from pydantic import BaseModel, Field\n", | ||
| "from langchain_google_genai import ChatGoogleGenerativeAI\n", | ||
| "from langchain_community.tools.tavily_search import TavilySearchResults\n", | ||
| "\n", | ||
| "# Define the Agent State (Memory)\n", | ||
| "class AgentState(TypedDict):\n", | ||
| " topic: str\n", | ||
| " plan: List[str]\n", | ||
| " content: List[str]\n", | ||
| " final_report: str\n", | ||
| "\n", | ||
| "\n", | ||
| "class Plan(BaseModel):\n", | ||
| " steps: List[str] = Field(description=\"List of search queries to run.\")\n", | ||
| "\n", | ||
| "\n", | ||
| "MODEL_ID = \"gemini-2.5-flash\" # @param [\"gemini-2.5-flash\", \"gemini-1.5-pro\"]\n", | ||
| "\n", | ||
| "# Initialize the model using the variable from the form above\n", | ||
| "model = ChatGoogleGenerativeAI(model=MODEL_ID, temperature=0)\n", | ||
| "\n", | ||
| "search_tool = TavilySearchResults(max_results=1)\n" | ||
| ], | ||
| "metadata": { | ||
| "id": "XQtk7mac9iLt" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "source": [ | ||
| "## Step 2: Create the Agent Nodes\n", | ||
| "\n", | ||
| "Here we define the three distinct roles our agent will perform:\n", | ||
| "1. **Planner:** Uses Gemini's structured output to break the topic into search queries.\n", | ||
| "2. **Researcher:** Executes the queries using the Tavily API.\n", | ||
| "3. **Writer:** Synthesizes the findings into a report, streaming the output for a real-time effect." | ||
| ], | ||
| "metadata": { | ||
| "id": "MwsxCItT9jFx" | ||
| } | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "# Define the Planner Node\n", | ||
| "def plan_node(state: AgentState):\n", | ||
| " print(f\"PLANNING: {state['topic']}\")\n", | ||
| " planner = model.with_structured_output(Plan) # Pydantic schema for the model to invoke the structured output\n", | ||
| " result = planner.invoke(f\"Create a step-by-step search plan for: {state['topic']}\")\n", | ||
| " return {\"plan\": result.steps} #Displays the steps only\n", | ||
| "\n", | ||
| "# Define the Researcher Node\n", | ||
| "def research_node(state: AgentState):\n", | ||
| " print(f\" RESEARCHING \")\n", | ||
| " content = []\n", | ||
| " for step in state['plan']:\n", | ||
| " result = search_tool.invoke(step) #Tavily tool will invoke the plan and start researching\n", | ||
| " content.append(str(result))\n", | ||
| " return {\"content\": content}\n", | ||
| "\n", | ||
| "# Define the Writer Node\n", | ||
| "def write_node(state: AgentState):\n", | ||
| " print(f\" WRITING \")\n", | ||
| " prompt = (\n", | ||
| " f\"Write a detailed article about '{state['topic']}' using this research: {state['content']}. \"\n", | ||
| " f\"Focus on the content. Do NOT include a 'Date', 'Prepared For', or 'Prepared By' section. \"\n", | ||
| " f\"Start directly with the Title and Introduction.\"\n", | ||
| " )\n", | ||
| "\n", | ||
| " full_report = \"\"\n", | ||
| "\n", | ||
| " #Stream output for real time effect\n", | ||
| " for chunk in model.stream(prompt):\n", | ||
| " text = chunk.content # Here we will print every chunk as soon as it generates\n", | ||
| " print(text,end=\"\", flush=True)\n", | ||
| " full_report += text\n", | ||
| "\n", | ||
| " print(\"\\n\")\n", | ||
| " return {\"final_report\": full_report}\n", | ||
| "\n" | ||
| ], | ||
| "metadata": { | ||
| "id": "tQsabZrI9hxQ" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "source": [ | ||
| "## Step 3: Build the graph with \"Human-in-the-Loop\"\n", | ||
| "\n", | ||
| "You connect the nodes using **LangGraph**.\n", | ||
| "\n", | ||
| "Key features here:\n", | ||
| "* **`MemorySaver()`**: Persists the state between pauses.\n", | ||
| "* **`interrupt_before=[\"researcher\"]`**: This is the \"Manager Mode.\" The graph will **PAUSE** after planning, allowing you to approve the plan before the agent spends time searching." | ||
| ], | ||
| "metadata": { | ||
| "id": "psK2BaJ0-FfC" | ||
| } | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "from langgraph.checkpoint.memory import MemorySaver #Import for memory\n", | ||
| "from langgraph.graph import StateGraph ,START, END\n", | ||
| "\n", | ||
| "# Create the Graph\n", | ||
| "workflow = StateGraph(AgentState)\n", | ||
| "\n", | ||
| "# Add Nodes\n", | ||
| "workflow.add_node(\"planner\", plan_node)\n", | ||
| "workflow.add_node(\"researcher\", research_node)\n", | ||
| "workflow.add_node(\"writer\", write_node)\n", | ||
| "\n", | ||
| "# Add Edges\n", | ||
| "workflow.add_edge(START , \"planner\")\n", | ||
| "workflow.add_edge(\"planner\", \"researcher\")\n", | ||
| "workflow.add_edge(\"researcher\", \"writer\")\n", | ||
| "workflow.add_edge(\"writer\", END)\n", | ||
| "\n", | ||
| "#Compile\n", | ||
| "memory = MemorySaver() #It will remember the pauses\n", | ||
| "app = workflow.compile(\n", | ||
| " checkpointer=memory,\n", | ||
| " interrupt_before=[\"researcher\"] # Pause here!\n", | ||
| ")" | ||
| ], | ||
| "metadata": { | ||
| "id": "PpngzXemKsjn" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "source": [ | ||
| "## Step 4: Execution till Planning\n", | ||
| "\n", | ||
| "Run the cell below to generate a plan. The agent will **pause** automatically." | ||
| ], | ||
| "metadata": { | ||
| "id": "SuEOQTKQ-tE_" | ||
| } | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "# GENERATE PLAN\n", | ||
| "thread = {\"configurable\": {\"thread_id\": \"1\"}}\n", | ||
| "\n", | ||
| "#Enter your query here\n", | ||
| "\n", | ||
| "topic_input = \"The mystery of the bermuda triangle\" # @param {type: \"string\"}\n", | ||
| "user_input = {\"topic\": topic_input }\n", | ||
| "\n", | ||
| "# Run until the pause\n", | ||
| "for event in app.stream(user_input, thread, stream_mode='values'):\n", | ||
| " result = event\n", | ||
| "\n", | ||
| "# Print the plan for the user to see\n", | ||
| "print(f\"Current plan: {result['plan']}\")\n", | ||
| "print(\"\\n PAUSED: Waiting for your approval....\")\n", | ||
| "print(\"If the plan matches your expectations Execute the next cell to continue the breakdown\")" | ||
| ], | ||
| "metadata": { | ||
| "id": "EGx5IrrbKvND" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "source": [ | ||
| "## Step 5: Approval after planning\n", | ||
| "\n", | ||
| "If you are happy with the plan above, run this cell to **resume** the agent. It will execute the searches and write the final report." | ||
| ], | ||
| "metadata": { | ||
| "id": "GVnD2JTs_WEM" | ||
| } | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "source": [ | ||
| "from IPython.display import clear_output, Markdown\n", | ||
| "\n", | ||
| "print(\" APPROVED. RESUMING GRAPH \")\n", | ||
| "\n", | ||
| "# Passing None resumes execution from the paused state\n", | ||
| "for event in app.stream(None, thread, stream_mode=\"values\"):\n", | ||
| " if 'final_report' in event and event['final_report']:\n", | ||
| " final_content = event['final_report']\n", | ||
| "\n", | ||
| "clear_output()\n", | ||
| "\n", | ||
| "# Show only the beautiful final report\n", | ||
| "display(Markdown(final_content))" | ||
| ], | ||
| "metadata": { | ||
| "id": "yqeaA1BaNGqa" | ||
| }, | ||
| "execution_count": null, | ||
| "outputs": [] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "source": [ | ||
| "**Conclusion and future roadmap**\n", | ||
| "\n", | ||
| "Congratulations!! You have accomplished a fantastic job of creating a live example of a Human in loop research agent utilizing Gemini 2.5 Flash and LangGraph!\n", | ||
| "\n", | ||
| "**What Was Learned?**\n", | ||
| "\n", | ||
| "- **Structured Planning** – Gemini's JSON mode for defining the logic for a dependable acting plan.\n", | ||
| "- **Cyclical Logic** – Moving from a linear approach to workflows to an adaptive set of workflows in cycles.\n", | ||
| "- **Human In The Loop** – Included the 'interrupt_before' command and added memory saving functionality to implement a “manager” step for approval cycles.\n", | ||
| "\n", | ||
| "**Roadmap to Production**\n", | ||
| "\n", | ||
| "The example presented is a prospective human-in-the-loop research agent (HILRA) application with level 1 functionalities. However, the following will significantly enhance the architectural model for HILRA moving from prototype to a production-ready human-in-the-loop research agent:\n", | ||
| "\n", | ||
| "1. **Persistence (Data Storage):**\n", | ||
| " - Current : The current version of the Memory Saver expands to use RAM, and as such once the notebook has been shut down/started, the work done will be lost.\n", | ||
| " - Future : Implementing a permanent solution to store persistent data and maintain the ability to restart where we left off will be achieved by using a permanent storage solution such as Postgres, Redis, or CheckPoint.\n", | ||
| " \n", | ||
| "2. **Agentic RAG (Retrieval Augmented Generation):**\n", | ||
| " - Current : Currently, the HILRA accesses the internet to search for content to assist the research.\n", | ||
| " - Future : By integrating a Vector database with a Vector engine, HILRAs will be able to retrieve all information stored on the private PDFs and produce a response from that information.\n", | ||
| "\n", | ||
| "3. **Observability (Using LangSmith):**\n", | ||
| " - Current : Currently, you are only able to debug using 'print()' statements. As such, debugging across several papers can be challenging.\n", | ||
| " - Future: The integration of LangSmith will allow for visualisation of the full execution paths, token usage on the execution graph, and to verify at what steps the loops fail effectively.\n", | ||
| "\n", | ||
| "4. **Collaborative Agents:**\n", | ||
| " - Future : The intention is to ultimately have the capability to utilise many specialised HILRAs instead of one (Generalisation) HILRA to enable much broader and deeper research opportunities." | ||
| ], | ||
| "metadata": { | ||
| "id": "Xvr3aCpmcVmS" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.