v1.7.1 — Converse Stream API + 流式 UX 改进
本次发布在多路由合成中引入了 Bedrock Converse Stream API,并为 AgentCore Gateway 响应应用了打字效果。同时为监控查询新增了时间范围过滤,解决了 pg pool 耗尽问题。
Converse Stream API · 打字效果(50 字符/15ms) · 聊天气泡宽度稳定化 · 3 项监控查询时间过滤
借助 Converse Stream API 实现实时合成
多路由响应合成从伪分块转换为真正的令牌流式传输。
Before / After
| 项目 | Before (v1.7.0) | After (v1.7.1) |
|---|---|---|
| 合成 API | InvokeModelCommand | ConverseStreamCommand |
| 合成函数 | synthesizeResponses() | synthesizeResponsesStreaming() |
| 流式方式 | 生成完整响应 → simulateStreaming() | 令牌生成后立即发送 chunk 事件 |
| 首个令牌延迟 | 等待整个响应完成(5~15 秒) | ~200ms(首个令牌立即到达) |
| 用户体感 | 长时间等待后快速输出 | 立即开始,流畅自然 |
代码变更
Before — synthesizeResponses() + simulateStreaming():
// src/app/api/ai/route.ts (v1.7.0)
const synthesized = await synthesizeResponses(
lastMsg, successful, modelKey, clientLang
);
// 将完成的文本按 50 字符切分做伪流式传输
await simulateStreaming(synthesized, send);
After — synthesizeResponsesStreaming() + 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 处):
- 单个 AgentCore Gateway 响应 — 对完整文本应用打字效果
- 多路由合成(v1.7.0) — 应用于
synthesizeResponses()结果(v1.7.1 中已由 Converse Stream 替代) - 多路由单一成功 — 仅有 1 个 Gateway 成功的情况
Bedrock 直接调用可以通过 InvokeModelWithResponseStreamCommand 实现真正的流式传输,但 AgentCore Gateway 会在内部多次调用工具后只返回最终文本。为了给用户提供一致的流式体验,我们应用了 50 字符/15ms 的打字效果。
聊天气泡宽度稳定化(PR #8)
修复了 SSE 流式传输过程中聊天气泡宽度突然变化的问题。
| 项目 | Before | After |
|---|---|---|
| CSS | max-w-5xl | w-full max-w-5xl |
| 行为 | 内容增加时宽度跳变 | 从首个 chunk 起固定为最大宽度 |
| 文件 | 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>
对于像 SSE 流式传输这样内容逐步增加的容器,请使用 w-full max-w-{size} 组合。
只使用 max-w-{size} 时,宽度会随内容量而变化。
监控查询时间范围过滤
修复了没有时间范围限制的监控查询通过 Steampipe API 拉取全部历史数据、占满 pg pool 全部 5 个连接的问题。
| 查询 | 文件 | Before | After |
|---|---|---|---|
ec2CpuHourly | queries/metrics.ts | 无时间限制 | WHERE timestamp >= NOW() - INTERVAL '24 hours' |
ebsIopsHourly | queries/metrics.ts | WHERE r.timestamp IS NOT NULL | AND r.timestamp >= NOW() - INTERVAL '24 hours' |
rdsCpuDaily | queries/metrics.ts | 无时间限制 | WHERE timestamp >= NOW() - INTERVAL '30 days' |
在 46+ 台 EC2 实例 + 大量 EBS 卷的环境中,无时间限制的查询会占用连接长达数分钟。
当 pg pool 的 max: 5 全部被占用时,仪表盘和所有页面都会等待到 statement_timeout: 120s 后失败。
3 种流式模式概览
在 v1.7.1 中,AWSops AI 响应会根据场景使用 3 种流式模式。
| 模式 | 使用时机 | 实现 | 首个令牌延迟 |
|---|---|---|---|
| Real Streaming | Bedrock 直接调用 | InvokeModelWithResponseStreamCommand | ~200ms |
| Converse Stream | 多路由合成 | ConverseStreamCommand + contentBlockDelta | ~200ms |
| Simulated Streaming | AgentCore Gateway 响应 | simulateStreaming()(50 字符/15ms) | 响应完成后立即 |
由于 3 种模式都使用相同的 SSE chunk 事件,客户端(src/app/ai/page.tsx)无需区分模式,只需将 delta 累加到 streamingContent 状态中,并通过 ReactMarkdown 实时渲染。