mcpField
S
streaming

streaming

github.com/assistant-ui開発296.5k 回インストール1 日前に更新
リポジトリ

streaming について

streamingはassistant-uiの基盤となるストリーム処理ライブラリで、カスタムのAI応答エンドポイント、ストリームのデコード、再開可能な配信を扱う開発者向けです。バックエンドの出力を共通形式に正規化し、複数のワイヤー形式に対応するエンコーダーとデコーダーを備えています。

主な機能

  1. 01AssistantStreamChunk形式への正規化
  2. 023種類のワイヤー形式のエンコードとデコード
  3. 03カスタムストリーミング応答の生成
  4. 04再開可能なストリームのサポート
  5. 05テキスト、推論、ツール呼び出しの処理

活用シーン

  1. 01カスタムエンドポイントのストリーミング応答作成
  2. 02ストリームデータのデコードと確認
  3. 03ストリームへの再開機能の追加
  4. 04テキスト、推論、ツール呼び出し出力の処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/assistant-ui/skills --skill streaming

インストール後、Claude Code で/streamingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
assistant-transport.md
data-stream.md
encoders.md
resumable.md

詳細

assistant-ui ストリーミング

最新の API については、常に assistant-ui.com/llms.txt を参照してください。

assistant-stream は、assistant-ui のチャットランタイムの下にあるワイヤ層です。すべてのバックエンドを AssistantStreamChunk 値の単一のストリームに正規化し、3 つのワイヤフォーマット用のエンコーダーとデコーダーを提供し、そのいずれの上にも再開可能なストリーム層を追加します。バックエンドがすでに Vercel AI SDK を話す場合、このパッケージに直接触れることはほとんどありません(streamText と toUIMessageStream で十分です)。カスタムエンドポイントを書くとき、ストリームを自分でデコードする必要があるとき、または再開可能なストリームが欲しいときに利用してください。

リファレンス

  • ./references/data-stream.md -- Data Stream プロトコル、useDataStreamRuntime、およびそのワイヤフォーマット
  • ./references/assistant-transport.md -- Assistant Transport SSE フォーマットと useAssistantTransportRuntime 状態スナップショットランタイム
  • ./references/encoders.md -- エンコーダーとデコーダーのカタログ、PlainTextEncoder、UIMessageStreamDecoder、アキュムレーター、およびデバッグ
  • ./references/resumable.md -- assistant-stream/resumable: コンテキスト、ストア、およびクライアントの接続

使用する場面

Streaming the model call through the Vercel AI SDK?
├─ Yes → streamText + toUIMessageStream/createUIMessageStreamResponse (or result.toUIMessageStreamResponse())
│        assistant-stream is optional: only needed to decode the response yourself or add resumable streams
└─ No → build the response with assistant-stream
    ├─ Emitting message parts (text, reasoning, tool calls) → Data Stream
    └─ Streaming a full agent state snapshot with custom commands → Assistant Transport

インストール

npm install assistant-stream

@assistant-ui/ai-sdk は現在の AI SDK 統合パッケージです(フレームワーク非依存)。@assistant-ui/react-ai-sdk は古いインストール向けに同じ API を再エクスポートしていますが、新しいコードは @assistant-ui/ai-sdk からインポートすべきです。

カスタムストリーミングレスポンスを構築する

createAssistantStreamResponse は AssistantStreamController を引数にコールバックを実行し、Data Stream としてエンコードされた Response を返します(代替のエンコーダーについては data-stream.md を参照してください)。

import { createAssistantStreamResponse } from "assistant-stream";

export async function POST(req: Request) {
  return createAssistantStreamResponse(async (controller) => {
    controller.appendText("Hello ");
    controller.appendText("world!");

    controller.appendReasoning("Checking the forecast first.", {
      unstable_summary: "Looking up the weather",
    });

    controller.appendSource({
      type: "source",
      sourceType: "url",
      id: "s1",
      url: "https://example.com/forecast",
      title: "Forecast",
    });

    const tool = controller.addToolCallPart({ toolName: "get_weather" });
    tool.argsText.append('{"city":"NYC"}');
    tool.argsText.close();
    tool.setResponse({ result: { temperature: 22 } });

    controller.close();
  });
}

close() はまだ開いている部分をすべて閉じてストリームを終了します。コールバック内でキャッチされなかった throw は、自動的に error チャンクに変換されます。

AssistantStreamController

サーバーサイドのすべてのストリームは、最終的にどのエンコーダーでラップされるかにかかわらず、このコントローラーを通じて書き込まれます(createAssistantStream、createAssistantStreamController、createAssistantStreamResponse はすべてこれを渡します)。

メソッドシグネチャ備考
appendText(textDelta: string) => void最初の呼び出しでテキストパートを開き、次の呼び出しでそこに追記します
appendReasoning(reasoningDelta: string, options?: { unstable_summary?: string }) => voidoptions を渡すと常に新しいパートが開かれるため、サマリーは専用のパートに格納されます
appendSource(part: SourcePart) => voidSourcePart は { type: "source", sourceType: "url", id, url, title?, parentId? } です
appendFile(part: FilePart) => voidFilePart は { type: "file", data, mimeType, parentId? } です
appendData(part: DataPart) => voidDataPart は { type: "data", name, data, parentId? } で、アプリが定義する名前付きパートです
addTextPart() => TextStreamController明示的な { append(text), close() } ライターで、他のパートと交互に扱うためのものです
addReasoningPart(options?) => TextStreamControlleraddTextPart と同じライター形式です
addToolCallPart(toolName: string) => ToolCallStreamControllertoolCallId を生成します。安定した id については後述のオブジェクトオーバーロードを参照してください
addToolCallPart(init: ToolCallPartInit) => ToolCallStreamController{ toolCallId?, toolName, argsText?, args?, response? }
enqueue(chunk: AssistantStreamChunk) => void生のエスケープハッチです。上記のヘルパーを優先してください
merge(stream: AssistantStream) => void別の AssistantStream のパートをこれに結合します
withParentId(parentId: string) => AssistantStreamController書き込み時に parentId を付与する(ネストされた、または関連するパート)コントローラーを返します
close() => void開いているパートを閉じ、その後ストリームを閉じます

addToolCallPart は ToolCallStreamController を返します: { argsText: TextStreamController, setResponse(response), close() }。setResponse は { result, artifact?, isError?, modelContent?, messages? }(ToolResponse が返す形式)を受け取り、パートを自動的に閉じ、2 回目の呼び出しは無視します。

ストリームイベントとパートの種類

すべてのデコーダーは、ワイヤー形式にかかわらず、同じ正規化された AssistantStreamChunk ユニオン({ path: number[] } & { type, ... })を生成します:

type追加フィールド
part-startpart: PartInit(下記参照)
part-finishなし
tool-call-args-text-finishなし
text-deltatextDelta: string
annotationsannotations: ReadonlyJSONValue[]
datadata: ReadonlyJSONValue[]
step-startmessageId: string
step-finishfinishReason, usage: { inputTokens, outputTokens }, isContinued: boolean
message-finishfinishReason, usage
resultresult, isError: boolean, artifact?, modelContent?, messages?
errorerror: string, code?, severity?: "critical" | "warning" | "info"
update-stateoperations: AssistantTransportStateOperation[](assistant-transport.md を参照)

PartInit(part-start の part フィールド)は 6 つのパートタイプのいずれかで、すべてのバリアントが省略可能な parentId を持ちます:

type追加フィールド
textなし
reasoningunstable_summary?: string
tool-calltoolCallId: string, toolName: string
sourcesourceType: "url", id, url, title?
filedata: string, mimeType: string
dataname: string, data: ReadonlyJSONValue

よくある落とし穴

appendSource、appendFile、appendData がパートを黙って破棄する

  • type フィールド("source"、"file"、"data")を含む完全なパートオブジェクトを渡してください。メソッド名がそれを暗黙に補ってくれるわけではありません。

UI でツール呼び出しがいつまでも完了しない

  • addToolCallPart には toolName が必要です。id は自分で渡さない限り自動生成されます。argsText を閉じる(または自動で閉じてくれる setResponse を呼び出す)ようにしないと、パートは永遠に完了しません。レンダリングは非推奨の makeAssistantToolUI ではなく、"use generative" ツールキットで登録してください。詳しくは tools を参照してください。

クライアント上で 2 つの別々の推論パートが 1 つに統合されてしまう

  • Data Stream のワイヤー上では、推論パートのパート開始フレームは unstable_summary が設定されている場合にのみ送信されます。単なる appendReasoning(text) 呼び出しはテキストデルタとしてしか流れないため、デコーダーには新しいパートが始まったことを知る手がかりが他にありません。サマリーなしの推論パートを連続して 2 つ開くと(たとえばツール呼び出しを挟む場合など)、クライアント上では 1 つの連続した推論パートとして再構築されます。それぞれのパートに unstable_summary を付ける(中身が空に感じられるものでも構いません)か、ツール呼び出しを別のメッセージステップに振り分けて、それぞれを別個に保ってください。

ストリームが UI を更新しない

  • 実際に使用したエンコーダーに対して Content-Type を確認してください。DataStreamEncoder(createAssistantStreamResponse のデフォルト)は text/event-stream ではなく、text/plain; charset=utf-8 と x-vercel-ai-data-stream: v1 を送信します。AssistantTransportEncoder と AI SDK の UI メッセージストリームは text/event-stream を送信します。

デコーダーが「Stream ended abruptly without receiving [DONE] marker」をスローする

  • AssistantTransportDecoder と UIMessageStreamDecoder は終端の [DONE] センチネルを必要とします。ボディをバッファリングしたり切り詰めたりするプロキシ、CDN、ミドルウェアはこれを壊してしまいます。DataStreamDecoder にはそのようなマーカーはありません。

createAssistantStreamResponse は常に Data Stream としてエンコードする

  • これは DataStreamEncoder をハードコードしています。別のワイヤーフォーマットを使う場合は、手動でエンコードしてください: AssistantStream.toResponse(createAssistantStream(callback), new AssistantTransportEncoder())、または createAssistantStreamController を使用して、返されたストリームを自分でエンコードしてください。

関連スキル

  • runtime -- useLocalRuntime、useExternalStoreRuntime、および useAssistantTransportRuntime React フックと state フック
  • setup -- AI SDK のルートハンドラーと useChatRuntime のスキャフォールディング
  • tools -- "use generative" ツールキットとツール呼び出しのレンダリング
  • cloud -- assistant-cloud を使ったストリーミングされたスレッドとメッセージの永続化

コメント