s18 - 部署上线
前面 17 章,你从一行 API 调用开始,搭出了一个能对话、能调工具、能编排工作流、能处理错误的 Agent。但它一直跑在你的
localhost:3000上。 这一章的目标:把它推到互联网上,让任何人都能用。
这一章要解决什么问题
你的 Agent 在本地跑得很好。但 "在本地跑" 和 "上线" 之间隔着一整条沟:
- 本地用
.env.local存 API key,线上怎么办? - 本地没人攻击你,线上有人会刷你的接口
- 本地出了错你看终端,线上出了错你看什么?
- 本地
next dev跑的是开发模式,线上需要next build
这一章要把这些全部解决。
为什么用 Vercel
Next.js 是 Vercel 做的,部署到 Vercel 是零配置的。免费 tier 够用:
- 100GB 带宽/月
- Serverless Functions 自动扩缩
- 自动 HTTPS
- 推一次代码就部署
不需要你买服务器、配 Nginx、管 SSL 证书。git push 就完事了。
第一步:推代码到 GitHub
Vercel 从 GitHub 拉代码。先确保你的项目在 GitHub 上:
cd your-project
git init
git add .
git commit -m "ready to deploy"
git remote add origin https://github.com/yourname/build-an-agent.git
git push -u origin main第二步:连接 Vercel
- 打开 vercel.com,用 GitHub 账号登录
- 点 "Add New Project"
- 选你刚推的仓库
- Vercel 会自动识别 Next.js,什么都不用改,点 "Deploy"
等 1-2 分钟,部署完成。你会拿到一个 https://xxx.vercel.app 的链接。
点开它。你的 Agent 现在在互联网上了。
但先别急着高兴——它现在还跑不了。 因为你还没配环境变量。
第三步:配置环境变量
在 Vercel 项目页面,进 Settings → Environment Variables,添加:
| 名称 | 值 | 说明 |
|---|---|---|
DEEPSEEK_API_KEY | sk-xxxxxxxx | DeepSeek API Key(必需) |
MODEL_NAME | deepseek-chat | 模型名称(可选,默认 deepseek-chat) |
ALLOWED_ORIGINS | * | CORS 允许的来源(可选) |
RATE_LIMIT_MAX_REQUESTS | 20 | 每窗口最大请求数(可选) |
RATE_LIMIT_WINDOW_MS | 60000 | Rate limit 窗口毫秒数(可选) |
加完之后,重新部署一次(Deployments → 最新一次 → Redeploy)。
安全提醒:永远不要把 API key 提交到 Git。 .env.local 应该在 .gitignore 里。Vercel 的环境变量是加密存储的,只有运行时才能读到。
生产环境做了什么防护
本地开发时,你的 API route 是"裸"的——谁都能发请求,发什么都行。线上不行。
s18 的代码在三个层面加了防护:
1. Middleware:Rate Limiting + CORS
app/middleware.ts 在每个 API 请求到达 route 之前拦截它:
// Rate Limiting:同一 IP 每分钟最多 20 次请求
const { allowed, remaining } = checkRateLimit(ip);
if (!allowed) {
return new Response("请求过于频繁", { status: 429 });
}
// CORS:控制谁能调你的 API
const response = NextResponse.next();
response.headers.set("Access-Control-Allow-Origin", origin);Rate limit 用内存 Map 实现,够小项目用。如果以后用户量上来,换 Redis。
2. API Routes:输入校验
每个 API route 都校验输入:
// chat route:消息数量和长度
if (messages.length > 50) {
return { error: "消息数量超过上限" };
}
if (msg.content.length > 8000) {
return { error: "单条消息超过 8000 字符上限" };
}
// execute-tool route:工具名只允许字母数字下划线
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tool)) {
return { error: "工具名格式非法" };
}不校验的后果:有人可以发一个 100MB 的消息让你的 serverless function 内存爆掉,或者注入一个特殊路径读你的系统文件。
3. ErrorBanner:错误信息脱敏
s17 的 ErrorBanner 把技术错误翻译成用户能看的话:
// 401 → "服务暂时不可用"
// 429 → "请求太频繁了"
// 500 → "服务出了点问题"
// 开发模式下才显示原始错误永远不要把内部错误信息原样返回给用户。 错误堆栈里可能有文件路径、数据库结构、API key 前缀。这些都是攻击者想要的信息。
结构化日志
本地开发时 console.log 够用。线上你需要结构化日志——每条日志是一个 JSON 对象,有时间戳、日志级别、请求 ID:
{
"level": "info",
"message": "执行工具",
"timestamp": "2025-05-28T10:30:00.000Z",
"context": {
"requestId": "req_m1abc_xyz123",
"tool": "read_file",
"args": { "path": "README.md" }
}
}app/utils/logger.ts 做了这件事。每个请求生成一个唯一 ID,贯穿整条调用链。出了问题,你在 Vercel 的 Logs 面板搜 requestId,就能看到这个请求的完整过程。
Vercel 免费 tier 保留最近 1 天的日志。如果需要更长的保留时间,接 Vercel Log Drain 推到第三方日志服务(Axiom、Better Stack 等)。
Vercel 配置
vercel.json 做了三件事:
{
"regions": ["hkg1"],
"functions": {
"app/api/chat/route.ts": { "maxDuration": 60 }
},
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" }
]
}- regions:指定部署到香港机房,国内访问延迟低
- functions.maxDuration:chat route 最多跑 60 秒(free tier 上限),防止长时间占用
- headers:安全响应头,防 MIME 嗅探和 clickjacking
生产检查清单
部署前过一遍这个清单:
- [ ]
.env.local在.gitignore里,API key 没有提交到 Git - [ ] Vercel 环境变量已配置(至少
DEEPSEEK_API_KEY) - [ ] 所有 API route 有输入校验(消息长度、数量、格式)
- [ ] Rate limiting 开启(middleware.ts)
- [ ] CORS 配置正确(生产环境不要用
*,指定你的域名) - [ ] 错误信息已脱敏(ErrorBanner 不暴露内部细节)
- [ ]
vercel.json配了maxDuration(别让一个请求跑 300 秒) - [ ] 安全响应头已加(X-Content-Type-Options, X-Frame-Options)
- [ ]
next build本地能跑过(没有 TypeScript 错误) - [ ] 日志有 requestId,方便排查问题
后续:你可能想做的事
部署上线不是终点。当你把 Agent 给别人用之后,你会很快发现新的需求:
- 用户认证:现在谁都能用,你需要登录系统
- 数据库:对话历史存在内存里,刷新就没了
- 用量统计:每个用户调了多少次 API,花了多少钱
- 流式输出优化:Vercel Edge Runtime 比 Node.js 更适合 SSE
- 多模型切换:让用户选 DeepSeek / GPT / Claude
这些超出了本教程的范围,但你现在有了一个清晰的起点。
教学边界
这一章只做一件事:把 Agent 部署到生产环境。
不涉及:
- 用户认证(OAuth、JWT)
- 数据库集成(PostgreSQL、Redis)
- CI/CD 流水线(GitHub Actions)
- 多环境管理(staging、production)
- 自定义域名配置
- 监控告警(Sentry、Datadog)
这些是"上线之后"的事。你现在已经知道怎么上线了。
回顾:从 s01 到 s18
18 章前,你发的第一行代码是这样的:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "用一句话解释什么是 Agent。"}],
)
print(response.choices[0].message.content)现在你有了一个完整的系统:
| 阶段 | 章节 | 你学到了什么 |
|---|---|---|
| Phase 1 | s01-s05 | 模型调工具、工具返回结果、结果送回模型。这是 Agent 的核心循环。 |
| Phase 2 | s06-s10 | 聊天界面、流式输出、Markdown 渲染、工具调用可视化。这是产品化的前端。 |
| Phase 3 | s11-s15 | 画布编排、条件分支、导入导出、执行日志。这是工作流引擎。 |
| Phase 4 | s16-s18 | 错误恢复、生产防护、部署上线。这是能跑在互联网上的系统。 |
从"调通一个 API"到"部署一个生产级 Agent",你走完了整个过程。
Agent 不是什么神秘的东西。 它就是:模型 + 工具 + 循环 + 界面 + 错误处理 + 部署。你已经全部亲手搭过一遍了。
一句话记住
代码写完不叫上线。配好环境变量、加好防护、跑通检查清单——这才叫上线。