From 6289c46042163fbf9f42fc52057e301f1e9ea979 Mon Sep 17 00:00:00 2001 From: Diptendu Das Date: Mon, 29 Dec 2025 21:03:03 +0530 Subject: [PATCH] fix: Store Pydantic models as dicts in session state for JSON serialization This fixes the 'Object of type IntentMandate is not JSON serializable' error when the ADK's SqliteSessionService tries to serialize session state. Changes: - Store IntentMandate, CartMandate, PaymentMandate, and PaymentReceipt as dicts using .model_dump() instead of raw Pydantic objects - Reconstruct Pydantic objects on retrieval where needed for property access --- .../payment_method_collector/tools.py | 5 +++- .../shopping_agent/subagents/shopper/tools.py | 14 +++++++---- .../python/src/roles/shopping_agent/tools.py | 23 +++++++++++++------ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/samples/python/src/roles/shopping_agent/subagents/payment_method_collector/tools.py b/samples/python/src/roles/shopping_agent/subagents/payment_method_collector/tools.py index f24fe572..b9fc2da3 100644 --- a/samples/python/src/roles/shopping_agent/subagents/payment_method_collector/tools.py +++ b/samples/python/src/roles/shopping_agent/subagents/payment_method_collector/tools.py @@ -20,6 +20,7 @@ from google.adk.tools.tool_context import ToolContext +from ap2.types.mandate import CartMandate from ap2.types.payment_request import PAYMENT_METHOD_DATA_DATA_KEY from common.a2a_message_builder import A2aMessageBuilder from common import artifact_utils @@ -41,7 +42,9 @@ async def get_payment_methods( Returns: A dictionary of the user's applicable payment methods. """ - cart_mandate = tool_context.state["cart_mandate"] + cart_mandate_data = tool_context.state["cart_mandate"] + # Reconstruct CartMandate from stored dict + cart_mandate = CartMandate.model_validate(cart_mandate_data) message_builder = ( A2aMessageBuilder() .set_context_id(tool_context.state["shopping_context_id"]) diff --git a/samples/python/src/roles/shopping_agent/subagents/shopper/tools.py b/samples/python/src/roles/shopping_agent/subagents/shopper/tools.py index cee16eff..aff52646 100644 --- a/samples/python/src/roles/shopping_agent/subagents/shopper/tools.py +++ b/samples/python/src/roles/shopping_agent/subagents/shopper/tools.py @@ -65,7 +65,8 @@ def create_intent_mandate( datetime.now(timezone.utc) + timedelta(days=1) ).isoformat(), ) - tool_context.state["intent_mandate"] = intent_mandate + # Store as dict to ensure JSON serialization compatibility with ADK session storage + tool_context.state["intent_mandate"] = intent_mandate.model_dump() return intent_mandate @@ -93,7 +94,7 @@ async def find_products( message = ( A2aMessageBuilder() .add_text("Find products that match the user's IntentMandate.") - .add_data(INTENT_MANDATE_DATA_KEY, intent_mandate.model_dump()) + .add_data(INTENT_MANDATE_DATA_KEY, intent_mandate) .add_data("risk_data", risk_data) .add_data("debug_mode", debug_mode) .add_data("shopping_agent_id", "trusted_shopping_agent") @@ -106,7 +107,8 @@ async def find_products( tool_context.state["shopping_context_id"] = task.context_id cart_mandates = _parse_cart_mandates(task.artifacts) - tool_context.state["cart_mandates"] = cart_mandates + # Store as list of dicts for JSON serialization compatibility + tool_context.state["cart_mandates"] = [cm.model_dump() for cm in cart_mandates] return cart_mandates @@ -117,7 +119,11 @@ def update_chosen_cart_mandate(cart_id: str, tool_context: ToolContext) -> str: cart_id: The ID of the chosen cart. tool_context: The ADK supplied tool context. """ - cart_mandates: list[CartMandate] = tool_context.state.get("cart_mandates", []) + cart_mandates_data = tool_context.state.get("cart_mandates", []) + # Reconstruct CartMandate objects from stored dicts + cart_mandates: list[CartMandate] = [] + for cm in cart_mandates_data: + cart_mandates.append(CartMandate.model_validate(cm)) for cart in cart_mandates: print( f"Checking cart with ID: {cart.contents.id} with chosen ID: {cart_id}" diff --git a/samples/python/src/roles/shopping_agent/tools.py b/samples/python/src/roles/shopping_agent/tools.py index 1d8b7bf1..455194a3 100644 --- a/samples/python/src/roles/shopping_agent/tools.py +++ b/samples/python/src/roles/shopping_agent/tools.py @@ -76,7 +76,8 @@ async def update_cart( _parse_cart_mandates(task.artifacts) ) - tool_context.state["cart_mandate"] = updated_cart_mandate + # Store as dict for JSON serialization compatibility + tool_context.state["cart_mandate"] = updated_cart_mandate.model_dump() tool_context.state["shipping_address"] = shipping_address return updated_cart_mandate @@ -163,7 +164,8 @@ def store_receipt_if_present(task, tool_context: ToolContext) -> None: ) if payment_receipts: payment_receipt = artifact_utils.only(payment_receipts) - tool_context.state["payment_receipt"] = payment_receipt + # Store as dict for JSON serialization compatibility + tool_context.state["payment_receipt"] = payment_receipt.model_dump() def create_payment_mandate( @@ -181,7 +183,9 @@ def create_payment_mandate( Returns: The payment mandate. """ - cart_mandate = tool_context.state["cart_mandate"] + cart_mandate_data = tool_context.state["cart_mandate"] + # Reconstruct CartMandate from stored dict + cart_mandate = CartMandate.model_validate(cart_mandate_data) payment_request = cart_mandate.contents.payment_request shipping_address = tool_context.state["shipping_address"] @@ -215,7 +219,8 @@ def create_payment_mandate( ), ) - tool_context.state["payment_mandate"] = payment_mandate + # Store as dict for JSON serialization compatibility + tool_context.state["payment_mandate"] = payment_mandate.model_dump() return payment_mandate @@ -238,8 +243,11 @@ def sign_mandates_on_user_device(tool_context: ToolContext) -> str: Returns: A string representing the simulated user authorization signature (JWT). """ - payment_mandate: PaymentMandate = tool_context.state["payment_mandate"] - cart_mandate: CartMandate = tool_context.state["cart_mandate"] + payment_mandate_data = tool_context.state["payment_mandate"] + cart_mandate_data = tool_context.state["cart_mandate"] + # Reconstruct Pydantic objects from stored dicts + payment_mandate = PaymentMandate.model_validate(payment_mandate_data) + cart_mandate = CartMandate.model_validate(cart_mandate_data) cart_mandate_hash = _generate_cart_mandate_hash(cart_mandate) payment_mandate_hash = _generate_payment_mandate_hash( payment_mandate.payment_mandate_contents @@ -250,7 +258,8 @@ def sign_mandates_on_user_device(tool_context: ToolContext) -> str: payment_mandate.user_authorization = ( cart_mandate_hash + "_" + payment_mandate_hash ) - tool_context.state["signed_payment_mandate"] = payment_mandate + # Store as dict for JSON serialization compatibility + tool_context.state["signed_payment_mandate"] = payment_mandate.model_dump() return payment_mandate.user_authorization