Paperclip架构:Node.js+React构建AI Agent工程化骨架
1. “Paperclip”不是回形针:它是一套面向AI Agent开发的Node.js+React工程化骨架
你搜“paperclip”,第一反应可能是办公桌抽屉里那枚银色小金属——但最近半年,在GitHub趋势榜和前端技术社区里,“Paperclip”已经悄悄变成一个高频代号,专指一类新型AI应用开发框架。它既不是某个具体开源库的官方名称,也不是某家公司的商业产品,而是一群工程师在反复踩坑后,自发沉淀出的一套Node.js后端服务 + React前端界面 + AI Agent逻辑编排三位一体的最小可行架构模式。我第一次见到这个词,是在一个凌晨三点的PR评论里:“这个Agent路由设计太重,建议按paperclip范式拆成stateless handler + context-aware middleware”。当时我愣了三秒——这词没出现在任何npm包名、文档标题或README里,却像暗语一样在十几个AI工具项目中高频复现。
它的核心关键词其实就三个:Node.js做胶水层、React做状态感知界面、AI Agent作为可插拔业务单元。不是用React写个聊天框再接个OpenAI API就叫AI应用,而是让Agent真正具备“记忆”“决策链路”“上下文隔离”“失败回滚”这些生产级能力。比如你做一个会议纪要Agent,它不该只是把录音转文字再总结,而要能识别发言角色、跳过无关寒暄、自动关联上周待办、在生成结果前调用校验函数——这些能力,全靠paperclip结构里的分层设计来承载。
这套模式之所以火,是因为它精准卡在了当前AI开发的“断层带”:LLM API调用简单,但围绕它构建可靠、可调试、可协作的完整工作流极其困难。传统Web架构(REST+DB)扛不住Agent的多步异步、状态漂移、中间态存取;纯客户端方案又无法处理敏感凭证、长时任务和模型编排。Paperclip本质上用Node.js做了个“智能调度中枢”,用React做了个“意图可视化画布”,让AI逻辑不再是一段黑盒prompt,而是一个可观察、可干预、可版本化的模块。它不解决模型本身的问题,但解决了“怎么让模型在真实业务里不掉链子”的问题。
如果你正被这些问题困扰:Agent跑着跑着就丢了上下文、前端刷新后整个对话历史消失、多个Agent同时运行时互相污染状态、调试时根本不知道哪一步prompt出了错——那你不是代码写得不好,而是缺了一套像paperclip这样的基础设施约定。它不强制你用哪个UI库或哪个LLM供应商,但规定了数据怎么流、错误怎么透出、状态怎么快照、日志怎么打点。接下来我会从零开始,带你亲手搭起一个paperclip最小实例,重点不是教你怎么写React组件,而是让你看清每个文件存在的理由、每条通信链路的设计动机、每个配置项背后的权衡取舍。
2. 为什么必须用Node.js做Agent调度层:不是为了“全栈”,而是为了“可控”
很多人看到paperclip架构的第一反应是:“前端直接调OpenAI API不更简单?”——这确实是最快跑通Demo的方式,但也是生产环境里最危险的路径。我见过三个团队用纯前端方案上线AI功能,平均存活时间是17天。问题不出在React或模型上,而出在状态失控和安全裸奔上。下面这张表对比了两种模式在关键维度的真实表现:
| 维度 | 纯前端直连LLM | Node.js调度层(paperclip核心) |
|---|---|---|
| 凭证管理 | API Key硬编码在前端Bundle里,用户F12就能复制 | Key存在服务端环境变量,前端只传递session token |
| 上下文维护 | 全靠useState存对话历史,刷新即丢失,无法跨设备同步 | Redis/MemoryStore持久化会话,支持WebSocket实时同步 |
| 错误隔离 | 一个Agent失败导致整个页面卡死,无降级策略 | 每个Agent运行在独立worker进程,失败不影响其他流程 |
| 审计追踪 | 只有前端console.log,无法追溯prompt原始输入 | 完整记录input/output/timestamp/agent_id,支持ELK接入 |
| 成本控制 | 用户可无限次调用API,无配额限制,账单爆炸 | 后端实现rate limit、token消耗统计、超时熔断 |
这不是技术洁癖,而是血泪教训。去年我们给一家律所做合同审查Agent,初期用纯前端方案,上线第三天发现有用户写了段脚本疯狂调用,单日API费用超预算3倍。更致命的是,当律师在PC端发起审查后,手机端打开同一份合同,看到的却是完全不同的分析结果——因为两个端的本地state完全独立,没有统一的上下文锚点。
Node.js在这里的价值,根本不是“用JavaScript写后端”这么浅层。它提供了四个不可替代的能力:
第一,进程级沙箱隔离。每个Agent实例启动时,我们用child_process.fork()创建独立子进程,传入专用的context对象。这样即使某个Agent因prompt注入崩溃,也不会影响主服务。实测中,一个故意构造的恶意prompt会让子进程内存溢出退出,主进程500ms内自动重启新实例,前端只感知到0.8秒延迟。
第二,状态快照与恢复。Agent执行是多步骤的:1. 解析PDF → 2. 提取条款 → 3. 匹配法条 → 4. 生成风险报告。paperclip约定每个步骤结束时,必须调用ctx.saveCheckpoint({step: 'extract_clauses', data: clauses})。这个checkpoint不是存在localStorage里,而是序列化后存入Redis,key为agent:${sessionId}:checkpoint。当用户中断后重新连接,服务端自动加载最新checkpoint,从第3步继续执行,而不是从头解析PDF。
第三,中间件链式编排。这是paperclip最精妙的设计。我们定义Agent时不是写一个大函数,而是声明一组middleware:
const reviewAgent = createAgent({ name: 'contract-review', middleware: [ validateInput, // 校验PDF是否有效 extractText, // OCR提取文本 detectJurisdiction, // 识别适用法律辖区 applyClauseRules, // 执行200+条法律条款规则 generateReport // 生成人类可读报告 ] })每个middleware接收ctx对象,可以读写ctx.state,调用ctx.next()进入下一步,或ctx.fail('reason')终止流程。这种设计让调试变得极其直观:日志里会清晰打印[contract-review] step=extractText duration=1240ms status=success,而不是笼统的“Agent failed”。
第四,WebSocket长连接管理。React前端用useEffect建立WebSocket连接,但所有消息路由、心跳保活、断线重连都由Node.js层处理。我们封装了一个AgentConnectionManager类,它维护着Map<sessionId, WebSocket>,当Agent需要推送进度时,直接manager.send(sessionId, {type: 'progress', step: 2, percent: 65})。前端只需监听事件,不用操心连接状态。这点在移动端尤其关键——iOS Safari的WebSocket在后台会被系统休眠,paperclip的重连机制能在3秒内自动恢复,用户无感知。
提示:不要用Express原生路由处理Agent请求。我们试过
app.post('/api/agent/run'),结果发现并发高时request body解析成为瓶颈。正确做法是用http.Server直接监听raw socket,用JSONStream流式解析,把body parsing压力分散到每个Agent子进程中。
3. React不是用来“展示AI结果”的,而是构建“意图操作系统”
在paperclip架构里,React的角色常被严重低估。很多人以为它只是个漂亮的UI层,把Agent返回的JSON塞进<div>{result}</div>就完事。实际上,React在这里承担着比后端更复杂的职责——它是整个AI工作流的意图操作系统(Intent OS)。它不负责执行逻辑,但负责理解用户此刻想做什么、正在做什么、可能想做什么,并把这种理解实时反馈给Node.js调度层。
举个具体例子:当用户上传一份合同时,传统做法是显示“上传中...”,等后端返回结果再渲染报告。但在paperclip的React层,你会看到这样的状态机:
// useAgentSession.ts const [session, setSession] = useState<AgentSession>({ status: 'idle', // idle | uploading | processing | reviewing | done progress: 0, currentStep: 'upload', steps: [ { id: 'upload', label: '上传文件', status: 'done' }, { id: 'parse', label: '解析条款', status: 'pending' }, { id: 'check', label: '风险检测', status: 'pending' }, { id: 'report', label: '生成报告', status: 'pending' } ] }) // 当WebSocket收到进度消息时 useEffect(() => { const handleProgress = (data: ProgressEvent) => { setSession(prev => ({ ...prev, progress: data.percent, currentStep: data.step, steps: prev.steps.map(s => s.id === data.step ? { ...s, status: 'active' } : prev.steps.findIndex(t => t.id === data.step) > prev.steps.findIndex(t => t.id === s.id) ? { ...s, status: 'pending' } : { ...s, status: 'done' } ) })) } }, [])这个看似简单的状态更新,背后是精心设计的意图映射协议。React不主动调用API,而是监听来自Node.js的progress事件,根据step字段自动推进状态机。用户看到的不是静态进度条,而是动态演化的操作路径图——这本身就是一种认知减负。当currentStep变为'check'时,界面上会自动展开“风险类型筛选器”,让用户提前选择关注哪些法律风险(违约责任?知识产权?管辖条款?)。这些筛选参数不是在最后一步才收集,而是随着流程自然浮现。
更关键的是,React实现了状态反控(State Back-Propagation)。比如在“风险检测”步骤,用户看到一条高亮提示:“第12条‘不可抗力’定义过于宽泛,建议修改”。旁边有个按钮“→ 修改此处”。点击后,React不会直接发请求,而是触发:
const handleModifyClause = () => { // 构造意图指令,发送给Node.js调度层 socket.send(JSON.stringify({ type: 'intent', sessionId, intent: 'modify_clause', payload: { clauseId: '12', suggestion: '将‘自然灾害’明确为‘地震、洪水、台风’' } })) }Node.js收到这个intent后,会暂停当前Agent流程,启动一个专门的clause-editor子Agent,传入原始PDF坐标和修改建议,生成修订版PDF。整个过程对用户透明,他只觉得“点了就改好了”。
这种设计彻底改变了人机协作模式。传统AI应用是“用户输入→机器输出→用户接受或拒绝”,paperclip模式是“用户输入→机器分步执行→用户在任意步骤介入修正→机器继续执行”。React在这里就是那个敏锐的协作者,它通过useReducer管理复杂状态,用React.memo避免不必要的重渲染(Agent状态更新频繁,但UI局部变化),用Suspense优雅处理异步资源加载(比如动态加载不同法律辖区的规则库)。
注意:不要在React组件里直接调用
fetch请求Agent接口。我们曾犯过这个错误——在useEffect里fetch('/api/agent/run'),结果发现用户快速切换合同文件时,旧请求的响应会覆盖新请求的状态。正确做法是所有Agent交互必须走WebSocket,用sessionId做唯一标识,服务端保证消息顺序性。
4. Agent不是“函数”,而是“有生命周期的实体”:从初始化到销毁的完整链路
在paperclip架构里,最常被误解的概念就是“Agent”。很多人把它当成一个普通函数,const result = await runAgent(input)。但真正的paperclip Agent是一个有完整生命周期的实体对象,它从创建、初始化、执行、暂停、恢复到销毁,每个阶段都有明确契约。理解这一点,是写出可维护Agent逻辑的前提。
我们以一个真实的“会议纪要生成Agent”为例,拆解它的生命周期:
4.1 初始化阶段:Context的构建与注入
Agent启动时,Node.js调度层首先创建一个AgentContext实例:
class AgentContext { public sessionId: string; public startTime: Date; public state: Record<string, any> = {}; // 跨步骤共享状态 public checkpoints: Checkpoint[] = []; // 历史快照 public logs: LogEntry[] = []; // 结构化日志 public timeout: number = 30000; // 全局超时 public abortController: AbortController; constructor(sessionId: string) { this.sessionId = sessionId; this.startTime = new Date(); this.abortController = new AbortController(); // 注入依赖:LLM客户端、向量库、规则引擎 this.llm = new OpenAIClient(process.env.OPENAI_KEY); this.vectorStore = new PineconeStore(process.env.PINECONE_API_KEY); this.ruleEngine = new LegalRuleEngine(); // 预加载200+条法律条款 } saveCheckpoint(data: any) { const checkpoint = { timestamp: new Date(), step: this.state.currentStep, data, memoryUsage: process.memoryUsage().heapUsed }; this.checkpoints.push(checkpoint); // 存入Redis,key: `agent:${this.sessionId}:checkpoint` } fail(reason: string) { this.logs.push({ level: 'error', message: reason, timestamp: new Date() }); this.abortController.abort(); } }这个AgentContext不是全局单例,而是每个Agent实例独享。它封装了所有外部依赖,屏蔽了底层细节。Agent开发者只需关注ctx.state和ctx.saveCheckpoint(),不用操心Redis连接或LLM初始化。
4.2 执行阶段:Middleware链的原子化流转
Agent的执行逻辑被拆解为一系列middleware函数,每个函数必须遵循严格签名:
type Middleware = (ctx: AgentContext, next: () => Promise<void>) => Promise<void>; const extractSpeakers: Middleware = async (ctx, next) => { // 1. 从ctx.state获取音频URL const audioUrl = ctx.state.audioUrl; // 2. 调用语音识别API(带重试) try { const speakers = await speechToText(audioUrl, { retry: 3, timeout: 15000 }); // 3. 更新state,保存checkpoint ctx.state.speakers = speakers; ctx.saveCheckpoint({ speakers }); } catch (err) { ctx.fail(`语音识别失败: ${err.message}`); return; } // 4. 调用next进入下一个middleware await next(); };关键点在于next()的调用时机。它必须在所有异步操作完成后调用,否则后续middleware会拿到不完整的state。我们用TypeScript的Awaited类型约束,确保每个middleware返回Promise。
4.3 暂停与恢复:基于Checkpoint的断点续传
当用户关闭浏览器标签页,或网络中断时,paperclip不是简单地终止Agent,而是触发优雅暂停:
// WebSocket断开时 socket.on('close', () => { // 1. 发送暂停指令给Agent子进程 childProcess.send({ type: 'pause', sessionId }); // 2. 主进程保存当前checkpoint到持久化存储 const latestCheckpoint = ctx.checkpoints[ctx.checkpoints.length - 1]; redis.setex(`agent:${sessionId}:paused`, 3600, JSON.stringify(latestCheckpoint)); });当用户重新连接,前端发送resume请求,Node.js会:
- 从Redis读取
paused数据 - 创建新
AgentContext,用checkpoint数据初始化ctx.state - 启动Agent子进程,从
latestCheckpoint.step对应的middleware开始执行
实测中,一次30分钟的会议纪要生成,中途断网2次,最终完成时间只比连续执行慢47秒——因为OCR和语音识别这些耗时步骤的结果都被完整保留。
4.4 销毁阶段:资源清理与审计归档
Agent完成或失败后,必须执行严格的销毁流程:
const destroyAgent = (ctx: AgentContext) => { // 1. 关闭所有外部连接 ctx.llm.close(); ctx.vectorStore.disconnect(); // 2. 归档日志到S3 const logArchiveKey = `agent-logs/${ctx.sessionId}-${Date.now()}.json`; s3.putObject({ Bucket: 'paperclip-logs', Key: logArchiveKey, Body: JSON.stringify(ctx.logs) }); // 3. 清理Redis临时数据 redis.del(`agent:${ctx.sessionId}:*`); // 4. 发送完成事件给前端 wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'completed', sessionId: ctx.sessionId, result: ctx.state.finalResult, duration: Date.now() - ctx.startTime.getTime() })); } }); };这个销毁函数确保没有内存泄漏,所有审计线索可追溯。我们甚至在销毁前会检查ctx.state是否包含敏感信息(如身份证号、银行卡号),自动脱敏后再归档。
实操心得:Agent middleware里绝对不要用
console.log。我们吃过亏——某个middleware里写了console.log(ctx.state),结果把整个会议录音base64字符串打满日志,单次Agent运行产生2GB日志。正确做法是用ctx.log(level, message, metadata),它会自动截断大字段,只记录关键元数据。
5. 从零搭建paperclip最小可行环境:避开npm install的12个坑
现在我们动手搭建一个paperclip最小实例。不是用脚手架一键生成,而是手动创建每个文件,让你看清每个依赖存在的理由。整个过程在macOS/Linux下验证,Windows用户请确保WSL2已启用。
5.1 目录结构与核心文件
先创建基础目录:
mkdir paperclip-demo && cd paperclip-demo mkdir -p server/src/{agents,middleware,utils} client/src/{hooks,components,lib} touch server/src/index.ts server/src/agents/meeting-agent.ts \ client/src/main.tsx client/src/App.tsx关键原则:前后端分离但共享类型定义。我们在根目录创建shared/types.ts:
// shared/types.ts export interface AgentSession { sessionId: string; status: 'idle' | 'running' | 'paused' | 'completed' | 'failed'; progress: number; currentStep: string; steps: Array<{ id: string; label: string; status: 'done' | 'active' | 'pending' }>; } export interface ProgressEvent { type: 'progress'; sessionId: string; step: string; percent: number; message?: string; } export interface IntentEvent { type: 'intent'; sessionId: string; intent: string; payload: Record<string, any>; }5.2 Node.js服务端:TypeScript + Express + WebSocket
安装服务端依赖:
npm init -y npm install express ws cors dotenv npm install --save-dev typescript ts-node @types/node @types/express @types/ws致命坑1:不要用npm install -g typescript。全局TS版本和项目TS版本不一致会导致tsconfig.json解析错误。必须用npx tsc --init生成项目专属配置。
致命坑2:tsconfig.json必须开启"moduleResolution": "node"和"resolveJsonModule": true。否则无法正确解析JSON配置文件和第三方模块。
server/src/index.ts内容:
import express from 'express'; import http from 'http'; import WebSocket from 'ws'; import cors from 'cors'; import { createServer } from 'http'; import { Server as SocketIOServer } from 'socket.io'; // 解决坑3:WebSocket必须挂载在原生http.Server上,不能挂express.app const app = express(); const server = createServer(app); const wss = new WebSocket.Server({ server }); app.use(cors()); app.use(express.json({ limit: '50mb' })); // 坑4:默认100kb不够传PDF app.use(express.static('../client/dist')); // 坑5:静态文件路径要相对server目录 // 坑6:WebSocket连接数限制。生产环境必须加 wss.on('connection', (ws, req) => { const ip = req.socket.remoteAddress; // 简单IP限频:5分钟内最多10个连接 const key = `ws:${ip}`; redis.incr(key); redis.expire(key, 300); if (parseInt(redis.get(key) || '0') > 10) { ws.close(429, 'Too many connections'); return; } }); // 坑7:不要在WebSocket里直接处理业务逻辑。创建专用AgentManager import { AgentManager } from './lib/agent-manager'; const agentManager = new AgentManager(wss); // 坑8:Express路由必须在WebSocket之后注册,否则CORS预检失败 app.post('/api/agent/start', async (req, res) => { try { const { agentName, input } = req.body; const sessionId = await agentManager.start(agentName, input); res.json({ sessionId }); } catch (err) { res.status(500).json({ error: err.message }); } }); server.listen(3000, () => { console.log('Paperclip server running on http://localhost:3000'); });5.3 React前端:Vite + TypeScript + WebSocket Hook
前端使用Vite而非Create React App,因为其HMR速度更快,且天然支持TypeScript:
npm create vite@latest client -- --template react-ts cd client && npm install # 安装共享类型 npm install --save-dev @types/ws致命坑9:Vite的vite.config.ts必须配置server.proxy。否则WebSocket连接会被浏览器拦截:
// client/vite.config.ts export default defineConfig({ server: { proxy: { '/ws': { target: 'http://localhost:3000', changeOrigin: true, rewrite: (path) => path.replace(/^\/ws/, '') } } } })致命坑10:React WebSocket Hook必须处理重连。原生WebSocket断开后不会自动重连:
// client/src/hooks/useAgentSocket.ts import { useState, useEffect, useRef } from 'react'; import { io, Socket } from 'socket.io-client'; export const useAgentSocket = () => { const [socket, setSocket] = useState<Socket | null>(null); const reconnectTimeout = useRef<NodeJS.Timeout | null>(null); useEffect(() => { const newSocket = io('http://localhost:3000', { transports: ['websocket'], reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000 }); newSocket.on('connect', () => { console.log('Connected to paperclip server'); if (reconnectTimeout.current) { clearTimeout(reconnectTimeout.current); } }); newSocket.on('disconnect', (reason) => { console.log('Disconnected:', reason); // 坑11:立即重连可能失败,加退避 reconnectTimeout.current = setTimeout(() => { newSocket.connect(); }, 2000); }); setSocket(newSocket); return () => { newSocket.close(); if (reconnectTimeout.current) { clearTimeout(reconnectTimeout.current); } }; }, []); return socket; };5.4 Agent逻辑:meeting-agent的完整实现
server/src/agents/meeting-agent.ts:
import { AgentContext } from '../lib/agent-context'; import { Middleware } from '../lib/middleware'; import { speechToText } from '../utils/speech-to-text'; import { summarizeTranscript } from '../utils/summarize'; // 坑12:Agent middleware必须用named export,便于单元测试 export const meetingAgent = { name: 'meeting-summary', middleware: [ async (ctx, next) => { // 步骤1:验证输入 if (!ctx.state.audioUrl) { ctx.fail('缺少音频URL'); return; } await next(); }, async (ctx, next) => { // 步骤2:语音转文字 try { const transcript = await speechToText(ctx.state.audioUrl); ctx.state.transcript = transcript; ctx.saveCheckpoint({ transcript }); } catch (err) { ctx.fail(`语音识别失败: ${err.message}`); return; } await next(); }, async (ctx, next) => { // 步骤3:生成摘要 try { const summary = await summarizeTranscript(ctx.state.transcript); ctx.state.summary = summary; ctx.saveCheckpoint({ summary }); } catch (err) { ctx.fail(`摘要生成失败: ${err.message}`); return; } await next(); } ] as Middleware[] };这个Agent只有3个步骤,但已具备paperclip全部特征:状态管理、checkpoint、错误隔离、进度通知。当你运行npm run dev,前端上传音频文件,后端会分步执行,前端实时显示进度——这就是paperclip的最小闭环。
6. 生产环境部署 checklist:从本地调试到百万QPS的17个必检项
paperclip架构在本地跑通只是第一步。真正考验它价值的,是能否在生产环境稳定支撑业务。我们服务过12个AI产品,总结出这份部署checklist,每一条都来自真实故障。
6.1 Node.js层:进程管理与资源隔离
- ✅ 必检项1:PM2配置必须启用
instances: 'max'和exec_mode: 'cluster'。单进程Node.js无法利用多核,集群模式下每个worker进程独立内存空间,避免GC风暴。 - ✅ 必检项2:设置
--max-old-space-size=4096。LLM推理常吃内存,V8默认1.4GB不够,OOM Killer会直接kill进程。 - ✅ 必检项3:Agent子进程必须设置
ulimit -n 65536。每个WebSocket连接占1个文件描述符,1000并发需至少2000+描述符。 - ✅ 必检项4:Redis连接池大小必须匹配Agent并发数。我们用
ioredis,配置maxRedeliveries: 10, retryStrategy: times => Math.min(times * 50, 2000)防雪崩。
6.2 React前端:构建优化与安全加固
- ✅ 必检项5:Vite构建必须开启
build.rollupOptions.output.manualChunks。把@xenova/transformers(本地LLM库)单独打包,首屏加载不阻塞。 - ✅ 必检项6:添加CSP头
script-src 'self' 'unsafe-eval'。WebAssembly模块需要unsafe-eval,但必须限定在'self'域。 - ✅ 必检项7:WebSocket连接必须用
wss://且证书有效。Chrome 110+对自签名证书WebSocket连接会静默失败。 - ✅ 必检项8:所有用户上传文件必须经过
file-type库校验MIME类型。不能只信<input type="file">的accept属性,攻击者可伪造。
6.3 Agent逻辑:可观测性与容错设计
- ✅ 必检项9:每个middleware必须有
timeout参数。我们用Promise.race([fn(), new Promise(r => setTimeout(r, 10000))])实现10秒超时。 - ✅ 必检项10:LLM调用必须实现指数退避重试。OpenAI API 429错误时,重试间隔为
1s, 2s, 4s, 8s,避免触发永久封禁。 - ✅ 必检项11:所有checkpoint数据必须经过
fast-json-stringify序列化。原生JSON.stringify对大对象性能差,且不支持循环引用。 - ✅ 必检项12:Agent日志必须包含
traceId字段。用uuid.v4()生成,贯穿整个请求链路,便于ELK关联查询。
6.4 基础设施:网络与存储可靠性
- ✅ 必检项13:WebSocket必须配置Nginx
proxy_read_timeout 300。默认60秒,长会议纪要生成会超时断连。 - ✅ 必检项14:Redis必须启用
redis.conf的save 900 1持久化。否则服务器宕机丢失所有未完成Agent状态。 - ✅ 必检项15:S3归档桶必须开启
versioning和lifecycle策略。日志文件自动转为Glacier存储,降低成本。 - ✅ 必检项16:所有环境变量必须用
dotenv-flow管理。.env.production.local覆盖.env.production,避免密钥泄露。 - ✅ 必检项17:健康检查端点
/health必须返回{ "status": "ok", "timestamp": "...", "redis": true, "llm": true }。K8s liveness probe据此判断Pod是否真健康。
这份checklist不是理论清单,而是我们线上事故的逆向工程。比如必检项13,源于一次凌晨3点的告警:所有WebSocket连接在58秒后断开。查Nginx日志发现upstream timed out,才意识到默认proxy_read_timeout是60秒,而会议纪要Agent平均耗时62秒。改配置后,故障率从12%降到0.03%。
部署paperclip不是把代码扔到服务器就完事,而是构建一套可预测、可监控、可回滚的AI运行时环境。每个检查项背后,都是一个曾经让我们彻夜难眠的故障场景。