Section 8/151 menit

8. Streaming Response

8. Streaming Response

Untuk UI yang responsif, gunakan streaming agar teks muncul secara progresif — persis seperti ChatGPT atau Writing Tools Apple.

Streaming Dasar

swift
func streamResponse(to prompt: String) async throws {
    let session = LanguageModelSession()
    let stream = session.streamResponse(to: prompt)

    for try await partial in stream {
        // partial.content berisi akumulasi teks hingga saat ini
        print(partial.content, terminator: "")
    }
    print() // newline di akhir
}

Streaming dengan Structured Output

Streaming juga bekerja dengan @Generable — field akan terisi secara progresif:

swift
@Generable
struct StoryOutline {
    @Guide(description: "Judul cerita")
    var title: String

    @Guide(description: "Paragraf pembuka")
    var opening: String

    @Guide(description: "Tiga poin plot utama")
    var plotPoints: [String]

    @Guide(description: "Kalimat penutup")
    var conclusion: String
}

func streamStoryOutline(topic: String) async throws {
    let session = LanguageModelSession()
    let stream = session.streamResponse(
        to: "Buat outline cerita pendek tentang: \(topic)",
        generating: StoryOutline.self
    )

    for try await partial in stream {
        // partial.content adalah StoryOutline parsial
        // Field yang sudah lengkap sudah bisa diakses
        if let title = partial.content.title {
            print("Judul:", title)
        }
    }
}

Streaming di SwiftUI dengan @Observable

swift
import FoundationModels
import SwiftUI

@Observable
class StreamingViewModel {
    var displayedText = ""
    var isStreaming = false

    private let session = LanguageModelSession()

    func sendMessage(_ prompt: String) async {
        displayedText = ""
        isStreaming = true
        defer { isStreaming = false }

        do {
            let stream = session.streamResponse(to: prompt)
            for try await partial in stream {
                displayedText = partial.content
            }
        } catch {
            displayedText = "Error: \(error.localizedDescription)"
        }
    }
}

struct StreamingChatView: View {
    @State private var viewModel = StreamingViewModel()
    @State private var inputText = ""

    var body: some View {
        VStack {
            ScrollView {
                Text(viewModel.displayedText)
                    .padding()
                    .frame(maxWidth: .infinity, alignment: .leading)
            }

            if viewModel.isStreaming {
                ProgressView()
            }

            HStack {
                TextField("Ketik pesan...", text: $inputText)
                    .textFieldStyle(.roundedBorder)

                Button("Kirim") {
                    let text = inputText
                    inputText = ""
                    Task {
                        await viewModel.sendMessage(text)
                    }
                }
                .disabled(viewModel.isStreaming || inputText.isEmpty)
            }
            .padding()
        }
    }
}