fix(web): allow session resume without new message

Merge https://github.com/google/adk-python/pull/4185

**Description**
 This PR resolves #4100 by making the `new_message` field optional in the `RunAgentRequest` model.

 **The Problem:**
 When attempting to resume an agent session via the FastAPI web server, the request would fail with a `422 Unprocessable Entity` if `new_message` was omitted. This prevented "resume-only" workflows where a user just wants to wake up an existing session.

 **The Solution:**
 Updated `RunAgentRequest.new_message` to be `Optional[types.Content] = None`. The underlying `runner.run_async` logic already supports `None` for resuming purposes, so no further logic changes were required.

 **Verification:**
 Verified that `RunAgentRequest` now validates successfully when `new_message` is missing, defaulting the field to `None`.

Co-authored-by: Liang Wu <wuliang@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4185 from Akshat8510:fix/fastapi-resume-4100 b6d252636aa5f96186507fccf47a278fe733a362
PiperOrigin-RevId: 869761179
This commit is contained in:
Akshat8510
2026-02-13 09:11:29 -08:00
committed by Copybara-Service
parent ede925b502
commit 30b2ed3ef8
2 changed files with 18 additions and 5 deletions
+1 -1
View File
@@ -204,7 +204,7 @@ class RunAgentRequest(common.BaseModel):
app_name: str
user_id: str
session_id: str
new_message: types.Content
new_message: Optional[types.Content] = None
streaming: bool = False
state_delta: Optional[dict[str, Any]] = None
# for resume long-running functions
+17 -4
View File
@@ -18,9 +18,7 @@ import logging
import os
from pathlib import Path
import signal
import sys
import tempfile
import time
from typing import Any
from typing import Optional
from unittest.mock import AsyncMock
@@ -38,14 +36,12 @@ from google.adk.errors.input_validation_error import InputValidationError
from google.adk.evaluation.eval_case import EvalCase
from google.adk.evaluation.eval_case import Invocation
from google.adk.evaluation.eval_result import EvalSetResult
from google.adk.evaluation.eval_set import EvalSet
from google.adk.evaluation.in_memory_eval_sets_manager import InMemoryEvalSetsManager
from google.adk.events.event import Event
from google.adk.events.event_actions import EventActions
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.adk.sessions.state import State
from google.genai import types
from pydantic import BaseModel
import pytest
@@ -1530,6 +1526,23 @@ def test_builder_save_rejects_traversal(builder_test_client, tmp_path):
assert not (tmp_path / "app" / "tmp" / "escape.yaml").exists()
def test_agent_run_resume_without_message_success(
test_app, create_test_session
):
"""Test that /run allows resuming a session with only an invocation_id, without a new message."""
info = create_test_session
url = "/run"
payload = {
"app_name": info["app_name"],
"user_id": info["user_id"],
"session_id": info["session_id"],
"invocation_id": "test_invocation_id",
"streaming": False,
}
response = test_app.post(url, json=payload)
assert response.status_code == 200
def test_health_endpoint(test_app):
"""Test the health endpoint."""
response = test_app.get("/health")