Skip to content

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 上:

bash
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

  1. 打开 vercel.com,用 GitHub 账号登录
  2. 点 "Add New Project"
  3. 选你刚推的仓库
  4. Vercel 会自动识别 Next.js,什么都不用改,点 "Deploy"

等 1-2 分钟,部署完成。你会拿到一个 https://xxx.vercel.app 的链接。

点开它。你的 Agent 现在在互联网上了。

但先别急着高兴——它现在还跑不了。 因为你还没配环境变量。

第三步:配置环境变量

在 Vercel 项目页面,进 Settings → Environment Variables,添加:

名称说明
DEEPSEEK_API_KEYsk-xxxxxxxxDeepSeek API Key(必需)
MODEL_NAMEdeepseek-chat模型名称(可选,默认 deepseek-chat)
ALLOWED_ORIGINS*CORS 允许的来源(可选)
RATE_LIMIT_MAX_REQUESTS20每窗口最大请求数(可选)
RATE_LIMIT_WINDOW_MS60000Rate limit 窗口毫秒数(可选)

加完之后,重新部署一次(Deployments → 最新一次 → Redeploy)。

安全提醒:永远不要把 API key 提交到 Git。 .env.local 应该在 .gitignore 里。Vercel 的环境变量是加密存储的,只有运行时才能读到。

生产环境做了什么防护

本地开发时,你的 API route 是"裸"的——谁都能发请求,发什么都行。线上不行。

s18 的代码在三个层面加了防护:

1. Middleware:Rate Limiting + CORS

app/middleware.ts 在每个 API 请求到达 route 之前拦截它:

typescript
// 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 都校验输入:

typescript
// 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 把技术错误翻译成用户能看的话:

typescript
// 401 → "服务暂时不可用"
// 429 → "请求太频繁了"
// 500 → "服务出了点问题"
// 开发模式下才显示原始错误

永远不要把内部错误信息原样返回给用户。 错误堆栈里可能有文件路径、数据库结构、API key 前缀。这些都是攻击者想要的信息。

结构化日志

本地开发时 console.log 够用。线上你需要结构化日志——每条日志是一个 JSON 对象,有时间戳、日志级别、请求 ID:

json
{
  "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 做了三件事:

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 章前,你发的第一行代码是这样的:

python
response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "用一句话解释什么是 Agent。"}],
)
print(response.choices[0].message.content)

现在你有了一个完整的系统:

阶段章节你学到了什么
Phase 1s01-s05模型调工具、工具返回结果、结果送回模型。这是 Agent 的核心循环。
Phase 2s06-s10聊天界面、流式输出、Markdown 渲染、工具调用可视化。这是产品化的前端。
Phase 3s11-s15画布编排、条件分支、导入导出、执行日志。这是工作流引擎。
Phase 4s16-s18错误恢复、生产防护、部署上线。这是能跑在互联网上的系统。

从"调通一个 API"到"部署一个生产级 Agent",你走完了整个过程。

Agent 不是什么神秘的东西。 它就是:模型 + 工具 + 循环 + 界面 + 错误处理 + 部署。你已经全部亲手搭过一遍了。

一句话记住

代码写完不叫上线。配好环境变量、加好防护、跑通检查清单——这才叫上线。