fix: stop logging the full live LlmRequest in base_llm_flow

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 959935395
This commit is contained in:
George Weale
2026-08-05 16:32:31 -07:00
committed by Copybara-Service
parent 73e862546c
commit 6fb5e04fcd
2 changed files with 61 additions and 2 deletions
@@ -591,10 +591,15 @@ class BaseLlmFlow(ABC):
llm_request.model = agent.canonical_live_model.model
llm = self.__get_llm(invocation_context)
# Only log non-sensitive request metadata. The full request carries the
# user conversation and http_options.headers, which may hold credentials.
logger.debug(
'Establishing live connection for agent: %s with llm request: %s',
'Establishing live connection for agent: %s, model: %s, contents: %s,'
' response modalities: %s',
agent.name,
llm_request,
llm_request.model,
len(llm_request.contents),
llm_request.live_connect_config.response_modalities,
)
attempt = 1
@@ -15,6 +15,7 @@
"""Unit tests for BaseLlmFlow toolset integration."""
import asyncio
import logging
from unittest import mock
from unittest.mock import AsyncMock
@@ -930,6 +931,59 @@ async def test_run_live_skips_send_history_on_resumption():
mock_connection.send_history.assert_not_called()
@pytest.mark.asyncio
async def test_run_live_does_not_log_http_options_headers(caplog):
"""run_live must not log http_options headers, which can carry secrets."""
sentinel = 'do-not-log-this-live-credential'
agent = Agent(name='test_agent', model=Gemini())
invocation_context = await testing_utils.create_invocation_context(
agent=agent,
run_config=RunConfig(
http_options=types.HttpOptions(
headers={'Authorization': f'Bearer {sentinel}'}
)
),
)
invocation_context.live_request_queue = LiveRequestQueue()
flow = BaseLlmFlowForTesting()
# We need a way to break the infinite loop in run_live for testing.
class StopError(Exception):
pass
async def mock_receive():
if False: # Makes this function an async generator.
yield
raise StopError('stop')
mock_connection = mock.AsyncMock()
mock_connection.receive = mock.Mock(side_effect=mock_receive)
with caplog.at_level(logging.DEBUG, logger='google_adk'):
with mock.patch.object(flow, '_send_to_model', new_callable=AsyncMock):
with mock.patch(
'google.adk.models.google_llm.Gemini.connect'
) as mock_connect:
mock_connect.return_value.__aenter__.return_value = mock_connection
try:
async for _ in flow.run_live(invocation_context):
pass
except StopError:
pass
# The request headers reached the flow, so the log line had access to them.
assert (
invocation_context.run_config.http_options.headers['Authorization']
== f'Bearer {sentinel}'
)
assert sentinel not in caplog.text
# The log line is still there and still useful.
assert 'Establishing live connection for agent: test_agent' in caplog.text
@pytest.mark.asyncio
async def test_live_session_resumption_go_away():
"""Test that go_away triggers reconnection."""