メインコンテンツへスキップ

v1.7.1 — Converse Stream API + ストリーミング UX 改善

· 約5分で読めます

マルチルート合成に Bedrock Converse Stream API を導入し、AgentCore Gateway の応答にタイピング効果を適用したリリースです。モニタリングクエリに時間範囲フィルターを追加し、pg pool 枯渇の問題も解決しました。

主な変更点

Converse Stream API · タイピング効果 (50char/15ms) · チャットバブル幅の安定化 · モニタリングクエリ時間フィルター 3 件

Converse Stream API によるリアルタイム合成

マルチルート応答の合成が、擬似チャンキングから本物のトークンストリーミングへ移行しました。

Before / After

項目Before (v1.7.0)After (v1.7.1)
合成 APIInvokeModelCommandConverseStreamCommand
合成関数synthesizeResponses()synthesizeResponsesStreaming()
ストリーミング方式応答全体を生成 → simulateStreaming()トークン生成と同時に chunk イベント送信
初回トークン遅延応答全体の完了まで待機(5〜15 秒)~200ms(最初のトークンを即時送信)
ユーザー体感長い待機の後に高速出力即時開始、自然な流れ

コード変更

BeforesynthesizeResponses() + simulateStreaming():

// src/app/api/ai/route.ts (v1.7.0)
const synthesized = await synthesizeResponses(
lastMsg, successful, modelKey, clientLang
);
// 完成したテキストを 50 文字ずつ分割して擬似ストリーミング
await simulateStreaming(synthesized, send);

AftersynthesizeResponsesStreaming() + ConverseStreamCommand:

// src/app/api/ai/route.ts (v1.7.1)
const synthesized = await synthesizeResponsesStreaming(
lastMsg, successful, send, modelKey, clientLang
);
// send() コールバックが contentBlockDelta ごとに即時 chunk を送信

synthesizeResponsesStreaming() の中核ロジック:

const response = await bedrockClient.send(new ConverseStreamCommand({
modelId,
system: [{ text: systemPrompt }],
messages: [{ role: 'user', content: [{ text: synthesisPrompt }] }],
inferenceConfig: { maxTokens: 4096 },
}));

for await (const event of response.stream) {
if (event.contentBlockDelta?.delta?.text) {
const text = event.contentBlockDelta.delta.text;
fullContent += text;
send('chunk', { delta: text }); // トークンを即時送信
}
}

タイピング効果 (simulateStreaming)

AgentCore Gateway は完成したテキストを一度に返すため、タイピング効果をシミュレートします。

// src/app/api/ai/route.ts
const CHUNK_SIZE = 50; // チャンクあたり 50 文字
const CHUNK_DELAY_MS = 15; // チャンク間 15ms のディレイ

async function simulateStreaming(
text: string,
send: (event: string, data: any) => void,
): Promise<void> {
for (let i = 0; i < text.length; i += CHUNK_SIZE) {
const chunk = text.slice(i, i + CHUNK_SIZE);
send('chunk', { delta: chunk });
if (i + CHUNK_SIZE < text.length) {
await new Promise(r => setTimeout(r, CHUNK_DELAY_MS));
}
}
}

適用対象(3 箇所):

  1. 単一 AgentCore Gateway 応答 — 完成したテキストにタイピング効果を適用
  2. マルチルート合成 (v1.7.0)synthesizeResponses() の結果に適用(v1.7.1 で Converse Stream に置き換え)
  3. マルチルート単一成功 — 1 つの Gateway のみ成功した場合
なぜシミュレーションが必要なのか?

Bedrock の直接呼び出しは InvokeModelWithResponseStreamCommand で本物のストリーミングが可能ですが、AgentCore Gateway は内部でツールを複数回呼び出した後に最終テキストのみを返します。ユーザーに一貫したストリーミング体験を提供するため、50 文字/15ms のタイピング効果を適用しています。

チャットバブル幅の安定化 (PR #8)

SSE ストリーミング中にチャットバブルの幅が突然変わる問題を修正しました。

項目BeforeAfter
CSSmax-w-5xlw-full max-w-5xl
動作コンテンツ増加時に幅がジャンプ最初のチャンクから最大幅に固定
ファイルsrc/app/ai/page.tsx同一

Before — 幅がジャンプ:

<div class="max-w-5xl rounded-lg px-4 py-3 ...">
<!-- コンテンツが短いときは狭く、長くなると突然広がる -->
</div>

After — 安定した幅:

<div class="w-full max-w-5xl rounded-lg px-4 py-3 ...">
<!-- 最初から w-full で最大幅を確保し、max-w-5xl で上限を制限 -->
</div>
CSS パターン

SSE ストリーミングのようにコンテンツが段階的に追加されるコンテナには w-full max-w-{size} の組み合わせを使用してください。 max-w-{size} のみを使用すると、コンテンツ量に応じて幅が変わります。

モニタリングクエリの時間範囲フィルター

時間範囲の制限がないモニタリングクエリが Steampipe API を通じて全履歴データを取得し、pg pool の 5 コネクションをすべて占有してしまう問題を修正しました。

クエリファイルBeforeAfter
ec2CpuHourlyqueries/metrics.ts時間制限なしWHERE timestamp >= NOW() - INTERVAL '24 hours'
ebsIopsHourlyqueries/metrics.tsWHERE r.timestamp IS NOT NULLAND r.timestamp >= NOW() - INTERVAL '24 hours'
rdsCpuDailyqueries/metrics.ts時間制限なしWHERE timestamp >= NOW() - INTERVAL '30 days'
pg Pool 枯渇の症状

46+ の EC2 インスタンス + 多数の EBS ボリュームがある環境では、時間制限のないクエリが数分間コネクションを占有します。 pg pool の max: 5 がすべて使用されると、ダッシュボードとすべてのページが statement_timeout: 120s まで待機した後に失敗します。

3 つのストリーミングモードまとめ

v1.7.1 では、AWSops の AI 応答は状況に応じて 3 つのストリーミングモードを使用します。

モード使用タイミング実装初回トークン遅延
Real StreamingBedrock 直接呼び出しInvokeModelWithResponseStreamCommand~200ms
Converse Streamマルチルート合成ConverseStreamCommand + contentBlockDelta~200ms
Simulated StreamingAgentCore Gateway 応答simulateStreaming()(50 文字/15ms)応答完了後すぐ
統一されたクライアント体験

3 つのモードすべてが同じ SSE chunk イベントを使用するため、クライアント(src/app/ai/page.tsx)はモードを区別せずに streamingContent 状態へ delta を蓄積し、ReactMarkdown でリアルタイムにレンダリングします。