Skip to content
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
10 changes: 10 additions & 0 deletions src/google/adk/utils/agent_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ class AgentInfo(pydantic.BaseModel):
tools: list[types.Tool]
sub_agents: list[str]

@pydantic.field_validator('instruction', mode='before')
@classmethod
def _coerce_callable_instruction(cls, value: object) -> object:
# LlmAgent.instruction is str | InstructionProvider. Providers may be
# async and may need session state, so app-info must not resolve them.
if callable(value):
name = getattr(value, '__name__', None) or type(value).__name__
return f'<InstructionProvider: {name}>'
return value


async def get_tools_info(tools: list[ToolUnion]) -> list[Any]:
"""Returns the info for a given list of tools."""
Expand Down
55 changes: 55 additions & 0 deletions tests/unittests/utils/test_agent_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,61 @@ async def test_get_agents_dict_single_agent_has_no_sub_agents():
assert agents['root'].tools == []


@pytest.mark.asyncio
async def test_get_agents_dict_coerces_callable_instruction():
def dynamic_instruction(ctx: ReadonlyContext) -> str:
raise RuntimeError('app-info must not resolve InstructionProvider')

agent = LlmAgent(
name='dyn',
description='Agent with a dynamic (callable) instruction.',
instruction=dynamic_instruction,
)

agents = await get_agents_dict(agent)

assert agents['dyn'].instruction == (
'<InstructionProvider: dynamic_instruction>'
)


@pytest.mark.asyncio
async def test_get_agents_dict_coerces_async_and_subagent_callables():
async def async_instruction(ctx: ReadonlyContext) -> str:
return 'async'

def child_instruction(ctx: ReadonlyContext) -> str:
return 'child'

child = LlmAgent(name='child', instruction=child_instruction)
root = LlmAgent(
name='root', instruction=async_instruction, sub_agents=[child]
)

agents = await get_agents_dict(root)

assert agents['root'].instruction == (
'<InstructionProvider: async_instruction>'
)
assert agents['child'].instruction == (
'<InstructionProvider: child_instruction>'
)


@pytest.mark.asyncio
async def test_get_agents_dict_coerces_instruction_provider_instance():
class Persona:

def __call__(self, ctx: ReadonlyContext) -> str:
return 'persona'

agent = LlmAgent(name='dyn', instruction=Persona())

agents = await get_agents_dict(agent)

assert agents['dyn'].instruction == '<InstructionProvider: Persona>'


@pytest.mark.asyncio
async def test_get_agents_dict_includes_transitively_nested_agents():
grandchild = LlmAgent(name='grandchild')
Expand Down