Skip to content

Commit de87480

Browse files
fix(adk-middleware): use O(1) lookup in /agents/state when use_thread_id_as_session_id is enabled (#1383)
* fix(adk-middleware): use O(1) lookup in /agents/state when use_thread_id_as_session_id is enabled The /agents/state endpoint always used the O(n) _find_session_by_thread_id scan path on cache miss, even when use_thread_id_as_session_id=True. This adds a direct get_session(session_id=thread_id) lookup first when the flag is enabled, falling back to the scan only for legacy sessions. Fixes #1243 https://claude.ai/code/session_01A1RL4QvwUhWNHocM7H4xkH * fix(adk-middleware): use getattr with strict True check for _use_thread_id_as_session_id Use `getattr(..., False) is True` instead of direct attribute access so that MagicMock-based test fixtures (which return truthy mocks for any attribute) correctly fall through to the scan path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8a18915 commit de87480

2 files changed

Lines changed: 109 additions & 14 deletions

File tree

integrations/adk-middleware/python/src/ag_ui_adk/endpoint.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,22 +237,34 @@ async def agents_state_endpoint(request_data: AgentStateRequest):
237237

238238
# Cache miss - search backend by thread_id
239239
if not session:
240-
session = await agent._session_manager._find_session_by_thread_id(
241-
app_name=app_name,
242-
user_id=user_id,
243-
thread_id=thread_id
244-
)
245-
if session:
246-
# Found - cache for future lookups
247-
session_id = session.id
248-
agent._session_lookup_cache[(thread_id, user_id)] = (session_id, app_name, user_id)
249-
250-
# Reload session to populate events (list_sessions returns metadata only)
251-
session = await agent._session_manager._session_service.get_session(
252-
session_id=session_id,
240+
# O(1) direct lookup when use_thread_id_as_session_id is enabled
241+
if getattr(agent._session_manager, '_use_thread_id_as_session_id', False) is True:
242+
session = await agent._session_manager.get_session(
243+
thread_id, app_name, user_id
244+
)
245+
if session:
246+
session_id = session.id
247+
agent._session_lookup_cache[(thread_id, user_id)] = (session_id, app_name, user_id)
248+
249+
# Fallback to O(n) scan (always used when flag is False,
250+
# also used as legacy fallback when flag is True but direct lookup misses)
251+
if not session:
252+
session = await agent._session_manager._find_session_by_thread_id(
253253
app_name=app_name,
254-
user_id=user_id
254+
user_id=user_id,
255+
thread_id=thread_id
255256
)
257+
if session:
258+
# Found - cache for future lookups
259+
session_id = session.id
260+
agent._session_lookup_cache[(thread_id, user_id)] = (session_id, app_name, user_id)
261+
262+
# Reload session to populate events (list_sessions returns metadata only)
263+
session = await agent._session_manager._session_service.get_session(
264+
session_id=session_id,
265+
app_name=app_name,
266+
user_id=user_id
267+
)
256268

257269
thread_exists = session is not None
258270

integrations/adk-middleware/python/tests/test_use_thread_id_as_session_id.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,86 @@ async def test_parameter_defaults_to_false(self):
355355
user_id="user",
356356
)
357357
assert adk._session_manager._use_thread_id_as_session_id is False
358+
359+
360+
class TestAgentsStateEndpointWithDirectLookup:
361+
"""Tests for /agents/state endpoint with use_thread_id_as_session_id=True."""
362+
363+
@pytest.fixture(autouse=True)
364+
def reset_session_manager(self):
365+
SessionManager.reset_instance()
366+
yield
367+
SessionManager.reset_instance()
368+
369+
@pytest.fixture
370+
def mock_agent(self):
371+
agent = Mock(spec=Agent)
372+
agent.name = "test_agent"
373+
agent.instruction = "Test instruction"
374+
agent.tools = []
375+
return agent
376+
377+
@pytest.fixture
378+
def adk_agent(self, mock_agent):
379+
return ADKAgent(
380+
adk_agent=mock_agent,
381+
app_name="test_app",
382+
user_id="test_user",
383+
use_in_memory_services=True,
384+
use_thread_id_as_session_id=True,
385+
)
386+
387+
@pytest.fixture
388+
def app(self, adk_agent):
389+
from fastapi import FastAPI
390+
from ag_ui_adk import add_adk_fastapi_endpoint
391+
app = FastAPI()
392+
add_adk_fastapi_endpoint(app, adk_agent)
393+
return app
394+
395+
@pytest.fixture
396+
def client(self, app):
397+
from starlette.testclient import TestClient
398+
return TestClient(app)
399+
400+
@pytest.mark.asyncio
401+
async def test_agents_state_uses_direct_lookup(self, adk_agent, client):
402+
"""When use_thread_id_as_session_id=True, /agents/state uses O(1) lookup."""
403+
# Create a session first via the session manager
404+
session, sid = await adk_agent._session_manager.get_or_create_session(
405+
thread_id="state-thread-123",
406+
app_name="test_app",
407+
user_id="test_user",
408+
)
409+
assert sid == "state-thread-123"
410+
411+
# Ensure the cache is clear so endpoint must look up from backend
412+
adk_agent._session_lookup_cache.clear()
413+
414+
# Spy on list_sessions to verify it's NOT called
415+
with patch.object(
416+
adk_agent._session_manager._session_service,
417+
"list_sessions",
418+
wraps=adk_agent._session_manager._session_service.list_sessions,
419+
) as spy:
420+
response = client.post(
421+
"/agents/state",
422+
json={"threadId": "state-thread-123"},
423+
)
424+
assert response.status_code == 200
425+
data = response.json()
426+
assert data["threadExists"] is True
427+
assert data["threadId"] == "state-thread-123"
428+
# The key assertion: list_sessions should NOT be called
429+
spy.assert_not_called()
430+
431+
@pytest.mark.asyncio
432+
async def test_agents_state_nonexistent_thread(self, adk_agent, client):
433+
"""/agents/state returns threadExists=False for unknown thread."""
434+
response = client.post(
435+
"/agents/state",
436+
json={"threadId": "nonexistent-thread"},
437+
)
438+
assert response.status_code == 200
439+
data = response.json()
440+
assert data["threadExists"] is False

0 commit comments

Comments
 (0)