From 13cba14b39e87621ab08a79fb742bbec8fd7ab0b Mon Sep 17 00:00:00 2001 From: zb-zhoufengen Date: Fri, 15 May 2026 16:52:10 +0800 Subject: [PATCH] fix: return literal text for unresolved session state variables instead of KeyError inject_session_state raises KeyError when instruction text contains literal {identifier} patterns (e.g., documentation describing interpolation syntax like ${expression}). This crashes agents that embed third-party tool descriptions, JSON schemas, or documentation examples into instruction strings. Fix by returning match.group() as-is when a valid identifier is not found in session state, with a debug log. This matches the existing behavior for invalid identifier names. --- src/google/adk/utils/instructions_utils.py | 6 ++++- .../utils/test_instructions_utils.py | 26 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/google/adk/utils/instructions_utils.py b/src/google/adk/utils/instructions_utils.py index 0dfe0a2b7f..326e92de5a 100644 --- a/src/google/adk/utils/instructions_utils.py +++ b/src/google/adk/utils/instructions_utils.py @@ -119,7 +119,11 @@ async def _replace_match(match) -> str: ) return '' else: - raise KeyError(f'Context variable not found: `{var_name}`.') + logger.debug( + 'Context variable %s not found in session state, returning as-is', + var_name, + ) + return match.group() return await _async_sub(r'{+[^{}]*}+', _replace_match, template) diff --git a/tests/unittests/utils/test_instructions_utils.py b/tests/unittests/utils/test_instructions_utils.py index 9e176241bc..fc58f73211 100644 --- a/tests/unittests/utils/test_instructions_utils.py +++ b/tests/unittests/utils/test_instructions_utils.py @@ -103,18 +103,30 @@ async def test_inject_session_state_with_optional_state(): @pytest.mark.asyncio -async def test_inject_session_state_with_missing_state_raises_key_error(): +async def test_inject_session_state_with_missing_state_returns_literal(): instruction_template = "Hello {missing_key}!" invocation_context = await _create_test_readonly_context( state={"user_name": "Foo"} ) - with pytest.raises( - KeyError, match="Context variable not found: `missing_key`." - ): - await instructions_utils.inject_session_state( - instruction_template, invocation_context - ) + populated_instruction = await instructions_utils.inject_session_state( + instruction_template, invocation_context + ) + assert populated_instruction == "Hello {missing_key}!" + + +@pytest.mark.asyncio +async def test_inject_session_state_preserves_literal_identifier_patterns(): + """Literal {identifier} patterns in docs/tool descriptions should not crash.""" + instruction_template = ( + "The formatString supports interpolation via {expression} syntax." + ) + invocation_context = await _create_test_readonly_context() + + populated_instruction = await instructions_utils.inject_session_state( + instruction_template, invocation_context + ) + assert populated_instruction == instruction_template @pytest.mark.asyncio