Skip to main content

Generating Text

还没翻译译自英文(ai-sdk.dev)本页 84 段都还没译

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.

generateText

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 { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});

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

import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  instructions:
    'You are a professional writer. ' +
    'You write simple, clear, and concise content.',
  prompt: `Summarize the following article in 3-5 sentences: ${article}`,
});

The result object of generateText contains the generated output and metadata:

  • result.content: The content that was generated in all steps.
  • result.text: The generated text from the final step.
  • result.files: The files that were generated in all steps.
  • result.sources: Sources that have been used as references in all steps (only available for some models).
  • result.toolCalls: The tool calls that were made in all steps.
  • result.toolResults: The results of the tool calls from all steps.
  • result.finishReason: The reason the model finished generating text.
  • result.rawFinishReason: The raw reason why the generation finished (from the provider).
  • result.usage: The total usage across all steps (for multi-step generations).
  • result.warnings: Warnings from the model provider in all steps (e.g. unsupported settings).
  • result.steps: Details for all steps, useful for getting information about intermediate steps, including per-step performance.
  • result.finalStep: Details for the final step, including performance.
  • result.output: The generated structured output using the output specification.

Each step includes performance with timing and throughput information:

  • effectiveOutputTokensPerSecond: Effective output tokens per second, calculated as outputTokens / requestSeconds.
  • outputTokensPerSecond: For streaming steps, output tokens per second after the first generated output chunk, calculated as outputTokens / outputStreamSeconds. For generateText, this is undefined.
  • inputTokensPerSecond: For streaming steps, input tokens per second before the first generated output chunk, calculated as inputTokens / ttftSeconds. For generateText, this is undefined.
  • effectiveTotalTokensPerSecond: Effective total tokens per second, calculated as (inputTokens + outputTokens) / requestSeconds.
  • stepTimeMs: Total time spent on the step, including language model response time and tool execution time, in milliseconds.
  • responseTimeMs: Time spent waiting for the language model response in milliseconds.
  • toolExecutionMs: Time spent executing each client-side tool call in the step in milliseconds, keyed by tool call ID.
  • timeToFirstOutputMs: For streaming steps, the time until the first generated output chunk was received in milliseconds. For generateText, this is undefined.
  • timeBetweenOutputChunksMs: For streaming steps with at least two output chunks, timing statistics for the gaps between generated output chunks in milliseconds.

Accessing response headers & body

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 finalStep.response property:

import { generateText } from 'ai';

const result = await generateText({
  // ...
});

console.log(JSON.stringify(result.finalStep.response.headers, null, 2));
console.log(JSON.stringify(result.finalStep.response.body, null, 2));

onEnd callback

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

import { generateText } from 'ai';

const result = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Invent a new holiday and describe its traditions.',
  onEnd({ text, finishReason, usage, responseMessages, steps, totalUsage }) {
    // your own logic, e.g. for saving the chat history or recording usage

    const messages = responseMessages; // messages that were generated
  },
});

Lifecycle callbacks (experimental)

Warning

Experimental callbacks are subject to breaking changes in incremental package releases.

generateText provides several experimental lifecycle callbacks that let you hook into different phases of the generation process. These are useful for logging, observability, debugging, and custom telemetry. Errors thrown inside these callbacks are silently caught and do not break the generation flow.

import { generateText } from 'ai';

const result = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'What is the weather in San Francisco?',
  tools: {
    // ... your tools
  },

  onStart({ modelId }) {
    console.log('Generation started', { modelId });
  },

  onStepStart({ stepNumber, modelId, messages }) {
    console.log(`Step ${stepNumber} starting`, { modelId });
  },

  onLanguageModelCallStart({ modelId, messages }) {
    console.log('Model call starting', {
      modelId,
      messageCount: messages.length,
    });
  },

  onLanguageModelCallEnd({ modelId, finishReason, content }) {
    console.log('Model call finished', {
      modelId,
      finishReason,
      contentParts: content.length,
    });
  },

  onToolExecutionStart({ toolCall }) {
    console.log(`Tool call starting: ${toolCall.toolName}`, {
      toolCallId: toolCall.toolCallId,
    });
  },

  onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) {
    console.log(
      `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`,
      {
        success: toolOutput.type === 'tool-result',
      },
    );
  },

  onStepEnd({ stepNumber, finishReason, usage, performance }) {
    console.log(`Step ${stepNumber} finished`, {
      finishReason,
      usage,
      performance,
    });
  },
});

The available lifecycle callbacks are:

  • onStart: Called once when the generateText operation begins, before any LLM calls. Receives model info, messages, settings, and runtimeContext.
  • onStepStart: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
  • onLanguageModelCallStart: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
  • onLanguageModelCallEnd: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata.
  • onToolExecutionStart: Called right before a tool's execute function runs. Receives the tool call object, messages, and toolContext.
  • onToolExecutionEnd: Called right after a tool's execute function completes or errors. Receives the tool call object, toolExecutionMs, and a toolOutput discriminated union (type: 'tool-result' with output, or type: 'tool-error' with error).
  • onStepEnd: Called after each step finishes. Includes stepNumber (zero-based index of the completed step).

streamText

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 { streamText } from 'ai';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Invent a new holiday and describe its traditions.',
});

// example: use textStream as an async iterable
for await (const textPart of result.textStream) {
  console.log(textPart);
}

Note

result.textStream is both a ReadableStream and an AsyncIterable.

Warning

streamText immediately starts streaming and suppresses errors to prevent server crashes. Use the onError callback to log errors.

You can use streamText on its own or in combination with AI SDK UI and AI SDK RSC. The result.stream can be passed to several standalone helpers to make the integration into AI SDK UI easier:

  • createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) }): Creates a UI Message stream HTTP response (with tool calls etc.) that can be used in a Next.js App Router API route.
  • pipeUIMessageStreamToResponse({ stream: toUIMessageStream({ stream: result.stream }), response }): Writes UI Message stream delta output to a Node.js response-like object.
  • createTextStreamResponse({ stream: toTextStream({ stream: result.stream }) }): Creates a simple text stream HTTP response.
  • pipeTextStreamToResponse({ stream: toTextStream({ stream: result.stream }), response }): Writes text delta output to a Node.js response-like object.

Note

streamText is using backpressure and only generates tokens as they are requested. You need to consume the stream in order for it to finish.

It also provides several promises that resolve when the stream is finished:

  • result.content: The content that was generated in all steps.
  • result.text: The generated text from the final step.
  • result.finalStep: Details for the final step, including per-step performance.
  • result.files: Files that have been generated by the model in all steps.
  • result.sources: Sources that have been used as references in all steps (only available for some models).
  • result.toolCalls: The tool calls that have been executed in all steps.
  • result.toolResults: The tool results that have been generated in all steps.
  • result.finishReason: The reason the model finished generating text.
  • result.rawFinishReason: The raw reason why the generation finished (from the provider).
  • result.usage: The total usage across all steps (for multi-step generations).
  • result.totalUsage: Deprecated. Use result.usage instead.
  • result.warnings: Warnings from the model provider in all steps (e.g. unsupported settings).
  • result.steps: Details for all steps, useful for getting information about intermediate steps, including per-step performance.

For streamText, timeToFirstOutputMs is set when the first generated output chunk is received for a step. timeBetweenOutputChunksMs includes min, p10, median, avg, p90, and max when at least two output chunks are received.

onError callback

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.

import { streamText } from 'ai';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Invent a new holiday and describe its traditions.',
  onError({ error }) {
    console.error(error); // your error logging logic here
  },
});

onChunk callback

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

It receives all stream part types from stream, including:

  • start
  • start-step
  • text-start
  • text-delta
  • text-end
  • reasoning-start
  • reasoning-delta
  • reasoning-end
  • custom
  • source
  • file
  • reasoning-file
  • tool-call
  • tool-input-start
  • tool-input-delta
  • tool-input-end
  • tool-result
  • tool-error
  • tool-output-denied
  • tool-approval-request
  • tool-approval-response
  • finish-step
  • finish
  • abort
  • error
  • raw
import { streamText } from 'ai';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Invent a new holiday and describe its traditions.',
  onChunk({ chunk }) {
    // implement your own logic here, e.g.:
    if (chunk.type === 'text-delta') {
      console.log(chunk.text);
    }
  },
});

onEnd callback

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

import { streamText } from 'ai';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Invent a new holiday and describe its traditions.',
  onEnd({ text, finishReason, usage, responseMessages, steps, totalUsage }) {
    // your own logic, e.g. for saving the chat history or recording usage

    const messages = responseMessages; // messages that were generated
  },
});

Lifecycle callbacks (experimental)

Warning

Experimental callbacks are subject to breaking changes in incremental package releases.

streamText provides several experimental lifecycle callbacks that let you hook into different phases of the streaming process. These are useful for logging, observability, debugging, and custom telemetry. Errors thrown inside these callbacks are silently caught and do not break the streaming flow.

import { streamText } from 'ai';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'What is the weather in San Francisco?',
  tools: {
    // ... your tools
  },

  onStart({ modelId, instructions, messages }) {
    console.log('Streaming started', { modelId });
  },

  onStepStart({ stepNumber, modelId, messages }) {
    console.log(`Step ${stepNumber} starting`, { modelId });
  },

  onLanguageModelCallStart({ modelId, messages }) {
    console.log('Model call starting', {
      modelId,
      messageCount: messages.length,
    });
  },

  onLanguageModelCallEnd({ modelId, finishReason, content }) {
    console.log('Model call finished', {
      modelId,
      finishReason,
      contentParts: content.length,
    });
  },

  onToolExecutionStart({ toolCall }) {
    console.log(`Tool call starting: ${toolCall.toolName}`, {
      toolCallId: toolCall.toolCallId,
    });
  },

  onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) {
    console.log(
      `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`,
      {
        success: toolOutput.type === 'tool-result',
      },
    );
  },

  onStepEnd({ finishReason, usage }) {
    console.log('Step finished', { finishReason, usage });
  },
});

The available lifecycle callbacks are:

  • onStart: Called once when the streamText operation begins, before any LLM calls. Receives model info, messages, settings, and runtimeContext.
  • onStepStart: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
  • onLanguageModelCallStart: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
  • onLanguageModelCallEnd: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata.
  • onToolExecutionStart: Called right before a tool's execute function runs. Receives the tool call object, messages, and toolContext.
  • onToolExecutionEnd: Called right after a tool's execute function completes or errors. Receives the tool call object, toolExecutionMs, and a toolOutput discriminated union (type: 'tool-result' with output, or type: 'tool-error' with error).
  • onStepEnd: Called after each step finishes. Receives the finish reason, usage, and other step details.

stream property

You can read a stream with all events using the stream 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 stream property:

import { streamText } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  tools: {
    cityAttractions: {
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({
        attractions: ['attraction1', 'attraction2', 'attraction3'],
      }),
    },
  },
  prompt: 'What are some San Francisco tourist attractions?',
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'start': {
      // handle start of stream
      break;
    }
    case 'start-step': {
      // handle start of step
      break;
    }
    case 'text-start': {
      // handle text start
      break;
    }
    case 'text-delta': {
      // handle text delta here
      break;
    }
    case 'text-end': {
      // handle text end
      break;
    }
    case 'reasoning-start': {
      // handle reasoning start
      break;
    }
    case 'reasoning-delta': {
      // handle reasoning delta here
      break;
    }
    case 'reasoning-end': {
      // handle reasoning end
      break;
    }
    case 'source': {
      // handle source here
      break;
    }
    case 'file': {
      // handle file here
      break;
    }
    case 'tool-call': {
      switch (part.toolName) {
        case 'cityAttractions': {
          // handle tool call here
          break;
        }
      }
      break;
    }
    case 'tool-input-start': {
      // handle tool input start
      break;
    }
    case 'tool-input-delta': {
      // handle tool input delta
      break;
    }
    case 'tool-input-end': {
      // handle tool input end
      break;
    }
    case 'tool-result': {
      switch (part.toolName) {
        case 'cityAttractions': {
          // handle tool result here
          break;
        }
      }
      break;
    }
    case 'tool-error': {
      // handle tool error
      break;
    }
    case 'finish-step': {
      // handle finish step
      break;
    }
    case 'finish': {
      // handle finish here
      break;
    }
    case 'error': {
      // handle error here
      break;
    }
    case 'raw': {
      // handle raw value
      break;
    }
  }
}

Stream transformation

You can use the experimental_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.

Smoothing streams

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

import { smoothStream, streamText } from 'ai';

const result = streamText({
  model,
  prompt,
  experimental_transform: smoothStream(),
});

Custom transformations

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:

import { streamText, type TextStreamPart, type ToolSet } from 'ai';

const upperCaseTransform =
  <TOOLS extends ToolSet>() =>
  (options: { tools: TOOLS; stopStream: () => void }) =>
    new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
      transform(chunk, controller) {
        controller.enqueue(
          // for text-delta chunks, convert the text to uppercase:
          chunk.type === 'text-delta'
            ? { ...chunk, text: chunk.text.toUpperCase() }
            : chunk,
        );
      },
    });

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 finish-step and finish events to guarantee that a well-formed stream is returned and all callbacks are invoked.

import { streamText, type TextStreamPart, type ToolSet } from 'ai';

const stopWordTransform =
  <TOOLS extends ToolSet>() =>
  ({ stopStream }: { stopStream: () => void }) =>
    new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
      // note: this is a simplified transformation for testing;
      // in a real-world version more there would need to be
      // stream buffering and scanning to correctly emit prior text
      // and to detect all STOP occurrences.
      transform(chunk, controller) {
        if (chunk.type !== 'text-delta') {
          controller.enqueue(chunk);
          return;
        }

        if (chunk.text.includes('STOP')) {
          // stop the stream
          stopStream();

          // simulate the finish-step event
          controller.enqueue({
            type: 'finish-step',
            finishReason: 'stop',
            rawFinishReason: 'stop',
            usage: {
              inputTokens: undefined,
              inputTokenDetails: {
                cacheReadTokens: undefined,
                cacheWriteTokens: undefined,
                noCacheTokens: undefined,
              },
              outputTokens: undefined,
              outputTokenDetails: {
                reasoningTokens: undefined,
                textTokens: undefined,
              },
              totalTokens: undefined,
            },
            performance: {
              effectiveOutputTokensPerSecond: 0,
              outputTokensPerSecond: undefined,
              inputTokensPerSecond: undefined,
              effectiveTotalTokensPerSecond: 0,
              stepTimeMs: 0,
              responseTimeMs: 0,
              toolExecutionMs: {},
              timeToFirstOutputMs: undefined,
            },
            response: {
              id: 'response-id',
              modelId: 'mock-model-id',
              timestamp: new Date(0),
            },
            providerMetadata: undefined,
          });

          // simulate the finish event
          controller.enqueue({
            type: 'finish',
            finishReason: 'stop',
            rawFinishReason: 'stop',
            totalUsage: {
              inputTokens: undefined,
              inputTokenDetails: {
                cacheReadTokens: undefined,
                cacheWriteTokens: undefined,
                noCacheTokens: undefined,
              },
              outputTokens: undefined,
              outputTokenDetails: {
                reasoningTokens: undefined,
                textTokens: undefined,
              },
              totalTokens: undefined,
            },
          });

          return;
        }

        controller.enqueue(chunk);
      },
    });

Multiple transformations

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

const result = streamText({
  model,
  prompt,
  experimental_transform: [firstTransform, secondTransform],
});

Sources

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

Sources can reference URLs or documents. You can access them using the sources property of the result. For OpenAI Responses and Anthropic web search, sources contain the retrieved references, which can include more references than the model cites in its answer. OpenAI file-search results are also exposed as document sources when the API returns those results. If retrieval data is unavailable, citation-derived references remain available as sources for compatibility.

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:

const result = await generateText({
  model: 'google/gemini-3.8-flash',
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt: 'List the top 5 San Francisco news from the past week.',
});

for (const source of result.sources) {
  if (source.sourceType === 'url') {
    console.log('ID:', source.id);
    console.log('Title:', source.title);
    console.log('URL:', source.url);
    console.log('Provider metadata:', source.providerMetadata);
    console.log();
  }
}

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

const result = streamText({
  model: 'google/gemini-3.8-flash',
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt: 'List the top 5 San Francisco news from the past week.',
});

for await (const part of result.stream) {
  if (part.type === 'source' && part.sourceType === 'url') {
    console.log('ID:', part.id);
    console.log('Title:', part.title);
    console.log('URL:', part.url);
    console.log('Provider metadata:', part.providerMetadata);
    console.log();
  }
}

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

Citations

OpenAI Responses and Anthropic attach an optional citations array to each text content part. A citation is a reference supporting that text, independently of the complete retrieved source set. Use citations to render inline references; use result.sources to list retrieved sources.

Each Citation (exported from ai) contains:

  • source: A URL or document source, including its title when available. Its ID identifies the cited resource and does not necessarily match an ID in result.sources.
  • startIndex and endIndex: Optional provider-supplied offsets within the containing generated text; the end is exclusive. Providers can use different character indexing conventions. The SDK preserves their offsets.
  • citedText: An optional supporting passage from the source. This is source text, rather than an offset in the generated answer.

For inline rendering, use the offsets within the containing text part and follow the provider's character indexing convention. JavaScript's slice() uses UTF-16 code units, so provider offsets may need conversion when the text contains emoji or other characters outside the basic multilingual plane. When offsets are absent or cannot be interpreted reliably, render the references alongside the containing text block instead of guessing a range.

citedText and provider-specific document locations identify the supporting evidence in the source; they do not locate the claim in the generated answer. Preserve query parameters and fragments in source URLs, since they can identify a particular version, page, or passage. Multiple citations can reference the same source at different locations in the generated text.

Anthropic supplies supporting passages and document locations instead of output text offsets. Provider-specific location details remain in provider metadata. OpenAI file-search citation index values are retained in provider metadata; they are not interpreted as output text ranges.

for (const part of result.content) {
  if (part.type === 'text') {
    for (const citation of part.citations ?? []) {
      console.log(citation.source, citation.startIndex, citation.endIndex);
    }
  }
}

For streamText, citations are available on text-end events, associated with the text block's id, and on text parts in the completed result.content. They also survive toUIMessageStream as TextUIPart.citations, including when source events are disabled. Citation fallback source events can arrive at the end of the provider stream, after the SDK knows whether retrieval data exists.

for await (const part of result.stream) {
  if (part.type === 'text-end') {
    console.log(part.id, part.citations);
  }
}

Raw OpenAI annotations and Anthropic citations remain in text provider metadata. Other providers may omit citations; an omitted field does not imply that their sources are a complete retrieval set.

Examples

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

generateText

streamText