Skip to content

Commit a61605a

Browse files
fix: restore LRO routing guard and streaming tests
1 parent 769ff0f commit a61605a

File tree

3 files changed

+92
-49
lines changed

3 files changed

+92
-49
lines changed

typescript-sdk/integrations/adk-middleware/python/src/ag_ui_adk/__init__.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,64 @@
55
This middleware enables Google ADK agents to be used with the AG-UI protocol.
66
"""
77

8+
from __future__ import annotations
9+
10+
import logging
11+
import os
12+
from typing import Dict, Iterable
13+
814
from .adk_agent import ADKAgent
915
from .event_translator import EventTranslator
1016
from .session_manager import SessionManager
1117
from .endpoint import add_adk_fastapi_endpoint, create_adk_app
1218

1319
__all__ = ['ADKAgent', 'add_adk_fastapi_endpoint', 'create_adk_app', 'EventTranslator', 'SessionManager']
1420

15-
__version__ = "0.1.0"
21+
__version__ = "0.1.0"
22+
23+
24+
def _configure_logging_from_env() -> None:
25+
"""Configure component loggers based on environment variables."""
26+
27+
root_level = os.getenv('LOG_ROOT_LEVEL')
28+
if root_level:
29+
try:
30+
level = getattr(logging, root_level.upper())
31+
except AttributeError:
32+
logging.getLogger(__name__).warning(
33+
"Invalid LOG_ROOT_LEVEL value '%s'", root_level
34+
)
35+
else:
36+
logging.basicConfig(level=level, force=True)
37+
38+
component_levels: Dict[str, Iterable[str]] = {
39+
'LOG_ADK_AGENT': ('ag_ui_adk.adk_agent',),
40+
'LOG_EVENT_TRANSLATOR': (
41+
'ag_ui_adk.event_translator',
42+
'event_translator',
43+
),
44+
'LOG_ENDPOINT': ('ag_ui_adk.endpoint', 'endpoint'),
45+
'LOG_SESSION_MANAGER': (
46+
'ag_ui_adk.session_manager',
47+
'session_manager',
48+
),
49+
}
50+
51+
for env_var, logger_names in component_levels.items():
52+
level_name = os.getenv(env_var)
53+
if not level_name:
54+
continue
55+
56+
try:
57+
level = getattr(logging, level_name.upper())
58+
except AttributeError:
59+
logging.getLogger(__name__).warning(
60+
"Invalid value '%s' for %s", level_name, env_var
61+
)
62+
continue
63+
64+
for logger_name in logger_names:
65+
logging.getLogger(logger_name).setLevel(level)
66+
67+
68+
_configure_logging_from_env()

typescript-sdk/integrations/adk-middleware/python/src/ag_ui_adk/event_translator.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,13 @@ async def _translate_text_content(
172172
elif hasattr(adk_event, 'is_final_response'):
173173
is_final_response = adk_event.is_final_response
174174

175-
# Handle None values: if is_final_response=True, it means streaming should end
176-
# Also check for finish_reason as a fallback indicator that streaming is complete
175+
# Handle None values: if a turn is complete or a final chunk arrives, end streaming
177176
has_finish_reason = bool(getattr(adk_event, 'finish_reason', None))
178-
should_send_end = (is_final_response and not is_partial) or (has_finish_reason and self._is_streaming)
177+
should_send_end = (
178+
(turn_complete and not is_partial)
179+
or (is_final_response and not is_partial)
180+
or (has_finish_reason and self._is_streaming)
181+
)
179182

180183
logger.info(f"📥 Text event - partial={is_partial}, turn_complete={turn_complete}, "
181184
f"is_final_response={is_final_response}, has_finish_reason={has_finish_reason}, "

typescript-sdk/integrations/adk-middleware/python/tests/test_event_translator_comprehensive.py

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,7 @@ async def test_translate_event_with_empty_parts(self, translator, mock_adk_event
9090

9191
@pytest.mark.asyncio
9292
async def test_translate_function_calls_detection(self, translator, mock_adk_event):
93-
"""Test function calls detection by asserting tool events are emitted.
94-
95-
This avoids brittle checks on debug log wording and instead
96-
verifies that function calls result in ToolCall events.
97-
"""
98-
# Mock event with one function call
93+
"""Test that function calls produce ToolCall events."""
9994
mock_function_call = MagicMock()
10095
mock_function_call.name = "test_function"
10196
mock_function_call.id = "call_123"
@@ -106,17 +101,10 @@ async def test_translate_function_calls_detection(self, translator, mock_adk_eve
106101
async for event in translator.translate(mock_adk_event, "thread_1", "run_1"):
107102
events.append(event)
108103

109-
# Expect a START, ARGS, END sequence for the function call
110-
types = [e.type for e in events]
111-
type_names = [str(t).split('.')[-1] for t in types]
112-
assert type_names == [
113-
"TOOL_CALL_START",
114-
"TOOL_CALL_ARGS",
115-
"TOOL_CALL_END",
116-
]
117-
# And the tool_call_id should match the mocked id
118-
ids = [getattr(e, 'tool_call_id', None) for e in events]
119-
assert all(tc_id == "call_123" for tc_id in ids)
104+
type_names = [str(event.type).split('.')[-1] for event in events]
105+
assert type_names == ["TOOL_CALL_START", "TOOL_CALL_ARGS", "TOOL_CALL_END"]
106+
ids = [getattr(event, 'tool_call_id', None) for event in events]
107+
assert ids == ["call_123", "call_123", "call_123"]
120108

121109
@pytest.mark.asyncio
122110
async def test_translate_function_responses_handling(self, translator, mock_adk_event):
@@ -195,7 +183,7 @@ async def test_translate_text_content_basic(self, translator, mock_adk_event_wit
195183
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
196184
events.append(event)
197185

198-
assert len(events) == 3 # START, CONTENT, END
186+
assert len(events) == 3 # START, CONTENT , END
199187
assert isinstance(events[0], TextMessageStartEvent)
200188
assert isinstance(events[1], TextMessageContentEvent)
201189
assert isinstance(events[2], TextMessageEndEvent)
@@ -222,10 +210,9 @@ async def test_translate_text_content_multiple_parts(self, translator, mock_adk_
222210
async for event in translator.translate(mock_adk_event, "thread_1", "run_1"):
223211
events.append(event)
224212

225-
assert len(events) == 3 # START, CONTENT, END
213+
assert len(events) == 3 # START, CONTENT , END
226214
assert isinstance(events[1], TextMessageContentEvent)
227215
assert events[1].delta == "First partSecond part" # Joined without newlines
228-
assert isinstance(events[2], TextMessageEndEvent)
229216

230217
@pytest.mark.asyncio
231218
async def test_translate_text_content_partial_streaming(self, translator, mock_adk_event_with_content):
@@ -237,6 +224,10 @@ async def test_translate_text_content_partial_streaming(self, translator, mock_a
237224
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
238225
events.append(event)
239226

227+
# The translator keeps streaming open; forcing a close should yield END
228+
async for event in translator.force_close_streaming_message():
229+
events.append(event)
230+
240231
assert len(events) == 3 # START, CONTENT, END (forced close)
241232
assert isinstance(events[0], TextMessageStartEvent)
242233
assert isinstance(events[1], TextMessageContentEvent)
@@ -306,7 +297,7 @@ async def test_translate_text_content_final_response_from_agent_callback(self, t
306297
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
307298
events.append(event)
308299

309-
assert len(events) == 3 # START, CONTENT, END (non-streaming final response)
300+
assert len(events) == 3 # START, CONTENT , END
310301
assert isinstance(events[0], TextMessageStartEvent)
311302
assert isinstance(events[1], TextMessageContentEvent)
312303
assert events[1].delta == mock_adk_event_with_content.content.parts[0].text
@@ -362,9 +353,8 @@ async def test_translate_text_content_mixed_text_parts(self, translator, mock_ad
362353
async for event in translator.translate(mock_adk_event, "thread_1", "run_1"):
363354
events.append(event)
364355

365-
assert len(events) == 3 # START, CONTENT, END
356+
assert len(events) == 3 # START, CONTENT , END
366357
assert events[1].delta == "Valid textMore text"
367-
assert isinstance(events[2], TextMessageEndEvent)
368358

369359
@pytest.mark.asyncio
370360
async def test_translate_function_calls_basic(self, translator, mock_adk_event):
@@ -650,24 +640,21 @@ async def test_streaming_state_management(self, translator, mock_adk_event_with_
650640
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
651641
events1.append(event)
652642

653-
assert len(events1) == 3 # START, CONTENT, END (forced close at translate completion)
643+
assert len(events1) == 3 # START, CONTENT, END
654644
message_id = events1[0].message_id
655645

656-
# Stream is closed after forced END
646+
# streaming is stoped after TextMessageEndEvent
657647
assert translator._is_streaming is False
658-
assert translator._streaming_message_id is None
648+
# since the streaming is stopped
649+
assert translator._streaming_message_id == None
659650

660-
# Second event should start a new stream with a new message ID
651+
# Second event should continue streaming (same message ID)
661652
events2 = []
662653
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
663654
events2.append(event)
664655

665-
assert len(events2) == 3 # START, CONTENT, END
666-
assert isinstance(events2[0], TextMessageStartEvent)
667-
assert events2[0].message_id != message_id
668-
assert isinstance(events2[2], TextMessageEndEvent)
669-
assert translator._is_streaming is False
670-
assert translator._streaming_message_id is None
656+
assert len(events2) == 3 # New Streaming (START , CONTENT ,END)
657+
assert events2[0].message_id != message_id # Same message ID
671658

672659
@pytest.mark.asyncio
673660
async def test_complex_event_with_multiple_features(self, translator, mock_adk_event):
@@ -691,8 +678,8 @@ async def test_complex_event_with_multiple_features(self, translator, mock_adk_e
691678
async for event in translator.translate(mock_adk_event, "thread_1", "run_1"):
692679
events.append(event)
693680

694-
# Should have text events, state delta, custom event, and END
695-
assert len(events) == 5 # START, CONTENT, STATE_DELTA, CUSTOM, END
681+
# Should have text events, state delta, and custom event
682+
assert len(events) == 5 # START, CONTENT, STATE_DELTA, CUSTOM , END
696683

697684
# Check event types
698685
event_types = [type(event) for event in events]
@@ -771,8 +758,8 @@ async def test_partial_streaming_continuation(self, translator, mock_adk_event_w
771758
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
772759
events1.append(event)
773760

774-
assert len(events1) == 3 # START, CONTENT, END (forced close)
775-
assert translator._is_streaming is False
761+
assert len(events1) == 2 # START, CONTENT (stream remains open)
762+
assert translator._is_streaming is True
776763
message_id = events1[0].message_id
777764

778765
# Second partial event (should continue streaming)
@@ -783,13 +770,11 @@ async def test_partial_streaming_continuation(self, translator, mock_adk_event_w
783770
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
784771
events2.append(event)
785772

786-
assert len(events2) == 3 # New START, CONTENT, END for continuation
787-
assert isinstance(events2[0], TextMessageStartEvent)
788-
assert isinstance(events2[1], TextMessageContentEvent)
789-
assert isinstance(events2[2], TextMessageEndEvent)
790-
assert translator._is_streaming is False
791-
new_message_id = events2[0].message_id
792-
assert new_message_id != message_id
773+
assert len(events2) == 1 # Additional CONTENT chunk
774+
assert isinstance(events2[0], TextMessageContentEvent)
775+
assert events2[0].message_id == message_id # Same stream continues
776+
assert translator._is_streaming is True
777+
assert translator._streaming_message_id == message_id
793778

794779
# Final event (should end streaming - requires is_final_response=True)
795780
mock_adk_event_with_content.partial = False
@@ -800,7 +785,9 @@ async def test_partial_streaming_continuation(self, translator, mock_adk_event_w
800785
async for event in translator.translate(mock_adk_event_with_content, "thread_1", "run_1"):
801786
events3.append(event)
802787

803-
assert len(events3) == 0 # Already emitted END in previous translate
788+
assert len(events3) == 1 # Final END to close the stream
789+
assert isinstance(events3[0], TextMessageEndEvent)
790+
assert events3[0].message_id == message_id
804791

805792
# Should reset streaming state
806793
assert translator._is_streaming is False

0 commit comments

Comments
 (0)