Docs/modules/AI 网关

AI 网关

NextShip 内置了一个 AI API 网关,可以代理请求到 AI 服务商(OpenAI、Anthropic 等),并支持基于积分的计费和用量追踪。

功能特性

  • 代理请求到多个 AI 服务商
  • 按请求的积分计费
  • 用量日志和分析
  • 用户 API 密钥管理
  • 速率限制
  • 请求/响应日志记录

架构

客户端请求
     ↓
API 密钥验证
     ↓
积分余额检查
     ↓
AI 服务商 (OpenAI/Anthropic)
     ↓
扣除积分
     ↓
记录用量
     ↓
返回响应给客户端

配置

环境变量

# AI 服务商
OPENAI_API_KEY=sk-xxx
ANTHROPIC_API_KEY=sk-ant-xxx
 
# 可选:自定义基础 URL
OPENAI_BASE_URL=https://api.openai.com/v1
ANTHROPIC_BASE_URL=https://api.anthropic.com

定价配置

API 定价在管理后台 (/api-pricing) 中管理:

// src/lib/db/schema.ts
export const apiPricing = pgTable("api_pricing", {
  id: text("id").primaryKey(),
  provider: text("provider").notNull(),      // "openai", "anthropic"
  model: text("model").notNull(),            // "gpt-4", "claude-3"
  inputPricePerToken: integer("input_price"), // 每 1K token 的积分数
  outputPricePerToken: integer("output_price"),
  isActive: boolean("is_active").default(true),
});

API 路由

聊天补全

// POST /api/v1/chat/completions
// 兼容 OpenAI API 格式
 
const response = await fetch("/api/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer YOUR_API_KEY",
  },
  body: JSON.stringify({
    model: "gpt-4",
    messages: [
      { role: "user", content: "Hello!" }
    ],
  }),
});

路由处理器

// src/app/api/v1/chat/completions/route.ts
export async function POST(req: Request) {
  // 1. 验证 API 密钥
  const apiKey = req.headers.get("authorization")?.replace("Bearer ", "");
  const keyData = await validateApiKey(apiKey);
  if (!keyData) {
    return Response.json({ error: "Invalid API key" }, { status: 401 });
  }
 
  // 2. 检查积分余额
  const balance = await getUserCredits(keyData.userId);
  if (balance <= 0) {
    return Response.json({ error: "Insufficient credits" }, { status: 402 });
  }
 
  // 3. 转发到 AI 服务商
  const body = await req.json();
  const response = await forwardToProvider(body);
 
  // 4. 计算并扣除积分
  const usage = response.usage;
  const cost = calculateCost(body.model, usage);
  await deductCredits(keyData.userId, cost, {
    model: body.model,
    inputTokens: usage.prompt_tokens,
    outputTokens: usage.completion_tokens,
  });
 
  // 5. 记录用量
  await logApiUsage({
    userId: keyData.userId,
    apiKeyId: keyData.id,
    model: body.model,
    inputTokens: usage.prompt_tokens,
    outputTokens: usage.completion_tokens,
    creditsUsed: cost,
  });
 
  return Response.json(response);
}

API 密钥管理

用户可以在 /api-keys 页面创建和管理 API 密钥:

// src/server/actions/api-keys.ts
 
// 创建新的 API 密钥
export async function createApiKey(name: string) {
  const session = await requireAuth();
  const key = `sk-${generateRandomString(48)}`;
 
  await db.insert(apiKeys).values({
    id: crypto.randomUUID(),
    userId: session.user.id,
    name,
    key: hashApiKey(key), // 存储哈希值
    lastUsed: null,
  });
 
  return { key }; // 明文密钥只返回一次
}
 
// 列出用户的 API 密钥
export async function getMyApiKeys() {
  const session = await requireAuth();
  return db.query.apiKeys.findMany({
    where: eq(apiKeys.userId, session.user.id),
    columns: {
      id: true,
      name: true,
      lastUsed: true,
      createdAt: true,
      // 不返回 key(安全考虑)
    },
  });
}
 
// 删除 API 密钥
export async function deleteApiKey(keyId: string) {
  const session = await requireAuth();
  await db.delete(apiKeys).where(
    and(
      eq(apiKeys.id, keyId),
      eq(apiKeys.userId, session.user.id)
    )
  );
}

用量追踪

用量日志 Schema

export const apiUsageLogs = pgTable("api_usage_logs", {
  id: text("id").primaryKey(),
  userId: text("user_id").references(() => users.id),
  apiKeyId: text("api_key_id").references(() => apiKeys.id),
  model: text("model").notNull(),
  inputTokens: integer("input_tokens"),
  outputTokens: integer("output_tokens"),
  creditsUsed: integer("credits_used"),
  createdAt: timestamp("created_at").defaultNow(),
});

查看用量

用户可以在 /api-usage 页面查看 API 用量:

// src/server/actions/api-keys.ts
export async function getMyApiUsageLogs(params: {
  page: number;
  limit: number;
  startDate?: Date;
  endDate?: Date;
}) {
  const session = await requireAuth();
 
  const logs = await db.query.apiUsageLogs.findMany({
    where: and(
      eq(apiUsageLogs.userId, session.user.id),
      // 日期过滤...
    ),
    orderBy: desc(apiUsageLogs.createdAt),
    limit: params.limit,
    offset: (params.page - 1) * params.limit,
  });
 
  return { items: logs, total };
}

管理后台

API 定价页面 (/api-pricing) 允许管理员:

  • 配置每个模型的定价
  • 查看所有用户的 API 用量汇总
  • 启用/禁用特定模型
  • 设置速率限制

积分计算

// src/lib/ai-gateway/pricing.ts
export async function calculateCost(
  model: string,
  usage: { prompt_tokens: number; completion_tokens: number }
) {
  const pricing = await getModelPricing(model);
 
  const inputCost = Math.ceil(
    (usage.prompt_tokens / 1000) * pricing.inputPricePerToken
  );
  const outputCost = Math.ceil(
    (usage.completion_tokens / 1000) * pricing.outputPricePerToken
  );
 
  return inputCost + outputCost;
}

最佳实践

  1. 对 API 密钥进行哈希 - 永远不要存储明文 API 密钥
  2. 速率限制 - 实现每用户的速率限制
  3. 监控用量 - 为异常活动设置告警
  4. 优雅降级 - 处理服务商故障
  5. 审计日志 - 记录所有 API 请求以便调试