|
32 | 32 |
|
33 | 33 |
|
34 | 34 | class StreamingMode(Enum): |
| 35 | + """Streaming modes for agent execution. |
| 36 | +
|
| 37 | + This enum defines different streaming behaviors for how the agent returns |
| 38 | + events as model response. |
| 39 | + """ |
| 40 | + |
35 | 41 | NONE = None |
| 42 | + """Non-streaming mode (default). |
| 43 | +
|
| 44 | + In this mode: |
| 45 | + - The runner returns one single content in a turn (one user / model |
| 46 | + interaction). |
| 47 | + - No partial/intermediate events are produced |
| 48 | + - Suitable for: CLI tools, batch processing, synchronous workflows |
| 49 | +
|
| 50 | + Example: |
| 51 | + ```python |
| 52 | + config = RunConfig(streaming_mode=StreamingMode.NONE) |
| 53 | + async for event in runner.run_async(..., run_config=config): |
| 54 | + # event.partial is always False |
| 55 | + # Only final responses are yielded |
| 56 | + if event.content: |
| 57 | + print(event.content.parts[0].text) |
| 58 | + ``` |
| 59 | + """ |
| 60 | + |
36 | 61 | SSE = 'sse' |
| 62 | + """Server-Sent Events (SSE) streaming mode. |
| 63 | +
|
| 64 | + In this mode: |
| 65 | + - The runner yields events progressively as the LLM generates responses |
| 66 | + - Both partial events (streaming chunks) and aggregated events are yielded |
| 67 | + - Suitable for: real-time display with typewriter effects in Web UIs, chat |
| 68 | + applications, interactive displays |
| 69 | +
|
| 70 | + Event Types in SSE Mode: |
| 71 | + - **Partial text events** (event.partial=True, contains text): |
| 72 | + Streaming text chunks for typewriter effect. These should typically be |
| 73 | + displayed to users in real-time. |
| 74 | +
|
| 75 | + - **Partial function call events** (event.partial=True, contains function_call): |
| 76 | + Internal streaming chunks used to progressively build function call |
| 77 | + arguments. These are typically NOT displayed to end users. |
| 78 | +
|
| 79 | + - **Aggregated events** (event.partial=False): |
| 80 | + The complete, aggregated response after all streaming chunks. Contains |
| 81 | + the full text or complete function call with all arguments. |
| 82 | +
|
| 83 | + Important Considerations: |
| 84 | + 1. **Duplicate text issue**: With Progressive SSE Streaming enabled |
| 85 | + (default), you will receive both partial text chunks AND a final |
| 86 | + aggregated text event. To avoid displaying text twice: |
| 87 | + - Option A: Only display partial text events, skip final text events |
| 88 | + - Option B: Only display final events, skip all partial events |
| 89 | + - Option C: Track what's been displayed and skip duplicates |
| 90 | +
|
| 91 | + 2. **Event filtering**: Applications should filter events based on their |
| 92 | + needs. Common patterns: |
| 93 | +
|
| 94 | + # Pattern 1: Display only partial text + final function calls |
| 95 | + async for event in runner.run_async(...): |
| 96 | + if event.partial and event.content and event.content.parts: |
| 97 | + # Check if it's text (not function call) |
| 98 | + if any(part.text for part in event.content.parts): |
| 99 | + if not any(part.function_call for part in event.content.parts): |
| 100 | + # Display partial text for typewriter effect |
| 101 | + text = ''.join(p.text or '' for p in event.content.parts) |
| 102 | + print(text, end='', flush=True) |
| 103 | + elif not event.partial and event.get_function_calls(): |
| 104 | + # Display final function calls |
| 105 | + for fc in event.get_function_calls(): |
| 106 | + print(f"Calling {fc.name}({fc.args})") |
| 107 | +
|
| 108 | + # Pattern 2: Display only final events (no streaming effect) |
| 109 | + async for event in runner.run_async(...): |
| 110 | + if not event.partial: |
| 111 | + # Only process final responses |
| 112 | + if event.content: |
| 113 | + text = ''.join(p.text or '' for p in event.content.parts) |
| 114 | + print(text) |
| 115 | +
|
| 116 | + 3. **Progressive SSE Streaming feature**: Controlled by the |
| 117 | + ADK_ENABLE_PROGRESSIVE_SSE_STREAMING environment variable (default: ON). |
| 118 | + - When ON: Preserves original part ordering, supports function call |
| 119 | + argument streaming, produces partial events + final aggregated event |
| 120 | + - When OFF: Simple text accumulation, may lose some information |
| 121 | +
|
| 122 | + Example: |
| 123 | + ```python |
| 124 | + config = RunConfig(streaming_mode=StreamingMode.SSE) |
| 125 | + displayed_text = "" |
| 126 | +
|
| 127 | + async for event in runner.run_async(..., run_config=config): |
| 128 | + if event.partial: |
| 129 | + # Partial streaming event |
| 130 | + if event.content and event.content.parts: |
| 131 | + # Check if this is text (not a function call) |
| 132 | + has_text = any(part.text for part in event.content.parts) |
| 133 | + has_fc = any(part.function_call for part in event.content.parts) |
| 134 | +
|
| 135 | + if has_text and not has_fc: |
| 136 | + # Display partial text chunks for typewriter effect |
| 137 | + text = ''.join(p.text or '' for p in event.content.parts) |
| 138 | + print(text, end='', flush=True) |
| 139 | + displayed_text += text |
| 140 | + else: |
| 141 | + # Final event - check if we already displayed this content |
| 142 | + if event.content: |
| 143 | + final_text = ''.join(p.text or '' for p in event.content.parts) |
| 144 | + if final_text != displayed_text: |
| 145 | + # New content not yet displayed |
| 146 | + print(final_text) |
| 147 | + ``` |
| 148 | +
|
| 149 | + See Also: |
| 150 | + - Event.is_final_response() for identifying final responses |
| 151 | + """ |
| 152 | + |
37 | 153 | BIDI = 'bidi' |
| 154 | + """Bidirectional streaming mode. |
| 155 | +
|
| 156 | + So far this mode is not used in the standard execution path. The actual |
| 157 | + bidirectional streaming behavior via runner.run_live() uses a completely |
| 158 | + different code path that doesn't rely on streaming_mode. |
| 159 | +
|
| 160 | + For bidirectional streaming, use runner.run_live() instead of run_async(). |
| 161 | + """ |
38 | 162 |
|
39 | 163 |
|
40 | 164 | class RunConfig(BaseModel): |
|
0 commit comments