Skip to content

Fix: Handle Pydantic v2 ValidationError in AgentLoader #1245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions src/google/adk/cli/utils/agent_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,7 @@ def _load_from_module_or_package(
e.msg = f"Fail to load '{agent_name}' module. " + e.msg
raise e
except Exception as e:
if hasattr(e, "msg"):
e.msg = f"Fail to load '{agent_name}' module. " + e.msg
raise e
e.args = (
f"Fail to load '{agent_name}' module. {e.args[0] if e.args else ''}",
) + e.args[1:]
raise e
raise RuntimeError(f"Fail to load '{agent_name}' module. {str(e)}") from e

return None

Expand Down Expand Up @@ -115,16 +109,9 @@ def _load_from_submodule(self, agent_name: str) -> Optional[BaseAgent]:
e.msg = f"Fail to load '{agent_name}.agent' module. " + e.msg
raise e
except Exception as e:
if hasattr(e, "msg"):
e.msg = f"Fail to load '{agent_name}.agent' module. " + e.msg
raise e
e.args = (
(
f"Fail to load '{agent_name}.agent' module."
f" {e.args[0] if e.args else ''}"
),
) + e.args[1:]
raise e
raise RuntimeError(
f"Fail to load '{agent_name}.agent' module. {str(e)}"
) from e

return None

Expand Down
55 changes: 43 additions & 12 deletions tests/unittests/cli/utils/test_agent_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,8 @@ def __init__(self):
"""))

loader = AgentLoader(str(temp_path))
# SyntaxError is a subclass of Exception, and importlib might wrap it
# The loader is expected to prepend its message and re-raise.
with pytest.raises(
SyntaxError
) as exc_info: # Or potentially ImportError depending on Python version specifics with importlib
# SyntaxError is now wrapped in RuntimeError by the loader
with pytest.raises(RuntimeError) as exc_info:
loader.load_agent(agent_name)

assert str(exc_info.value).startswith(
Expand All @@ -387,7 +384,7 @@ def __init__(self):
assert "invalid syntax" in str(exc_info.value).lower()

def test_agent_internal_name_error(self):
"""Test other import errors within an agent's code (e.g., SyntaxError)."""
"""Test other import errors within an agent's code (e.g., NameError)."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
agent_name = "name_error_agent"
Expand All @@ -408,17 +405,14 @@ def __init__(self):
"""))

loader = AgentLoader(str(temp_path))
# SyntaxError is a subclass of Exception, and importlib might wrap it
# The loader is expected to prepend its message and re-raise.
with pytest.raises(
NameError
) as exc_info: # Or potentially ImportError depending on Python version specifics with importlib
# NameError is now wrapped in RuntimeError by the loader
with pytest.raises(RuntimeError) as exc_info:
loader.load_agent(agent_name)

assert str(exc_info.value).startswith(
f"Fail to load '{agent_name}' module."
)
# Check for part of the original SyntaxError message
# Check for part of the original NameError message
assert "is not defined" in str(exc_info.value).lower()

def test_sys_path_modification(self):
Expand All @@ -443,3 +437,40 @@ def test_sys_path_modification(self):
# Now assert path was added
assert str(temp_path) in sys.path
assert agent.name == "path_agent"

def test_load_agent_with_pydantic_v2_validation_error(self):
"""Test that a Pydantic v2 style ValidationError is handled correctly."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
agent_name = "pydantic_v2_error_agent"

# Create a mock ValidationError class
# No need to define it here, it's defined in the agent code string

# Create agent file that raises this mock error
agent_file = temp_path / f"{agent_name}.py"
agent_file.write_text(dedent(f"""
# This import is just to make it a valid module for the loader
from google.adk.agents.base_agent import BaseAgent

class MockValidationError(Exception):
def __init__(self, message):
super().__init__(message)
# Intentionally do not set a 'msg' attribute

# Raise the error when this module is imported
raise MockValidationError("This is a Pydantic v2 style error")

# The rest of the agent definition is not reached
class {agent_name.title().replace("_", "")}Agent(BaseAgent):
def __init__(self):
super().__init__(name="{agent_name}")
root_agent = {agent_name.title().replace("_", "")}Agent()
"""))

loader = AgentLoader(str(temp_path))
with pytest.raises(RuntimeError) as exc_info:
loader.load_agent(agent_name)

assert str(exc_info.value).startswith(f"Fail to load '{agent_name}' module.")
assert "This is a Pydantic v2 style error" in str(exc_info.value)
Loading