fix: resolve google_search tool and artifact loading issues in home renovation agent

Fixes #409

## Changes

### agent.py
- Replace singleton `google_search` with explicit `GoogleSearchTool(bypass_multi_tools_limit=True)`
  to enable compatibility with nested agent setups (ADK limitation workaround)
- Update SearchAgent instruction to be clearer about how the built-in search works
  (it's automatic, not a callable function)

### tools.py
- Add validation for common LLM hallucination patterns (e.g., `_v0` version numbers)
- Add fallback logic when artifact loading fails:
  1. Try corrected filename if version 0 detected
  2. Look up known artifact versions in session state
  3. Fall back to last generated rendering
- Improve error messages to show available artifacts

## Root Cause Analysis

1. **google_search not found**: The LLM was trying to call 'google_search' as a
   function, but GoogleSearchTool is a Gemini built-in capability that works
   automatically. Using `bypass_multi_tools_limit=True` and clearer instructions
   resolves the confusion.

2. **Artifact not found**: The LLM sometimes hallucinated incorrect filenames
   (e.g., `_v0.png` instead of `_v1.png`). Added robust fallback logic to
   recover gracefully.
This commit is contained in:
awesomekoder
2026-01-31 20:24:35 -08:00
parent 50310fca2b
commit 62c52a6b63
2 changed files with 54 additions and 8 deletions

View File

@@ -10,7 +10,7 @@ Pattern Reference: https://google.github.io/adk-docs/agents/multi-agents/#coordi
"""
from google.adk.agents import LlmAgent, SequentialAgent
from google.adk.tools import google_search
from google.adk.tools import GoogleSearchTool
from google.adk.tools.agent_tool import AgentTool
from .tools import (
generate_renovation_rendering,
@@ -21,15 +21,20 @@ from .tools import (
# ============================================================================
# Helper Tool Agent (wraps google_search)
# Helper Tool Agent (wraps GoogleSearchTool)
# ============================================================================
# Use bypass_multi_tools_limit=True to allow GoogleSearchTool in nested agent setups
google_search_tool = GoogleSearchTool(bypass_multi_tools_limit=True)
search_agent = LlmAgent(
name="SearchAgent",
model="gemini-3-flash-preview",
description="Searches for renovation costs, contractors, materials, and design trends",
instruction="Use google_search to find current renovation information, costs, materials, and trends. Be concise and cite sources.",
tools=[google_search],
instruction="""You are a search specialist. When asked to find information about renovation costs,
contractors, materials, or design trends, the search capability is automatically enabled.
Simply respond with the information you find. Be concise and cite sources when available.""",
tools=[google_search_tool],
)

View File

@@ -305,18 +305,59 @@ async def edit_renovation_rendering(tool_context: ToolContext, inputs: EditRenov
if not artifact_filename:
artifact_filename = tool_context.state.get("last_generated_rendering")
if not artifact_filename:
return "❌ No artifact_filename provided and no previous rendering found in session. Please generate a rendering first or specify the artifact filename."
return "❌ No artifact_filename provided and no previous rendering found in session. Please generate a rendering first using generate_renovation_rendering."
logger.info(f"Using last generated rendering from session: {artifact_filename}")
# Validate filename format - check for common hallucination patterns
if "_v0." in artifact_filename:
# Version 0 doesn't exist - the first version is always v1
logger.warning(f"Invalid version v0 detected in filename: {artifact_filename}")
corrected_filename = artifact_filename.replace("_v0.", "_v1.")
logger.info(f"Attempting corrected filename: {corrected_filename}")
artifact_filename = corrected_filename
# Load the existing rendering
logger.info(f"Loading artifact: {artifact_filename}")
loaded_image_part = None
try:
loaded_image_part = await tool_context.load_artifact(artifact_filename)
if not loaded_image_part:
return f"❌ Could not find rendering artifact: {artifact_filename}"
except Exception as e:
logger.error(f"Error loading artifact: {e}")
return f"Error loading rendering artifact: {e}"
# If loading failed, try to find the most recent version of this asset
if not loaded_image_part:
# Extract base asset name and try to find any existing version
base_name = artifact_filename.split('_v')[0] if '_v' in artifact_filename else artifact_filename.replace('.png', '')
asset_filenames = tool_context.state.get("asset_filenames", {})
# Check if we have any version of this asset
if base_name in asset_filenames:
fallback_filename = asset_filenames[base_name]
logger.info(f"Attempting fallback to known artifact: {fallback_filename}")
try:
loaded_image_part = await tool_context.load_artifact(fallback_filename)
if loaded_image_part:
artifact_filename = fallback_filename
logger.info(f"Successfully loaded fallback artifact: {fallback_filename}")
except Exception as e:
logger.error(f"Fallback load also failed: {e}")
# Last resort: try the last generated rendering
if not loaded_image_part:
last_rendering = tool_context.state.get("last_generated_rendering")
if last_rendering and last_rendering != artifact_filename:
logger.info(f"Attempting last resort fallback to: {last_rendering}")
try:
loaded_image_part = await tool_context.load_artifact(last_rendering)
if loaded_image_part:
artifact_filename = last_rendering
logger.info(f"Successfully loaded last resort artifact: {last_rendering}")
except Exception as e:
logger.error(f"Last resort load also failed: {e}")
if not loaded_image_part:
available_renderings = get_asset_versions_info(tool_context)
return f"❌ Could not find rendering artifact: {inputs.artifact_filename}\n\n{available_renderings}\n\nPlease use one of the available artifact filenames, or generate a new rendering first."
# Handle reference image if specified
reference_image_part = None