Skip to content

Conversation

@faizan842
Copy link

Description

This PR adds a new sample agent (samples/agent/langgraph/restaurant_finder) implemented using LangGraph.

This sample demonstrates how to build A2UI-compliant applications using the LangGraph framework, replicating the functionality of the existing ADK restaurant finder. It provides a reference architecture for handling the A2UI protocol (UI generation, event handling, and data updates) within a LangGraph state machine.

Key improvements included in this implementation:

  • Robust JSON Handling: Includes validation logic to auto-repair common LLM syntax errors (e.g. unquoted keys).
  • Correct Event Parsing: Updates the executor logic to correctly handle book_restaurant and submit_booking events.
  • Stateless Design: Configured without a checkpointer to handle independent A2A server requests cleanly.

Pre-launch Checklist

  • I signed the CLA.
  • I read the Contributors Guide.
  • I read the Style Guide.
  • I have added updates to the CHANGELOG.
  • I updated/added relevant documentation (Added README.md for the sample).
  • My code changes (if any) have tests (Manually verified determinism and logic).

If you need help, consider asking for advice on the discussion board.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new sample agent for a restaurant finder using LangGraph. The implementation is comprehensive, covering the agent logic, server setup, and UI examples. My review focuses on improving code quality, fixing a critical issue with a hardcoded URL that would affect functionality, and ensuring the sample is easy for developers to use. I've provided suggestions for refactoring to handle state correctly, cleaning up unused code and developer comments, and aligning the project's configuration with its documentation.

Comment on lines 54 to 64
# Create a simple mock context that mimics what the tool expects
# In a real app we might want to pass the real context if we had one
# For now, we hardcode localhost or inject from somewhere if needed,
# but the tool just uses it for replacing URL in data.
# We can try to get base_url from environment or defaults.
base_url = "http://localhost:10002"
# Ideally should come from config, but inside a tool we don't have easy access to state unless we bind it.
# We can rely on default logic inside tool or pass valid one.

ctx = MockToolContext(base_url)
return get_restaurants(cuisine=cuisine or "", location=location or "", tool_context=ctx, count=count)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The base_url is hardcoded within the search_restaurants tool. This will cause incorrect image URLs to be generated if the server is run on a different host or port. The base_url is available in the AgentState, but the tool, being globally defined, cannot access it.

To fix this, you should refactor the graph and tool creation to be stateful, for instance, by moving them inside the RestaurantAgent class. This would allow the tool to be created with access to the base_url from the class instance.

from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from a2ui.a2ui_extension import get_a2ui_agent_extension
from agent_executor import RestaurantAgentExecutor
from agent import RestaurantAgent # To get SUPPORTED_CONTENT_TYPES ideally, but we can hardcode for now or add it to class
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import RestaurantAgent is not used in this file and should be removed. The associated comment appears to be a developer note that is no longer relevant.

Additionally, there are other developer comments that should be cleaned up to improve code clarity:

  • Lines 66-68: Comment about hardcoding SUPPORTED_CONTENT_TYPES.
  • Lines 103-105: Comment about the images directory.

Comment on lines +15 to +28
import json
import logging
import os
from typing import Annotated, Any, Dict, List, TypedDict

import jsonschema
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.runnables import RunnableConfig
from langgraph.graph import END, StateGraph, START
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This file contains several unused imports that should be removed for code clarity: os, Any, Dict, ToolMessage, ChatPromptTemplate, MessagesPlaceholder, and RunnableConfig. Additionally, MemorySaver is imported on line 192 but is also unused.

Suggested change
import json
import logging
import os
from typing import Annotated, Any, Dict, List, TypedDict
import jsonschema
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.runnables import RunnableConfig
from langgraph.graph import END, StateGraph, START
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
import json
import logging
from typing import Annotated, List, TypedDict
import jsonschema
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
from langchain_core.tools import tool
from langchain_google_genai import ChatGoogleGenerativeAI
from langgraph.graph import END, StateGraph, START
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode

return {"error_message": "Empty JSON part."}

# Attempt to repair common JSON errors (like unquoted keys)
import re
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import re statement is inside the validate_response function. According to the PEP 8 style guide, imports should be at the top of the file. Please move this import to the top-level of the module.

version = "0.1.0"
description = "Sample LangGraph-based Restaurant finder agent that uses a2ui UI."
readme = "README.md"
requires-python = ">=3.13"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The requires-python is set to >=3.13, but the README.md for this sample specifies Python 3.9 or higher. These should be consistent to avoid confusion for users trying to run the sample. Given that Python 3.13 is a very recent release, you might want to verify if the code can run on an earlier, more common version (like 3.9+) and update this requirement accordingly for broader compatibility.

logger = logging.getLogger(__name__)


def get_restaurants(cuisine: str, location: str, tool_context: ToolContext, count: int = 5) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The get_restaurants function's tool_context: ToolContext parameter introduces an unnecessary dependency on the ADK (google.adk.tools.tool_context). This requires creating a MockToolContext in agent.py just to pass the base_url.

To simplify and make the code more self-contained within the LangGraph context, consider passing base_url directly as a string argument. This would also require updating the implementation to use the base_url string directly instead of tool_context.state.get("base_url").

Suggested change
def get_restaurants(cuisine: str, location: str, tool_context: ToolContext, count: int = 5) -> str:
def get_restaurants(cuisine: str, location: str, base_url: str, count: int = 5) -> str:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant