Streaming
Display tokens as the model generates them for a responsive UI, rather than waiting for the complete response.
agent.stream()
agent.stream() returns an AsyncStream<StreamEvent>. Iterate it with for try await to receive events as they happen:
Swift
for try await event in agent.stream("Explain how TCP/IP works") {
switch event {
case .textDelta(let token):
print(token, terminator: "") // each token as it arrives
case .result(let final):
print("\n\nDone.")
default:
break
}
}
Stream Events
The StreamEvent enum covers every stage of the agent loop:
| Case | When it fires | Associated value |
|---|---|---|
.textDelta(String) | Each token of model text output | The token string |
.toolUse(ToolUseBlock) | Model decides to call a tool | Tool name + input |
.toolResult(ToolResultBlock) | Tool execution completes | Status + output content |
.cycleComplete(CycleMetrics) | One reasoning cycle finishes | Latency, token counts |
.result(AgentResult) | Final response ready | Full result + metrics |
.error(Error) | Unrecoverable error | The error |
SwiftUI Example
Stream tokens into a @State variable for a typewriter effect:
ContentView.swiftSwift
import SwiftUI
import StrandsAgents
struct ContentView: View {
@State private var response = ""
@State private var isStreaming = false
let agent = Agent(model: provider)
var body: some View {
VStack(alignment: .leading, spacing: 16) {
ScrollView {
Text(response)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
Button(isStreaming ? "Streaming…" : "Ask Agent") {
Task { await ask() }
}
.disabled(isStreaming)
}
}
func ask() async {
isStreaming = true
response = ""
do {
for try await event in agent.stream("Write a short poem about Swift") {
if case .textDelta(let token) = event {
response += token
}
}
} catch {
response = "Error: \(error.localizedDescription)"
}
isStreaming = false
}
}
Showing Tool Activity
Use .toolUse and .toolResult events to give users visibility into what the agent is doing:
Swift
@State private var statusLine = ""
for try await event in agent.stream(prompt) {
switch event {
case .textDelta(let token):
response += token
case .toolUse(let use):
statusLine = "Calling \(use.name)…"
case .toolResult(let res):
statusLine = res.status == .success ? "Tool completed" : "Tool failed"
case .result:
statusLine = ""
default:
break
}
}
Collecting Metrics
The final .result event carries the same AgentResult as a non-streaming run() call, including full metrics:
Swift
for try await event in agent.stream(prompt) {
if case .result(let final) = event {
print("Cycles: \(final.metrics.cycleCount)")
print("Latency: \(final.metrics.totalLatencyMs)ms")
print("Tokens/sec: \(final.metrics.outputTokensPerSecond)")
print("Total tokens: \(final.usage.totalTokens)")
}
}