Files
stateful-deepagents/stateful_deepagent.ipynb
j-broekhuizen 650cb13644 cleaning up
2025-11-07 13:23:47 -08:00

242 lines
13 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "409d0ca9",
"metadata": {},
"source": [
"## Setup: Agent Config and Tool Definition"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "74fd8f7e",
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
"import os\n",
"from tavily import TavilyClient\n",
"from langchain.agents import create_agent\n",
"from langchain.agents.middleware.types import AgentMiddleware\n",
"from langchain_core.messages import HumanMessage, ToolMessage\n",
"from langchain_core.tools import tool\n",
"from langchain_openai import ChatOpenAI\n",
"from langgraph.checkpoint.memory import InMemorySaver\n",
"from langgraph.types import Command, interrupt\n",
"from deepagents.graph import create_deep_agent \n",
"from deepagents.middleware.subagents import CompiledSubAgent\n",
"from dotenv import load_dotenv\n",
"\n",
"load_dotenv()\n",
"\n",
"tavily_client = TavilyClient(os.getenv(\"TAVILY_API_KEY\"))\n",
"model = ChatOpenAI(model=\"gpt-5-nano\", temperature=0)\n",
"\n",
"@tool\n",
"def get_current_weather(\n",
" query: str,\n",
" max_results: int = 5,\n",
" topic: Literal[\"general\", \"news\", \"finance\"] = \"general\",\n",
" include_raw_content: bool = False,\n",
"):\n",
" \"\"\"\n",
" A tool to search the web for current weather conditions.\n",
" Args:\n",
" query: The query about weather to search for\n",
" max_results: The maximum number of results to return.\n",
" topic: The topic of the search.\n",
" include_raw_content: Whether to include the raw content of the search results.\n",
" \"\"\"\n",
" return tavily_client.search(\n",
" query,\n",
" max_results=max_results,\n",
" include_raw_content=include_raw_content,\n",
" topic=topic,\n",
" )\n",
"\n",
"class WeatherApprovalMiddleware(AgentMiddleware):\n",
" \"\"\"Pause the weather subagent until a human approves the tool output.\"\"\"\n",
"\n",
" def before_model(self, state):\n",
" messages = state['messages']\n",
"\n",
" last_message = messages[-1]\n",
" if not isinstance(last_message, ToolMessage) or last_message.name != \"get_current_weather\":\n",
" return None\n",
"\n",
" interrupt_payload = {\n",
" \"prompt\": \"Approve or edit the latest weather lookup result.\",\n",
" \"tool_call_id\": last_message.tool_call_id,\n",
" \"tool_name\": last_message.name,\n",
" \"tool_output\": last_message.content,\n",
" }\n",
"\n",
" decision = interrupt(interrupt_payload)\n",
"\n",
" if not isinstance(decision, dict):\n",
" raise ValueError(\n",
" \"Resume payload must be a dict with 'status' and optional 'message'.\"\n",
" )\n",
"\n",
" status = decision.get(\"status\")\n",
" if status not in {\"approved\", \"edit\"}:\n",
" raise ValueError(\"Resume payload must include status 'approved' or 'edit'.\")\n",
"\n",
" review_note = decision.get(\"message\")\n",
" if status == \"edit\" and not review_note:\n",
" raise ValueError(\"Edits must include a 'message' detailing the edit to make.\")\n",
" if not review_note:\n",
" review_note = \"Weather lookup approved.\"\n",
"\n",
" human_msg = HumanMessage(\n",
" content=f\"[{\"APPROVED\" if status == \"approved\" else \"EDITED\"}] {review_note}\",\n",
" name=\"tool_reviewer\",\n",
" additional_kwargs={\n",
" \"approval_status\": status,\n",
" \"tool_call_id\": last_message.tool_call_id\n",
" },\n",
" )\n",
" return {\"messages\": [human_msg]}\n",
"\n",
"\n",
"weather_agent_runnable = create_agent(\n",
" model,\n",
" system_prompt='You are a weather expert. Use the get_current_weather tool to get the current weather conditions for a given location.',\n",
" tools=[get_current_weather],\n",
" middleware=[WeatherApprovalMiddleware()],\n",
" checkpointer=True, # \n",
")\n",
"\n",
"\n",
"weather_agent = CompiledSubAgent(\n",
" name='weather-agent',\n",
" description='A weather expert that uses the get_current_weather tool to get the current weather conditions for a given location.',\n",
" runnable=weather_agent_runnable,\n",
")\n",
"\n",
"\n",
"super_agent_ckpt = InMemorySaver()\n",
"super_agent = create_deep_agent(\n",
" model=model,\n",
" system_prompt='You are a weather expert. You have access to a specialist weather agent that can get the current weather conditions for a given location. Route to the weather agent when you need to get the current weather conditions for a given location.',\n",
" subagents=[weather_agent],\n",
" checkpointer=super_agent_ckpt,\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "71db0c2f",
"metadata": {},
"source": [
"## Invoke Agent and Handle Interrupt\n",
"\n",
"This cell demonstrates the interrupt workflow:\n",
"\n",
"- **Thread Configuration**: Sets up a thread ID for stateful conversation tracking\n",
"- **Agent Invocation**: Invokes the super agent with a weather query\n",
"- **Interrupt Handling**: The weather subagent's `before_model` middleware inspects the most recent `ToolMessage` and pauses whenever `get_current_weather` returns so a human can review the tool output before the next LLM call\n",
"- **Interrupt Inspection**: Extracts and prints the interrupt payload to show what information is available for approval\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "58ab943c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Super agent interrupt payload: {'prompt': 'Approve or edit the latest weather lookup result.', 'tool_call_id': 'call_GztCZhZ0RB4tqvQbVuKFGnUD', 'tool_name': 'get_current_weather', 'tool_output': '{\"query\": \"San Francisco current weather\", \"follow_up_questions\": null, \"answer\": null, \"images\": [], \"results\": [{\"title\": \"Weather in San Francisco, California, USA\", \"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.775, \\'lon\\': -122.4183, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1762552110, \\'localtime\\': \\'2025-11-07 13:48\\'}, \\'current\\': {\\'last_updated_epoch\\': 1762551900, \\'last_updated\\': \\'2025-11-07 13:45\\', \\'temp_c\\': 19.4, \\'temp_f\\': 66.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 4.7, \\'wind_kph\\': 7.6, \\'wind_degree\\': 271, \\'wind_dir\\': \\'W\\', \\'pressure_mb\\': 1020.0, \\'pressure_in\\': 30.12, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 76, \\'cloud\\': 50, \\'feelslike_c\\': 19.4, \\'feelslike_f\\': 66.9, \\'windchill_c\\': 18.9, \\'windchill_f\\': 66.0, \\'heatindex_c\\': 18.9, \\'heatindex_f\\': 66.0, \\'dewpoint_c\\': 16.6, \\'dewpoint_f\\': 61.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 3.4, \\'gust_mph\\': 6.6, \\'gust_kph\\': 10.7}}\", \"score\": 0.8121933, \"raw_content\": null}, {\"url\": \"https://world-weather.info/forecast/usa/san_francisco/november-2025/\", \"title\": \"Weather in San Francisco in November 2025 (California)\", \"content\": \"Detailed ⚡ San Francisco Weather Forecast for November 2025 day/night 🌡️ temperatures, precipitations World-Weather.info.\", \"score\": 0.776708, \"raw_content\": null}, {\"url\": \"https://weatherspark.com/h/m/557/2025/7/Historical-Weather-in-July-2025-in-San-Francisco-California-United-States\", \"title\": \"San Francisco July 2025 Historical Weather Data (California, United ...\", \"content\": \"July 2025 Weather History in San Francisco California, United States ; San Francisco Temperature History July 2025 · 50°F · 50°F · 55°F · 55°F ; Hourly Temperature in\", \"score\": 0.7065353, \"raw_content\": null}, {\"url\": \"https://www.cbsnews.com/sanfrancisco/video/friday-morning-first-alert-weather-forecast-with-zoe-mintz-71125/\", \"title\": \"Friday morning First Alert weather forecast with Zoe Mintz - 7/11/25\", \"content\": \"San Francisco 49ers · San Francisco Giants · Golden State Warriors ... Friday morning First Alert weather forecast with Zoe Mintz - 7/11/25.\", \"score\": 0.65412235, \"raw_content\": null}, {\"url\": \"https://world-weather.info/forecast/usa/san_francisco/july-2025/\", \"title\": \"Weather in San Francisco in July 2025 (California)\", \"content\": \"Detailed ⚡ San Francisco Weather Forecast for July 2025 day/night 🌡️ temperatures, precipitations World-Weather.info.\", \"score\": 0.6133529, \"raw_content\": null}], \"response_time\": 1.83, \"request_id\": \"2cb97e6b-ffaa-4a4b-9d51-ddca03006961\"}'}\n"
]
}
],
"source": [
"\n",
"thread_config = {'configurable': {'thread_id': '1'}}\n",
"\n",
"initial_result = super_agent.invoke(\n",
" {'messages': [{'role': 'user', 'content': 'What is the current weather in San Francisco?'}]},\n",
" config=thread_config,\n",
")\n",
"interrupt_result = initial_result['__interrupt__'][0]\n",
"print('Super agent interrupt payload:', interrupt_result.value)"
]
},
{
"cell_type": "markdown",
"id": "4d9dbb49",
"metadata": {},
"source": [
"## Resume Agent After Approval\n",
"\n",
"This cell demonstrates resuming the agent after approval (or rejection):\n",
"\n",
"- **Resume Command**: Creates a `Command` with `resume` data that includes a `status` (\"approved\" or \"rejected\") and an optional `message` (required if rejecting) that will be inserted as a `HumanMessage` after the tool message\n",
"- **Agent Resume**: Invokes the super agent again with the resume command, continuing from where it was interrupted\n",
"- **Result Extraction**: Extracts tool messages, the inserted review message, and the final agent response to show the complete workflow result\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "2e2f2b58",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"Current weather in San Francisco, CA:\n",
"- Temperature: 66.9°F (19.4°C)\n",
"- Conditions: Partly cloudy\n",
"- Wind: 4.7 mph from the West, gusts up to 6.6 mph\n",
"- Humidity: 76%\n",
"- Visibility: 9.0 miles (16.0 km)\n",
"- Weather alerts: None active\n",
"- Last updated: 1:45 PM PT\n",
"\n",
"Would you like any additional details (dew point, pressure, or hourly forecast)?\n"
]
}
],
"source": [
"review_message = {\n",
" 'status': 'approved',\n",
" 'message': '',\n",
"}\n",
"resume_cmd = Command(resume=review_message)\n",
"resume_result = super_agent.invoke(resume_cmd, config=thread_config)\n",
"resume_result['messages'][-1].pretty_print()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}