簡介
最後一課——透過監控、警報、成本追蹤和 CI/CD 管道將 AI 代理平台部署到生產中。
1. Docker Compose — 生產堆疊
# docker-compose.yml
services:
server:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@postgres:5432/xclaw
- MONGODB_URL=mongodb://mongo:27017/xclaw
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
depends_on:
postgres:
condition: service_healthy
mongo:
condition: service_started
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
web:
build:
context: ./packages/web
dockerfile: Dockerfile
ports:
- "3001:3001"
postgres:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=xclaw
- POSTGRES_USER=user
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
mongo:
image: mongo:7
volumes:
- mongodata:/data/db
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
mongodata:
redisdata:
2. 監控服務
// packages/core/src/monitoring/monitoring-service.ts
export class MonitoringService {
private db: Db;
async recordLLMUsage(data: {
tenantId: string;
userId: string;
provider: string;
model: string;
promptTokens: number;
completionTokens: number;
latencyMs: number;
cost: number;
}) {
await this.db.collection('llm_usage').insertOne({
...data,
timestamp: new Date(),
});
}
async recordToolExecution(data: {
tenantId: string;
toolName: string;
success: boolean;
durationMs: number;
}) {
await this.db.collection('tool_usage').insertOne({
...data,
timestamp: new Date(),
});
}
async getUsageAnalytics(tenantId: string, days: number = 30) {
const since = new Date(Date.now() - days * 86400000);
const [llmStats, toolStats, costByDay] = await Promise.all([
this.db.collection('llm_usage').aggregate([
{ $match: { tenantId, timestamp: { $gte: since } } },
{ $group: {
_id: '$model',
totalRequests: { $sum: 1 },
totalTokens: { $sum: { $add: ['$promptTokens', '$completionTokens'] } },
totalCost: { $sum: '$cost' },
avgLatency: { $avg: '$latencyMs' },
}},
]).toArray(),
this.db.collection('tool_usage').aggregate([
{ $match: { tenantId, timestamp: { $gte: since } } },
{ $group: {
_id: '$toolName',
totalCalls: { $sum: 1 },
successRate: { $avg: { $cond: ['$success', 1, 0] } },
avgDuration: { $avg: '$durationMs' },
}},
]).toArray(),
this.db.collection('llm_usage').aggregate([
{ $match: { tenantId, timestamp: { $gte: since } } },
{ $group: {
_id: { $dateToString: { format: '%Y-%m-%d', date: '$timestamp' } },
cost: { $sum: '$cost' },
requests: { $sum: 1 },
}},
{ $sort: { _id: 1 } },
]).toArray(),
]);
return { llmStats, toolStats, costByDay };
}
}
3. 結構化日誌記錄
// packages/core/src/logging/logger.ts
export class Logger {
private context: Record<string, unknown>;
constructor(context: Record<string, unknown> = {}) {
this.context = context;
}
child(ctx: Record<string, unknown>) {
return new Logger({ ...this.context, ...ctx });
}
info(message: string, data?: Record<string, unknown>) {
this.log('info', message, data);
}
warn(message: string, data?: Record<string, unknown>) {
this.log('warn', message, data);
}
error(message: string, error?: Error, data?: Record<string, unknown>) {
this.log('error', message, {
...data,
error: error ? {
message: error.message,
stack: error.stack,
name: error.name,
} : undefined,
});
}
private log(level: string, message: string, data?: Record<string, unknown>) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level,
message,
...this.context,
...data,
}));
}
}
// Usage
const logger = new Logger({ service: 'xclaw' });
const chatLogger = logger.child({ component: 'chat' });
chatLogger.info('Chat completed', { sessionId, tokens: 1500, latencyMs: 2340 });
4.健康檢驗API
// packages/gateway/src/routes/health.ts
app.get('/health', async (c) => {
const checks = await Promise.allSettled([
checkPostgres(),
checkMongo(),
checkRedis(),
]);
const status = checks.every(c => c.status === 'fulfilled') ? 'healthy' : 'degraded';
return c.json({
status,
uptime: process.uptime(),
version: process.env.APP_VERSION || 'dev',
checks: {
postgres: checks[0].status === 'fulfilled' ? 'ok' : 'error',
mongo: checks[1].status === 'fulfilled' ? 'ok' : 'error',
redis: checks[2].status === 'fulfilled' ? 'ok' : 'error',
},
timestamp: new Date().toISOString(),
}, status === 'healthy' ? 200 : 503);
});
5. CI/CD 管道
# .github/workflows/deploy.yml
name: Build & Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run test
build-and-push:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /opt/xclaw
docker compose pull
docker compose up -d --remove-orphans
6. 生產清單
| 類別 | 檢查 | 狀態 |
|---|---|---|
| 安全 | JWT 秘密 32+ 位元組 | ○ |
| API 金鑰已加密 | ○ | |
| CORS 限制 | ○ | |
| 速率限制 | ○ | |
| 僅限 HTTPS | ○ | |
| 效能 | 連線池 | ○ |
| 響應流 | ○ | |
| 靜態資產CDN | ○ | |
| 可靠性 | 健康檢查 | ○ |
| 優雅關機 | ○ | |
| 每日資料庫備份 | ○ | |
| 錯誤警報 | ○ | |
| 監控 | 每個租戶的法學碩士成本 | ○ |
| 代幣使用儀表板 | ○ | |
| 錯誤率警報 | ○ | |
| 延遲追蹤(第 50 頁、第 99 頁) | ○ |
7. 回顧系列
透過20堂課,您已經建立了一個完整的AI代理平台:
| 層 | 文章 | 內容 |
|---|---|---|
| 基金會 | 1-4 | 1-4架構、Monorepo、資料庫、API 閘道 |
| 人工智慧核心 | 5-8 | LLM 路由器、工具註冊表、代理、串流媒體 |
| 知識 | 9-11 | 文件處理、嵌入、RAG 引擎 |
| 自動化 | 12-13 | 工作流程引擎、驗證與執行 |
| 擴充 | 14-16 | 技能、網域包、外掛程式和 MCP |
| 訪問 | 17-18 | 17-18多租戶 RBAC、聊天頻道 |
| 生產 | 19-20 | 19-20 React 前端、監控與部署 |