AI 应用架构设计
2024 年之后,"AI 能力" 已经从锦上添花变成 SaaS 产品的标配。但把 ChatGPT API 包一层皮并不是 AI 应用——真正的 AI 产品需要解决模型选型、上下文管理、流式交互、知识增强、工具调用、成本控制等一系列工程问题。本章从需求分析出发,梳理 AI 应用的技术选型和架构设计,为后续章节(Chat、RAG、Agent、商业化)打下地基。
1. AI 应用需求分析
1.1 AI 应用的三个层次
在 SaaS 产品中嵌入 AI 能力,通常分为三个递进的层次:
| 层次 | 典型功能 | 技术复杂度 | 用户价值 |
|---|---|---|---|
| L1 对话助手 | 智能客服、内容生成、翻译润色 | 低 | 提升效率 |
| L2 知识增强 | 基于企业文档问答、智能搜索、报告分析 | 中 | 数据价值释放 |
| L3 自主代理 | 自动化工作流、多步推理、跨系统操作 | 高 | 流程自动化 |
大多数团队应该从 L1 起步——一个稳定的对话界面 + 流式输出就足以验证 PMF(Product-Market Fit)。过早跳到 L3 会导致可靠性问题:Agent 的每一步推理都有失败概率,五步链式调用的整体成功率可能只有 60%。
1.2 需求拆解框架
在决定技术方案之前,先回答这五个核心问题:
问题一:用户是谁?
- B2B SaaS:用户是企业员工,对准确性要求高,对响应速度容忍度高
- B2C 产品:用户是普通消费者,对体验要求高,对幻觉(Hallucination)容忍度低
- 开发者工具:用户是技术人员,需要精确的代码/API 输出
问题二:输入是什么?
- 纯文本对话:最简单,直接调用 Chat Completion API
- 文档/文件:需要解析(PDF、Word、Excel)+ 分块 + 向量化
- 结构化数据:需要 Text-to-SQL 或数据查询管道
- 多模态:图片理解、语音转文字,模型选型完全不同
问题三:输出是什么?
- 自然语言回答:标准 Chat 场景
- 结构化数据:JSON 输出,需要 Schema 约束(Structured Output)
- 执行动作:调用 API、发送邮件、创建工单——这就是 Agent
问题四:上下文有多大?
- 短上下文(< 4K tokens):单轮问答,简单场景
- 中上下文(4K - 32K):多轮对话 + 少量文档
- 长上下文(32K - 128K+):整本文档分析、长对话历史
上下文窗口直接决定模型选型和成本。128K 上下文的单次调用成本可能是 4K 的 30 倍。
问题五:对延迟的要求?
- 实时交互(< 2s 首 token):需要流式输出 + 快速模型
- 后台处理(分钟级):可以用更强但更慢的模型,批量处理
- 混合模式:简单问题快速回答,复杂问题异步处理
1.3 产品形态矩阵
根据上述问题,常见的 AI 产品形态可以归纳为:
| 产品形态 | 输入 | 输出 | 核心技术 | 代表产品 |
|---|---|---|---|---|
| AI 聊天 | 文本 | 文本流 | Chat Completion + Streaming | ChatGPT, Claude |
| 文档问答 | 文档 + 问题 | 答案 + 引用 | RAG (检索增强生成) | Notion AI, Glean |
| 内容生成 | 提示词 | 长文本/结构化 | Prompt Engineering + Templates | Jasper, Copy.ai |
| 数据分析 | 数据表 + 自然语言 | 图表/SQL/洞察 | Text-to-SQL + Visualization | Julius AI |
| AI Agent | 目标描述 | 多步执行结果 | Tool Use + ReAct | Devin, Cursor |
| AI 工作流 | 触发条件 | 自动化操作 | Workflow + LLM Node | Zapier AI, n8n |
本系列后续章节将逐一实现前四种形态——Chat(第 76 章)、RAG(第 77 章)、Agent(第 78 章),以及商业化(第 79 章)。
2. AI 技术选型
2.1 模型提供商对比
选择 LLM 提供商是第一个决策点。以下是 2024-2025 年的主流选择:
| 提供商 | 代表模型 | 上下文窗口 | 优势 | 劣势 |
|---|---|---|---|---|
| OpenAI | GPT-4o, GPT-4o-mini | 128K | 生态最成熟,工具调用稳定 | 价格较高,数据隐私顾虑 |
| Anthropic | Claude 3.5 Sonnet/Haiku | 200K | 超长上下文,指令遵循优秀 | 工具调用略弱于 GPT-4 |
| Gemini 1.5 Pro/Flash | 1M | 超长上下文,多模态原生 | API 稳定性有波动 | |
| DeepSeek | DeepSeek-V3, R1 | 64K | 性价比极高,推理能力强 | 服务可用性、合规性 |
| 本地部署 | Llama 3, Qwen2.5 | 32K-128K | 数据完全私有,无 API 费用 | 需要 GPU,运维成本高 |
选型建议:
- 快速起步:OpenAI GPT-4o-mini 作为默认模型,成本低、速度快、足够大多数场景
- 高质量需求:GPT-4o 或 Claude 3.5 Sonnet 作为高级模型,用于复杂推理
- 成本敏感:DeepSeek-V3 性价比极高,中文场景效果好
- 数据合规:本地部署 Llama 3 或 Qwen2.5,数据不出内网
2.2 AI SDK 选型
在 Next.js 生态中,Vercel AI SDK 是事实标准。它解决的不是"能不能调 API"的问题,而是"如何优雅地处理流式响应、工具调用、多模型切换"。
| SDK | 特点 | 推荐场景 |
|---|---|---|
Vercel AI SDK (ai) | 框架级集成、Provider 模式、流式原语 | ✅ Next.js 项目首选 |
| LangChain.js | 链式编排、丰富的 Loader/Splitter | RAG 管道复杂时 |
| LlamaIndex.TS | 专注 RAG,索引/检索抽象好 | 重度 RAG 场景 |
| 直接调用 OpenAI SDK | 最大灵活性 | 简单场景或学习用途 |
Vercel AI SDK 的核心优势在于 Provider 模式——同一套代码可以无缝切换 OpenAI、Anthropic、Google 等多个模型提供商:
// 通过不同 Provider 使用不同模型,接口完全一致
import { openai } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'
import { google } from '@ai-sdk/google'
// 三行代码,三个模型提供商
const gpt4o = openai('gpt-4o')
const claude = anthropic('claude-3-5-sonnet-20241022')
const gemini = google('gemini-1.5-pro')这种设计的工程价值在于:当你发现某个模型在特定任务上表现更好(或更便宜),切换成本几乎为零。
2.3 模型路由策略
成熟的 AI 产品不会只用一个模型。根据任务类型动态选择模型,是控制成本和质量的关键策略:
// lib/ai/model-router.ts
import { openai } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'
type TaskType = 'chat' | 'summarize' | 'code' | 'analysis' | 'simple'
const MODEL_MAP: Record<TaskType, ReturnType<typeof openai>> = {
simple: openai('gpt-4o-mini'), // 简单问答:最便宜
chat: openai('gpt-4o'), // 通用对话:平衡
summarize: anthropic('claude-3-5-haiku-20241022'), // 摘要:Haiku 又快又好
code: anthropic('claude-3-5-sonnet-20241022'),// 代码:Sonnet 最强
analysis: openai('gpt-4o'), // 数据分析:GPT-4o 结构化输出稳定
}
export function getModel(task: TaskType) {
return MODEL_MAP[task]
}更进一步,可以基于用户套餐选择模型——免费用户用 mini 模型,付费用户用旗舰模型。这个策略在第 79 章商业化中详细展开。
2.4 向量数据库选型
RAG(检索增强生成)需要向量数据库来存储和检索文档嵌入。主流选择如下:
| 方案 | 类型 | 特点 | 推荐场景 |
|---|---|---|---|
| pgvector | PostgreSQL 扩展 | 复用现有 PG,运维简单 | ✅ 中小规模首选 |
| Pinecone | 托管服务 | 全托管,自动扩展 | 不想运维向量库 |
| Weaviate | 开源向量库 | 混合搜索(向量 + 关键词) | 搜索质量要求高 |
| Qdrant | 开源向量库 | Rust 写的,性能好 | 大规模向量检索 |
| Chroma | 嵌入式向量库 | 最简单,适合原型 | 本地开发/POC |
推荐 pgvector——SaaS 产品已经有 PostgreSQL 了,加一个扩展就能做向量检索,不需要引入新的基础设施。百万级文档以下,pgvector 的性能完全够用。
2.5 Embedding 模型选型
Embedding 模型将文本转换为向量表示,是 RAG 检索质量的关键:
| 模型 | 维度 | 价格 (1M tokens) | 特点 |
|---|---|---|---|
| text-embedding-3-small | 1536 | $0.02 | ✅ 性价比最高 |
| text-embedding-3-large | 3072 | $0.13 | 更高精度 |
| Cohere embed-v3 | 1024 | $0.10 | 多语言优秀 |
| BGE-M3 (本地) | 1024 | 免费 | 中文效果好,需自部署 |
对于大多数 SaaS 场景,OpenAI 的 text-embedding-3-small 足够好且最便宜——1M tokens 只需 $0.02,一本 300 页的书大约 500K tokens,成本不到 $0.01。
3. 架构设计
3.1 整体分层架构
一个完整的 AI SaaS 应用分为五层:
┌──────────────────────────────────────────────────┐
│ 客户端层 │
│ Chat UI ─ Markdown 渲染 ─ 流式显示 ─ 文件上传 │
├──────────────────────────────────────────────────┤
│ API 层 │
│ Route Handlers ─ Server Actions ─ 鉴权 ─ 限流 │
├──────────────────────────────────────────────────┤
│ AI 编排层 │
│ Prompt 管理 ─ 模型路由 ─ 工具调用 ─ RAG 管道 │
├──────────────────────────────────────────────────┤
│ 数据层 │
│ 对话历史(PG) ─ 向量存储(pgvector) ─ 文件存储(S3) │
├──────────────────────────────────────────────────┤
│ 基础设施层 │
│ LLM API ─ Embedding API ─ 队列(Inngest) ─ 缓存 │
└──────────────────────────────────────────────────┘
每一层的职责边界清晰:
- 客户端层:只负责显示和交互,不直接调用 LLM API
- API 层:鉴权、限流、参数校验,是安全边界
- AI 编排层:核心逻辑——Prompt 构建、模型选择、RAG 检索、工具调度
- 数据层:持久化存储,对话历史、向量索引、上传文件
- 基础设施层:外部依赖——LLM 提供商、异步队列、缓存
3.2 Next.js 路由结构
app/
├── (dashboard)/
│ ├── chat/
│ │ ├── page.tsx ← 对话列表
│ │ └── [id]/page.tsx ← 单个对话
│ ├── knowledge/
│ │ ├── page.tsx ← 知识库列表
│ │ └── [id]/page.tsx ← 知识库详情 + 文档管理
│ └── agents/
│ ├── page.tsx ← Agent 列表
│ └── [id]/page.tsx ← Agent 配置
├── api/
│ ├── chat/route.ts ← 对话 API(流式)
│ ├── knowledge/
│ │ ├── upload/route.ts ← 文档上传
│ │ └── query/route.ts ← RAG 查询
│ └── agent/
│ └── run/route.ts ← Agent 执行
lib/
├── ai/
│ ├── models.ts ← 模型配置 + 路由
│ ├── prompts.ts ← System Prompt 管理
│ ├── tools.ts ← 工具定义
│ └── rag/
│ ├── embed.ts ← 文档嵌入
│ ├── retrieve.ts ← 向量检索
│ └── chunk.ts ← 文档分块
3.3 数据模型设计
AI 功能需要的核心数据表:
// lib/db/schema.ts(AI 相关表)
// 对话
export const conversations = pgTable('conversations', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
userId: uuid('user_id').references(() => users.id).notNull(),
title: text('title'), // 自动生成的对话标题
model: text('model').default('gpt-4o-mini'),
systemPrompt: text('system_prompt'),
metadata: jsonb('metadata'), // 额外配置
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
})
// 消息
export const messages = pgTable('messages', {
id: uuid('id').primaryKey().defaultRandom(),
conversationId: uuid('conversation_id')
.references(() => conversations.id, { onDelete: 'cascade' }).notNull(),
role: text('role', { enum: ['user', 'assistant', 'system', 'tool'] }).notNull(),
content: text('content').notNull(),
toolCalls: jsonb('tool_calls'), // 工具调用记录
toolResults: jsonb('tool_results'), // 工具返回结果
tokenCount: integer('token_count'), // 消耗的 token 数
model: text('model'), // 实际使用的模型
latencyMs: integer('latency_ms'), // 响应时间
createdAt: timestamp('created_at').defaultNow().notNull(),
})
// 知识库
export const knowledgeBases = pgTable('knowledge_bases', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
name: text('name').notNull(),
description: text('description'),
embeddingModel: text('embedding_model').default('text-embedding-3-small'),
chunkSize: integer('chunk_size').default(512),
chunkOverlap: integer('chunk_overlap').default(50),
documentCount: integer('document_count').default(0),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
// 文档
export const documents = pgTable('documents', {
id: uuid('id').primaryKey().defaultRandom(),
knowledgeBaseId: uuid('knowledge_base_id')
.references(() => knowledgeBases.id, { onDelete: 'cascade' }).notNull(),
name: text('name').notNull(),
fileKey: text('file_key'), // S3 存储路径
fileSize: integer('file_size'),
mimeType: text('mime_type'),
status: text('status', { enum: ['pending', 'processing', 'ready', 'failed'] })
.default('pending').notNull(),
chunkCount: integer('chunk_count').default(0),
errorMessage: text('error_message'),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
// 文档块 + 向量(需要 pgvector 扩展)
export const documentChunks = pgTable('document_chunks', {
id: uuid('id').primaryKey().defaultRandom(),
documentId: uuid('document_id')
.references(() => documents.id, { onDelete: 'cascade' }).notNull(),
content: text('content').notNull(),
embedding: vector('embedding', { dimensions: 1536 }), // pgvector
metadata: jsonb('metadata'), // 页码、章节等
chunkIndex: integer('chunk_index').notNull(),
})
// Token 用量记录
export const aiUsage = pgTable('ai_usage', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
userId: uuid('user_id').references(() => users.id).notNull(),
model: text('model').notNull(),
inputTokens: integer('input_tokens').notNull(),
outputTokens: integer('output_tokens').notNull(),
cost: numeric('cost', { precision: 10, scale: 6 }), // 美元
operationType: text('operation_type'), // 'chat' | 'embedding' | 'tool'
createdAt: timestamp('created_at').defaultNow().notNull(),
})几个关键的设计决策说明:
- messages 表不嵌套在 conversations 的 JSONB 中:消息可能非常多,独立成表才能高效查询、分页、统计
- documentChunks 使用 pgvector 的
vector类型:PostgreSQL 原生支持向量相似度搜索 - aiUsage 独立成表:用量统计是计费的基础,需要精确记录每一次调用
- 所有表都带 tenantId:多租户隔离,和前几章的 SaaS 架构一致
3.4 关键索引
-- 向量搜索索引(IVFFlat,适合百万级数据)
CREATE INDEX ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 对话查询:按租户 + 用户 + 时间
CREATE INDEX idx_conversations_tenant_user
ON conversations (tenant_id, user_id, created_at DESC);
-- 消息查询:按对话 + 时间
CREATE INDEX idx_messages_conversation
ON messages (conversation_id, created_at ASC);
-- 用量统计:按租户 + 月份
CREATE INDEX idx_ai_usage_tenant_month
ON ai_usage (tenant_id, created_at);3.5 请求生命周期
以一次 AI 对话请求为例,完整的生命周期如下:
用户发送消息
│
▼
① Middleware:鉴权 + Rate Limit
│
▼
② API Route:参数校验 + 获取 tenantId
│
▼
③ 配额检查:当月 token 余量是否足够
│
▼
④ 上下文构建:System Prompt + 历史消息 + RAG 检索结果(可选)
│
▼
⑤ 模型调用:streamText() 流式输出
│
▼
⑥ 流式返回:逐 token 推送到客户端
│
▼
⑦ 完成回调:保存消息 + 记录 token 用量
│
▼
⑧ 异步任务:生成对话标题(首次对话)
其中,步骤 ④ 到 ⑦ 是核心链路,必须确保低延迟。步骤 ⑧ 是异步的,不阻塞用户。
4. 与 SaaS 基座融合
4.1 复用已有基础设施
前面 14 章搭建的 SaaS 基座,在 AI 功能中可以直接复用:
| SaaS 能力 | AI 场景中的用途 |
|---|---|
| 认证系统 | AI API 鉴权,确保只有登录用户才能调用 |
| 多租户 | 对话/知识库按租户隔离 |
| RBAC 权限 | 控制谁可以管理知识库、谁只能对话 |
| 订阅计费 | 按套餐分配 AI 额度(token 配额) |
| 审计日志 | 记录 AI 操作(谁在什么时候问了什么) |
| 文件存储 | 知识库文档上传复用 S3 存储 |
| Feature Flags | 灰度发布 AI 新功能 |
| 后台管理 | 监控 AI 用量、成本、异常 |
这正是"SaaS 基座 + AI 能力"架构的优势——AI 不是一个独立系统,而是在已有基础设施上叠加的能力层。
4.2 权限模型扩展
在已有的 RBAC 基础上,新增 AI 相关权限:
| 权限 | 说明 | owner | admin | member | viewer |
|---|---|---|---|---|---|
ai:chat | 使用 AI 对话 | ✅ | ✅ | ✅ | ❌ |
ai:knowledge:read | 查看知识库 | ✅ | ✅ | ✅ | ✅ |
ai:knowledge:write | 管理知识库(上传/删除文档) | ✅ | ✅ | ❌ | ❌ |
ai:agent:use | 使用 Agent | ✅ | ✅ | ✅ | ❌ |
ai:agent:manage | 创建/编辑 Agent | ✅ | ✅ | ❌ | ❌ |
ai:usage:view | 查看用量统计 | ✅ | ✅ | ❌ | ❌ |
4.3 套餐与 AI 额度绑定
| 套餐 | 月 Token 额度 | 可用模型 | 知识库数 | 文档数/知识库 |
|---|---|---|---|---|
| Free | 50K tokens | gpt-4o-mini | 1 | 10 |
| Pro | 1M tokens | gpt-4o-mini, gpt-4o | 5 | 100 |
| Business | 10M tokens | 全部模型 | 20 | 1,000 |
| Enterprise | 自定义 | 全部 + 私有部署 | 无限 | 无限 |
这种阶梯式的限制既能保证免费用户体验到 AI 能力(促进转化),又能控制成本(50K tokens 大约 $0.01,几乎可以忽略)。
4.4 配置管理
将 AI 相关配置集中管理,便于运维调整:
// lib/ai/config.ts
export const AI_CONFIG = {
// 模型配置
defaultModel: 'gpt-4o-mini',
premiumModel: 'gpt-4o',
embeddingModel: 'text-embedding-3-small',
// RAG 配置
chunkSize: 512, // 分块大小(tokens)
chunkOverlap: 50, // 分块重叠
retrieveTopK: 5, // 检索返回数量
similarityThreshold: 0.7, // 相似度阈值
// 限制
maxMessageLength: 10000, // 单条消息最大字符数
maxHistoryMessages: 50, // 上下文最多保留消息数
maxToolCalls: 10, // 单次对话最多工具调用次数
// 成本(美元/1K tokens)
costs: {
'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
'gpt-4o': { input: 0.0025, output: 0.01 },
'claude-3-5-sonnet': { input: 0.003, output: 0.015 },
'text-embedding-3-small': { input: 0.00002, output: 0 },
} as Record<string, { input: number; output: number }>,
}4.5 安全考量
AI 功能引入了一些独特的安全风险:
| 风险 | 说明 | 缓解措施 |
|---|---|---|
| Prompt 注入 | 用户通过精心构造的输入覆盖 System Prompt | System Prompt 放在最前面 + 输入过滤 |
| 数据泄露 | 模型可能泄露训练数据或其他租户的上下文 | 严格租户隔离,不跨租户共享上下文 |
| 有害内容 | 模型生成违规内容 | OpenAI Moderation API 过滤 |
| 成本攻击 | 恶意用户大量调用消耗 token | Rate Limit + 配额限制 |
| PII 泄露 | 用户上传包含敏感信息的文档 | 提示用户脱敏 + 可选 PII 检测 |
// lib/ai/safety.ts
import OpenAI from 'openai'
const openai = new OpenAI()
export async function moderateContent(text: string): Promise<{
flagged: boolean
categories: string[]
}> {
const result = await openai.moderations.create({ input: text })
const output = result.results[0]
return {
flagged: output.flagged,
categories: Object.entries(output.categories)
.filter(([, flagged]) => flagged)
.map(([category]) => category),
}
}本章小结
- 需求分析:AI 应用分三个层次(对话 → 知识增强 → Agent),从 L1 起步验证 PMF
- 五个核心问题:用户是谁、输入是什么、输出是什么、上下文多大、延迟要求
- 模型选型:GPT-4o-mini 做默认,GPT-4o / Claude Sonnet 做高质量,模型路由按任务动态选择
- SDK 选型:Vercel AI SDK 的 Provider 模式统一多模型接口,切换成本为零
- 向量数据库:pgvector 复用 PostgreSQL,百万级以下足够
- 五层架构:客户端 → API → AI 编排 → 数据 → 基础设施,职责边界清晰
- 数据模型:conversations / messages / knowledgeBases / documents / documentChunks / aiUsage 六张核心表
- SaaS 融合:复用认证、多租户、RBAC、计费、审计、存储、Feature Flags
- 套餐绑定:Free 50K tokens / Pro 1M / Business 10M,阶梯式 AI 额度
- 安全:Prompt 注入防护 + 租户隔离 + 内容审核 + 速率限制 + 成本控制