Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 370 additions & 0 deletions examples/Agents/Gemini_LangGraph_Research_Agent.ipynb
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",
"\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.\")"
],
"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",
"\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"
}
}
]
}
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,5 @@ Some old examples are still using the legacy SDK, they should still work and are
* [JSON Capabilities](./json_capabilities/): A directory with guides containing different types of tasks you can do with JSON schemas.
* [Automate Google Workspace tasks with the Gemini API](./Apps_script_and_Workspace_codelab/): This codelabs shows you how to connect to the Gemini API using Apps Script, and uses the function calling, vision and text capabilities to automate Google Workspace tasks - summarizing a document, analyzing a chart, sending an email and generating some slides directly. All of this is done from a free text input.
* [Langchain examples](./langchain/): A directory with multiple examples using Gemini with Langchain.

* [Human-in-the-Loop Research Agent](Gemini_LangGraph_Research_Agent.ipynb): Build an interactive agent with LangGraph and Gemini.