一周备稿 · 2026-08-11 · 11:30
上篇:
articles/drafts/w2-agent-memory.md相关阅读:Streaming UI 工程化 · Tool Calling 前端怎么接
下篇预告:Embedding 怎么选
把 Chat UI 接上模型 API,Demo 十分钟能跑。上线后第一批事故往往出在 Route Handler:请求超时、流式断连、鉴权漏口、Serverless 冷启动把 P99 拉爆。
先给结论:Route Handler 只做「鉴权 + 限流 + 流式转发 + 观测」;长工具调用放 Queue,密钥永不进客户端,超时分层设而不是一个 60s 打天下。
你将学到
- App Router 下
/api/chat的标准职责边界 - 超时怎么分层:平台、Route、上游模型、工具
- SSE / AI SDK 流式响应的可复制骨架
- Session 鉴权与 API Key 隔离
- 踩坑:Edge Runtime、Body 大小、AbortSignal
- 与 Vercel / 自托管的差异点
一、先结论:Handler 是网关,不是业务全集
| 层 | 做什么 | 不做什么 |
|---|---|---|
| Route Handler | 验用户、限流、调 LLM、流式返回 | 复杂 Agent 编排(可下沉 Worker) |
| Server Action | 表单、轻量 mutation | 长时流式对话 |
| 客户端 | UI、Abort、重连展示 | 持有 API Key |
| 后台 Worker | 慢工具、批处理、回调 | 直接暴露公网 |
text
Browser → POST /api/chat (Cookie/JWT)
→ 鉴权 + rate limit
→ streamText(model, messages)
→ SSE chunks → UI
→ (可选)异步写 Session 记忆
二、最小 Route Handler(AI SDK)
ts
// app/api/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { auth } from "@/lib/auth";
import { rateLimit } from "@/lib/rate-limit";
export const runtime = "nodejs"; // 见踩坑:慎选 edge
export const maxDuration = 60; // Vercel Pro 可调更高
export async function POST(req: Request) {
const user = await auth(req);
if (!user) return new Response("Unauthorized", { status: 401 });
const limited = await rateLimit(user.id);
if (!limited.ok) return new Response("Too Many Requests", { status: 429 });
const { messages } = await req.json();
if (!Array.isArray(messages) || messages.length > 50) {
return new Response("Bad Request", { status: 400 });
}
const result = streamText({
model: openai("gpt-4o-mini"),
messages,
abortSignal: req.signal, // 客户端断开时取消上游
maxTokens: 2048,
});
return result.toDataStreamResponse();
}
这段代码刻意 不包含工具执行------工具应在服务端单独模块调用,便于单测与超时控制。
三、超时:四层别混为一谈
| 层级 | 典型值 | 说明 |
|---|---|---|
| CDN / 负载均衡 | 30--120s | 流式需关闭缓冲或调大 |
maxDuration(Vercel) |
10--300s | Hobby 上限低,生产要 Pro |
| 模型 API | 按厂商默认 | 可用 abortSignal 提前砍 |
| 工具调用 | 5--30s / 工具 | 超时则 tool_error 回灌,勿拖死整段流 |
长任务模式 :Route 只返回 jobId,Worker 跑完通过 SSE / WebSocket 推送------下篇系列会讲 Human-in-the-loop 确认。
ts
// 工具层单独 timeout
async function callTool(name: string, args: unknown) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 15_000);
try {
return await execTool(name, args, ctrl.signal);
} finally {
clearTimeout(timer);
}
}
四、流式:断连、重试与 Heartbeat
客户端 Abort
用户点「停止」→ AbortController.abort() → fetch 断开 → Route 的 req.signal 触发 → 上游取消。省 Token 也省算力。
代理缓冲
Nginx 默认可能缓冲 SSE。自托管时:
nginx
location /api/chat {
proxy_buffering off;
proxy_read_timeout 300s;
chunked_transfer_encoding on;
}
错误 mid-stream
流式中途失败时,AI SDK 会在 data stream 里带 error part;前端要能渲染「生成中断,可重试」,而不是白屏。
五、鉴权:三种常见模式
| 模式 | 适用 | 注意 |
|---|---|---|
| Cookie Session | 同源 Web App | CSRF:POST + SameSite |
| JWT Bearer | 移动 / 第三方 | 短 TTL + refresh |
| API Key(服务端) | B2B | 按 Key 限流,不进浏览器 |
红线 :OPENAI_API_KEY 只存在于 Server 环境变量;客户端最多拿 Session Token。
ts
// lib/auth.ts --- 最小示例
export async function auth(req: Request) {
const token = req.headers.get("authorization")?.replace("Bearer ", "")
?? parseCookie(req.headers.get("cookie") ?? "");
if (!token) return null;
return verifySession(token); // 失败返回 null
}
租户隔离
多租户 SaaS 在 Handler 入口校验 user.tenantId,后续查库、查向量库都带租户条件------别只靠 Prompt 里写「你是 A 公司助手」。
六、观测与限流
生产 Handler 至少打这些字段:
ts
console.log(JSON.stringify({
event: "chat_route",
userId: user.id,
model: "gpt-4o-mini",
msgCount: messages.length,
latencyMs: Date.now() - t0,
aborted: req.signal.aborted,
}));
限流维度建议:userId + IP 双轨;免费档按日 Token 封顶,别等账单报警才想起来。
七、踩坑清单
runtime = "edge"调不通某些 Node SDK ------ AI 路由默认nodejs更稳- 一个 60s 超时套工具 + 生成长文 ------ 必然误杀,要分层
- 在 Handler 里同步跑爬虫 / SQL 慢查询 ------ 阻塞 Event Loop,改 Queue
- 未校验 messages 体积 ------ 恶意大包打爆内存
- 流式响应被中间层 gzip 缓冲 ------ 用户看「卡很久才出一坨」
- 把 Session 记忆写进 Handler 主路径 ------ 应用 after 流结束异步写
相关阅读
| 主题 | 链接 |
|---|---|
| Streaming 工程 | 163041834 |
| Tool UI | 163083715 |
| Human-in-the-loop | articles/drafts/w2-human-in-loop.md |
| Embedding 选型 | articles/drafts/w2-embedding-choose.md |
小结
Next.js AI Route Handler 的工程化核心是 网关思维 :鉴权、限流、流式转发、可取消、可观测。
超时按层设置,长工具别堵在 Handler 里;密钥留服务端,Abort 贯通客户端到模型。
Demo 能跑只是起点,P99 和账单才是分水岭。
标签:Next.js · AI SDK · Route Handler · 流式 · 鉴权