Skip to content

Generating Text

This page adapts the original AI SDK documentation: Generating and Streaming Text.

Large language models (LLMs) can generate text in response to a prompt, which can contain instructions and information to process. For example, you can ask a model to come up with a recipe, draft an email, or summarize a document.

The AI SDK Core provides two functions to generate text and stream it from LLMs:

  • generateText: Generates text for a given prompt and model.
  • streamText: Streams text from a given prompt and model.

Advanced LLM features such as tool calling and structured data generation are built on top of text generation.

You can generate text using the generateText function. This function is ideal for non-interactive use cases where you need to write text (e.g. drafting email or summarizing web pages) and for agents that use tools.

import SwiftAISDK
import OpenAIProvider
let result = try await generateText(
model: openai("gpt-4o"),
prompt: "Write a vegetarian lasagna recipe for 4 people."
)
print(result.text)

You can use more advanced prompts to generate text with more complex instructions and content:

import SwiftAISDK
import OpenAIProvider
let result = try await generateText(
model: openai("gpt-4o"),
instructions: "You are a professional writer. You write simple, clear, and concise content.",
prompt: "Summarize the following article in 3-5 sentences: \(article)"
)
print(result.text)

Pass options directly to generateText, in Swift’s declared argument order. Reuse a model, CallSettings, and provider options across calls:

import SwiftAISDK
import OpenAIProvider
let model = try openai("gpt-5")
let settings = CallSettings(maxRetries: 2)
let providerOptions: ProviderOptions = [
"openai": ["reasoningEffort": .string("low")]
]
let a = try await generateText(
model: model,
prompt: "A",
providerOptions: providerOptions,
settings: settings
)
let b = try await generateText(
model: model,
prompt: "B",
providerOptions: providerOptions,
settings: settings
)
print(a.text)
print(b.text)

generateText returns its completed result. Structured output access can throw if parsing failed:

  • result.content: The content generated across all steps.
  • result.text: Text generated in the final step.
  • result.reasoning: The full reasoning that the model has generated in the last step.
  • result.reasoningText: The reasoning text of the model (only available for some models).
  • result.files: Files generated across all steps.
  • result.sources: Sources referenced across all steps (only available for some models).
  • result.toolCalls: Tool calls made across all steps.
  • result.toolResults: Tool results from all steps.
  • result.finishReason: The reason the model finished generating text.
  • result.usage: Total usage across all steps, equal to result.totalUsage.
  • result.totalUsage: The total usage across all steps (for multi-step generations).
  • result.warnings: Provider warnings collected across all steps (e.g. unsupported settings).
  • result.request: Additional request information.
  • result.response: Additional response information, including response messages and body.
  • result.providerMetadata: Additional provider-specific metadata.
  • result.steps: Details for all steps, useful for getting information about intermediate steps.
  • result.output: The generated structured output (configured via output:).
  • result.finalStep: The last step; use its properties for final-step-only content, tool results, or usage.

Need to debug the entire payload (same as JSON.stringify in TypeScript)? Call jsonString() or jsonValue() directly on the result.

let result = try await generateText(
model: openai("gpt-4o"),
prompt: "Invent a new holiday and describe its traditions."
)
let fullJSON = try result.jsonString()
print(fullJSON)

Both helpers mirror the upstream object shape, so you can log or persist the response without manually traversing the structure.

Sometimes you need access to the full response from the model provider, e.g. to access some provider-specific headers or body content.

You can access the raw response headers and body using the response property:

let result = try await generateText(
model: openai("gpt-4o"),
prompt: "..."
)
print(String(describing: result.response.headers))
print(String(describing: result.response.body))

When using generateText, you can provide an onEnd callback that is triggered after the last step is finished. It contains the text, usage information, finish reason, messages, steps, total usage, and more:

let _ = try await generateText(
model: openai("gpt-4o"),
prompt: "Invent a new holiday and describe its traditions.",
onEnd: { event in
let followUp = try await generateText(
model: openai("gpt-4o"),
messages: event.response.messages.asModelMessages() + [.user("Summarize in 2 sentences.")]
)
print(followUp.text)
}
)

Tip response.messages returns [ResponseMessage]. Combine it with existing history via messages = messages + response.messages or call response.messages.asModelMessages() to get [ModelMessage].

Depending on your model and prompt, it can take a large language model (LLM) up to a minute to finish generating its response. This delay can be unacceptable for interactive use cases such as chatbots or real-time applications, where users expect immediate responses.

AI SDK Core provides the streamText function which simplifies streaming text from LLMs:

import SwiftAISDK
import OpenAIProvider
let result = try streamText(
model: openai("gpt-4o"),
prompt: "Invent a new holiday and describe its traditions."
)
for try await textPart in result.textStream {
print(textPart)
}

Reuse configuration values and pass them directly to each streamText call:

import SwiftAISDK
import OpenAIProvider
let model = try openai("gpt-5")
let settings = CallSettings(maxRetries: 2)
let providerOptions: ProviderOptions = [
"openai": ["reasoningEffort": .string("low")]
]
let result = try streamText(
model: model,
prompt: "Invent a new holiday and describe its traditions.",
providerOptions: providerOptions,
onError: { error in
print("Stream error: \(error)")
return nil
},
settings: settings
)
for try await textPart in result.textStream {
print(textPart)
}

Note In Swift, textStream is an AsyncThrowingStream<String, Error> that you iterate with for try await.

Warning streamText starts streaming immediately. Use onError to log errors.

You can use streamText on its own or integrate it with your Swift HTTP framework. The result object provides helpers for server responses:

  • try result.toUIMessageStreamResponse(options: StreamTextUIResponseOptions<UIMessage>()): Creates a typed UI message stream HTTP response, including tool calls.
  • try await result.pipeUIMessageStreamToResponse(...): Pipes UI message stream deltas to a StreamTextResponseWriter, observing write failures and completion.
  • result.toTextStreamResponse(init: nil): Creates a simple text stream HTTP response.
  • result.pipeTextStreamToResponse(...): Pipes text deltas to any StreamTextResponseWriter.

Note streamText uses backpressure and generates tokens as requested. You must consume the stream for it to finish.

Its completed-result properties are async throws; read them with try await after, or instead of, iterating the stream:

  • result.content: The content generated across all steps.
  • result.text: Text generated in the final step.
  • result.reasoning: Reasoning generated in the final step.
  • result.reasoningText: The reasoning text of the model (only available for some models).
  • result.files: Files generated across all steps.
  • result.sources: Sources referenced across all steps (only available for some models).
  • result.toolCalls: Tool calls made across all steps.
  • result.toolResults: Tool results from all steps.
  • result.finishReason: The reason the model finished generating text.
  • result.usage: Total usage across all steps, equal to result.totalUsage.
  • result.totalUsage: The total usage across all steps (for multi-step generations).
  • result.warnings: Provider warnings collected across all steps (e.g. unsupported settings).
  • result.steps: Details for all steps, useful for getting information about intermediate steps.
  • result.request: Additional request information from the last step.
  • result.response: Additional response information from the last step.
  • result.providerMetadata: Additional provider-specific metadata from the last step.
  • result.finalStep: The last step; for example, (try await result.finalStep).usage gives only that step’s usage.

streamText immediately starts streaming to enable sending data without waiting for the model. Errors become part of the stream and are not thrown to prevent e.g. servers from crashing.

To log errors, you can provide an onError callback that is triggered when an error occurs.

let _ = try streamText(
model: openai("gpt-4o"),
prompt: "Invent a new holiday and describe its traditions.",
onError: { error in
print("Stream error: \(error)")
return nil
}
)

When using streamText, you can provide an onChunk callback that is triggered for each chunk of the stream.

It receives the following chunk types:

  • text
  • reasoning
  • source
  • tool-call
  • tool-input-start
  • tool-input-delta
  • tool-result
  • raw
let _ = try streamText(
model: openai("gpt-4o"),
prompt: "Invent a new holiday and describe its traditions.",
onChunk: { part in
if case .textDelta(_, let text, _) = part { print(text) }
}
)

When using streamText, you can provide an onEnd callback that is triggered when the stream is finished. It contains the text, usage information, finish reason, messages, steps, total usage, and more:

let _ = try streamText(
model: openai("gpt-4o"),
prompt: "Invent a new holiday and describe its traditions.",
onEnd: { event in
let followUp = try await generateText(
model: openai("gpt-4o"),
messages: event.response.messages.asModelMessages() + [.user("Summarize the plan in 2 sentences.")]
)
print(followUp.text)
}
)

You can read a stream with all events using the fullStream property. This can be useful if you want to implement your own UI or handle the stream in a different way. Here is an example of how to use the fullStream property:

import SwiftAISDK
import OpenAIProvider
struct CityQuery: Codable, Sendable { let city: String }
struct Attractions: Codable, Sendable { let attractions: [String] }
let cityAttractions = tool(
description: "List attractions for a city",
inputSchema: CityQuery.self
) { query, _ in
Attractions(attractions: [
"Golden Gate Bridge",
"Ferry Building",
"Golden Gate Park"
])
}
let result = try streamText(
model: openai("gpt-4.1"),
tools: ["cityAttractions": cityAttractions.eraseToTool()],
prompt: "What are some San Francisco tourist attractions?"
)
for try await part in result.fullStream {
switch part {
case .start:
// handle start of stream
break
case .startStep:
// handle start of step
break
case .textStart:
// handle text start
break
case .textDelta(_, let text, _):
// handle text delta here
print(text)
case .textEnd:
// handle text end
break
case .reasoningStart, .reasoningDelta, .reasoningEnd:
// handle reasoning parts
break
case .source(let src):
// handle source
print(src)
case .file(let file, _), .reasoningFile(let file, _):
// handle file
print(file)
case .toolCall(let call):
if call.toolName == "cityAttractions" {
// handle tool call
}
case .toolInputStart, .toolInputDelta, .toolInputEnd:
// handle tool input phases
break
case .toolResult(let result):
// handle tool result
print(result)
case .toolError(let err):
// handle tool error
print(err)
case .finishStep:
// handle finish step
break
case .finish:
// handle finish
break
case .error(let error):
// handle stream error
print(error)
case .toolOutputDenied, .toolApprovalRequest, .toolApprovalResponse:
break
case .abort(let reason):
print("Aborted:", reason ?? "No reason provided")
case .custom, .raw:
break
}
}

You can use the transform option to transform the stream. This is useful for e.g. filtering, changing, or smoothing the text stream.

The transformations are applied before the callbacks are invoked and the promises are resolved. If you e.g. have a transformation that changes all text to uppercase, the onEnd callback will receive the transformed text.

The AI SDK Core provides a smoothStream function that can be used to smooth out text streaming.

Note A smoothing helper is coming to the Swift port. You can build custom transforms today (see below).

You can also implement your own custom transformations. The transformation function receives the tools that are available to the model, and returns a function that is used to transform the stream. Tools can either be generic or limited to the tools that you are using.

Here is an example of how to implement a custom transformation that converts all text to uppercase:

let upperCaseTransform: StreamTextTransform = { stream, _ in
AsyncThrowingStream<TextStreamPart, Error> { continuation in
let task = Task {
do {
var it = stream.makeAsyncIterator()
while let part = try await it.next() {
if case .textDelta(let id, let text, let meta) = part {
continuation.yield(.textDelta(id: id, text: text.uppercased(), providerMetadata: meta))
} else {
continuation.yield(part)
}
}
continuation.finish()
} catch { continuation.finish(throwing: error) }
}
continuation.onTermination = { _ in task.cancel() }
}
}

You can also stop the stream using the stopStream function. This is e.g. useful if you want to stop the stream when model guardrails are violated, e.g. by generating inappropriate content.

When you invoke stopStream, it is important to simulate the step-finish and finish events to guarantee that a well-formed stream is returned and all callbacks are invoked.

let stopWordTransform: StreamTextTransform = { stream, opts in
AsyncThrowingStream<TextStreamPart, Error> { continuation in
let task = Task {
do {
var it = stream.makeAsyncIterator()
while let part = try await it.next() {
if case .textDelta(_, let text, _) = part, text.contains("STOP") {
opts.stopStream()
break
}
continuation.yield(part)
}
continuation.finish()
} catch { continuation.finish(throwing: error) }
}
continuation.onTermination = { _ in task.cancel() }
}
}

You can also provide multiple transformations. They are applied in the order they are provided.

let result = try streamText(
model: openai("gpt-4o"),
prompt: "...",
transform: [upperCaseTransform, stopWordTransform]
)

Some providers such as Perplexity and Google Generative AI include sources in the response.

Currently sources are limited to web pages that ground the response. You can access them using the sources property of the result.

Each url source contains the following properties:

  • id: The ID of the source.
  • url: The URL of the source.
  • title: The optional title of the source.
  • providerMetadata: Provider metadata for the source.

When you use generateText, you can access the sources using the sources property:

let g = try await generateText(
model: openai("gpt-4o"),
prompt: "List the top 5 San Francisco news from the past week."
)
for source in g.sources { print(source) }

When you use streamText, you can access the sources using the fullStream property:

let s = try streamText(
model: openai("gpt-4o"),
prompt: "List the top 5 San Francisco news from the past week."
)
for try await part in s.fullStream {
if case .source(let src) = part { print(src) }
}

The sources are also available in the result.sources promise.

You can see generateText and streamText in action using various frameworks in the following examples:

Examples for Swift will be added in a dedicated Quickstarts section (iOS/macOS, Vapor, CLI). Node/Next.js links are omitted in the Swift port.

Examples for Swift will be added in a dedicated Quickstarts section (iOS/macOS, Vapor, CLI). Node/Next.js links are omitted in the Swift port.