Multi-tenant platform design with unified LLM provider abstraction, RAG knowledge base, and tenant-isolated data storage. MVP uses SQLite + ChromaDB, with planned migration to PostgreSQL + pgvector. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
22 KiB
22 KiB
数字员工平台设计文档
日期:2026-05-05 状态:已批准
1. 背景与目标
1.1 背景
企业对 AI 数字员工的需求日益增长,但不同企业的业务场景、知识体系、合规要求各不相同。现有解决方案要么是通用聊天机器人(缺乏企业定制),要么是单点 SaaS 产品(数据隔离弱、模型选择受限)。需要一个多租户平台,让每家企业能配置专属数字员工,接入自选 LLM,使用私有知识库。
1.2 目标
- 构建多租户数字员工平台,支持企业级数据隔离与独立计费
- 统一 LLM Provider 抽象层,企业可自选模型提供商(OpenAI / 通义千问 / 可扩展)
- RAG 知识库支持企业私有文档上传与检索增强生成
- 提供管理后台与对话前端,形成完整可演示链路
1.3 成功标准
| 指标 | MVP 目标 |
|---|---|
| 租户隔离 | 企业间数据零泄露,API Key 独立计费 |
| 对话延迟 | 首 token 响应 < 3s(不含 LLM 调用开销) |
| RAG 准确性 | 知识库内问题 Top-3 召回率 > 80% |
| 可演示性 | 管理后台 + 对话界面端到端可用 |
| Provider 扩展 | 新增 Provider 实现工作量 < 1 天 |
2. 范围
2.1 MVP 范围(P0)
- 企业租户管理(CRUD + API Key 配置)
- 数字员工人设配置(角色、语气、专业知识描述)
- 对话接口(实时聊天,SSE 流式响应)
- RAG 知识库(上传文档 → 分块嵌入 → 检索增强生成)
- 对话历史存储与查询
2.2 MVP 基础版(P1)
- 管理后台 UI(企业管理、员工配置、对话监控)
- 对话前端 UI(聊天界面)
2.3 后续迭代(P2)
- 用量统计 / 计费
- 权限控制(企业管理员 vs 普通用户)
2.4 拓展功能(P3,按价值排序)
- 对话质量评估:自动评估 RAG 回答的准确性与相关性
- 数字员工模板市场:预置客服/HR/销售等角色模板,一键启用
- 多渠道接入:微信/钉钉/飞书/Web 统一对话接口
- 审批工作流:敏感操作前需人类确认
- 审计日志与合规:完整对话审计链路
2.5 非目标
- 自训练/微调模型
- 实时语音对话
- 多模态输入(图片/视频)
- 移动端原生 App
3. 方案对比
3.1 方案 A:轻量直连架构
FastAPI → Provider 抽象层 → OpenAI/通义千问 API
→ SQLite + ChromaDB(嵌入式向量库)
→ 文件存储(对话历史)
- 零外部依赖,
pip install即跑 - 开发周期 1-2 周
- 单机部署,不适合大规模
3.2 方案 B:LangChain 编排架构
FastAPI → LangChain/LangGraph → 多 LLM Provider
→ PostgreSQL + pgvector
→ Redis(缓存/会话)
- LangChain 500+ 集成,LangGraph 支持多步 Agent
- 重依赖,升级频繁有 breaking change
- 生产维护成本高
3.3 方案 C:自研编排 + PostgreSQL 全栈
FastAPI → 自研 Provider 抽象 + RAG Pipeline
→ PostgreSQL + pgvector(统一存储)
→ Redis(会话/缓存)
- 完全可控,无重框架依赖
- PostgreSQL 统一关系数据 + 向量检索
- RAG pipeline 需自建,初始工作量较大
3.4 决策:方案 A 先行,演进到方案 C
| 阶段 | 存储 | 向量库 | 缓存 | 理由 |
|---|---|---|---|---|
| MVP | SQLite | ChromaDB | 无 | 零外部依赖,快速验证 |
| V1 | PostgreSQL | pgvector | Redis | 生产级,统一存储 |
4. 推荐方案详细设计
4.1 系统架构总览
┌─────────────────────────────────────────────────────────┐
│ 前端层 (TypeScript) │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ 管理后台 UI │ │ 对话前端 UI │ │ 模板市场 UI │ │
│ └──────┬───────┘ └──────┬───────┘ └───────┬───────┘ │
└─────────┼─────────────────┼──────────────────┼──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ API 网关层 (FastAPI) │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────┐ │
│ │ 租户管理 │ │ 员工配置 │ │ 对话接口 │ │ 知识库管理 │ │
│ └──────────┘ └───────────┘ └──────────┘ └───────────┘ │
└─────────────────────┬───────────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────────┐ ┌─────────────────────┐
│ 对话编排层 │ │ RAG Pipeline │
│ ┌─────────────┐ │ │ ┌─────────────────┐ │
│ │ 会话管理 │ │ │ │ 文档解析/分块 │ │
│ │ Prompt 构建 │ │ │ │ 嵌入生成 │ │
│ │ 流式响应 │ │ │ │ 向量检索 │ │
│ └─────────────┘ │ │ │ 上下文注入 │ │
│ ┌─────────────┐ │ │ └─────────────────┘ │
│ │ Provider │ │ └─────────────────────┘
│ │ 抽象层 │ │
│ │ ┌────────┐ │ │
│ │ │ OpenAI │ │ │
│ │ │ 通义 │ │ │
│ │ │ ... │ │ │
│ │ └────────┘ │ │
│ └─────────────┘ │
└──────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 存储层 │
│ MVP: SQLite + ChromaDB + 文件存储 │
│ V1: PostgreSQL + pgvector + Redis │
└─────────────────────────────────────────────────────────┘
4.2 核心模块设计
4.2.1 租户管理模块
数据模型:
class Tenant:
id: UUID # 租户 ID
name: str # 企业名称
slug: str # URL 标识(唯一)
status: TenantStatus # active / suspended / deleted
created_at: datetime
updated_at: datetime
class TenantConfig:
id: UUID
tenant_id: UUID # 关联租户
llm_provider: str # openai / qwen / anthropic
llm_api_key: str # 加密存储的 API Key
llm_model: str # gpt-4o / qwen-max / ...
llm_base_url: str | None # 自定义 API 端点(可选)
max_tokens_per_month: int # 用量限制
created_at: datetime
updated_at: datetime
API 端点:
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/tenants |
创建租户 |
| GET | /api/v1/tenants/{id} |
获取租户详情 |
| PUT | /api/v1/tenants/{id} |
更新租户 |
| DELETE | /api/v1/tenants/{id} |
删除租户(软删除) |
| PUT | /api/v1/tenants/{id}/config |
更新 LLM 配置 |
安全约束:
- API Key 使用 AES-256 加密存储,密钥从环境变量读取
- 租户间数据严格隔离,所有查询强制带
tenant_id过滤 - 软删除策略,删除后 30 天可恢复
4.2.2 数字员工配置模块
数据模型:
class DigitalEmployee:
id: UUID
tenant_id: UUID # 所属租户
name: str # 员工名称(如"客服小助手")
role: str # 角色:customer_service / hr / sales / ...
avatar_url: str | None # 头像
system_prompt: str # 人设系统提示词
greeting: str | None # 开场白
temperature: float # 生成温度 0.0-1.0
max_context_messages: int # 上下文窗口消息数
knowledge_base_ids: list[UUID] # 关联知识库
status: EmployeeStatus # active / inactive
created_at: datetime
updated_at: datetime
API 端点:
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/tenants/{tid}/employees |
创建数字员工 |
| GET | /api/v1/tenants/{tid}/employees |
列出数字员工 |
| GET | /api/v1/tenants/{tid}/employees/{eid} |
获取详情 |
| PUT | /api/v1/tenants/{tid}/employees/{eid} |
更新配置 |
| DELETE | /api/v1/tenants/{tid}/employees/{eid} |
删除 |
4.2.3 对话接口模块
对话流程:
用户消息
→ 会话加载(历史上下文)
→ RAG 检索(如果员工关联了知识库)
→ 查询向量化
→ ChromaDB/pgvector 相似度检索 Top-K
→ 上下文注入到 Prompt
→ Prompt 构建(system + 知识上下文 + 历史消息 + 用户消息)
→ LLM Provider 调用(流式)
→ SSE 流式响应
→ 持久化对话记录
数据模型:
class Conversation:
id: UUID
tenant_id: UUID
employee_id: UUID
user_id: str # 外部用户标识
title: str | None
created_at: datetime
updated_at: datetime
class Message:
id: UUID
conversation_id: UUID
role: MessageRole # user / assistant / system
content: str
token_count: int | None
sources: list[dict] | None # RAG 引用来源
created_at: datetime
API 端点:
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/tenants/{tid}/employees/{eid}/conversations |
创建对话 |
| GET | /api/v1/tenants/{tid}/conversations |
列出对话 |
| GET | /api/v1/tenants/{tid}/conversations/{cid} |
获取对话详情(含消息) |
| POST | /api/v1/tenants/{tid}/conversations/{cid}/messages |
发送消息(SSE 流式响应) |
| DELETE | /api/v1/tenants/{tid}/conversations/{cid} |
删除对话 |
SSE 响应格式:
event: message_start
data: {"conversation_id": "...", "message_id": "..."}
event: token
data: {"content": "你"}
event: token
data: {"content": "好"}
event: sources
data: {"documents": [{"title": "...", "snippet": "...", "score": 0.92}]}
event: message_end
data: {"token_count": 156}
event: error
data: {"code": "provider_error", "message": "..."}
4.2.4 RAG 知识库模块
处理流程:
文档上传
→ 文件类型检测(PDF / DOCX / TXT / MD)
→ 文档解析(提取纯文本)
→ 文本分块(RecursiveCharacterTextSplitter)
→ chunk_size: 500, overlap: 50
→ 嵌入生成(调用 Embedding API)
→ 向量存储(ChromaDB,按 tenant_id 隔离命名空间)
→ 元数据存储(文档标题、来源、分块偏移量)
数据模型:
class KnowledgeBase:
id: UUID
tenant_id: UUID
name: str
description: str | None
embedding_model: str # text-embedding-3-small / ...
chunk_count: int
created_at: datetime
updated_at: datetime
class Document:
id: UUID
knowledge_base_id: UUID
tenant_id: UUID
filename: str
file_type: str # pdf / docx / txt / md
file_size: int
chunk_count: int
status: DocumentStatus # processing / ready / failed
error_message: str | None
created_at: datetime
API 端点:
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/tenants/{tid}/knowledge-bases |
创建知识库 |
| POST | /api/v1/tenants/{tid}/knowledge-bases/{kid}/documents |
上传文档 |
| GET | /api/v1/tenants/{tid}/knowledge-bases/{kid}/documents |
列出文档 |
| DELETE | /api/v1/tenants/{tid}/knowledge-bases/{kid}/documents/{did} |
删除文档 |
| GET | /api/v1/tenants/{tid}/knowledge-bases |
列出知识库 |
| DELETE | /api/v1/tenants/{tid}/knowledge-bases/{kid} |
删除知识库 |
4.2.5 LLM Provider 抽象层
设计原则:
- 统一接口,Provider 可插拔
- 每个企业独立 API Key,按租户路由
- 支持流式和非流式调用
- 统一错误处理与重试
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class LLMMessage:
role: str # system / user / assistant
content: str
@dataclass
class LLMResponse:
content: str
token_usage: dict
model: str
finish_reason: str
class BaseLLMProvider(ABC):
"""LLM Provider 抽象基类"""
@abstractmethod
async def chat(
self,
messages: list[LLMMessage],
model: str,
temperature: float = 0.7,
max_tokens: int = 2048,
**kwargs,
) -> LLMResponse:
...
@abstractmethod
async def chat_stream(
self,
messages: list[LLMMessage],
model: str,
temperature: float = 0.7,
max_tokens: int = 2048,
**kwargs,
) -> AsyncIterator[str]:
...
@abstractmethod
async def embed(self, texts: list[str], model: str) -> list[list[float]]:
...
class OpenAIProvider(BaseLLMProvider):
"""OpenAI / 兼容 OpenAI 接口的 Provider"""
class QwenProvider(BaseLLMProvider):
"""通义千问 Provider(兼容 OpenAI 接口格式)"""
# Provider 注册表
PROVIDERS: dict[str, type[BaseLLMProvider]] = {
"openai": OpenAIProvider,
"qwen": QwenProvider,
}
def get_provider(provider_name: str, api_key: str, base_url: str | None = None) -> BaseLLMProvider:
provider_cls = PROVIDERS.get(provider_name)
if not provider_cls:
raise ValueError(f"Unknown provider: {provider_name}")
return provider_cls(api_key=api_key, base_url=base_url)
Provider 配置约定:
| Provider | chat model | embedding model | 特殊说明 |
|---|---|---|---|
| openai | gpt-4o / gpt-4o-mini | text-embedding-3-small | 标准 OpenAI API |
| qwen | qwen-max / qwen-plus | text-embedding-v3 | 兼容 OpenAI SDK 格式 |
4.3 安全设计
4.3.1 数据隔离
- 所有数据表包含
tenant_id字段 - 所有查询强制
WHERE tenant_id = :current_tenant - API 中间件自动注入
tenant_id,业务代码无法绕过 - ChromaDB 使用 tenant_id 作为 collection namespace 隔离向量数据
4.3.2 API Key 安全
- LLM API Key 使用 AES-256-GCM 加密存储
- 加密密钥从环境变量
ENCRYPTION_KEY读取,不入库不入日志 - 运行时解密仅用于发起 LLM 调用,不返回给前端
4.3.3 输入校验
- 所有 API 入参使用 Pydantic 校验
- 文档上传限制:单文件 < 20MB,仅允许 pdf/docx/txt/md
- 对话消息长度限制:单条 < 10000 字符
- 速率限制:每租户每分钟 60 次请求
4.4 技术栈
| 层 | MVP 技术选型 | V1 演进 |
|---|---|---|
| 后端框架 | FastAPI 0.115+ | FastAPI |
| 数据库 | SQLite (aiosqlite) | PostgreSQL 16 |
| 向量库 | ChromaDB 0.5+ | pgvector |
| 缓存 | 无 | Redis |
| ORM | SQLAlchemy 2.0+ (async) | SQLAlchemy 2.0+ |
| 嵌入/LLM | openai SDK | openai SDK |
| 文档解析 | PyPDF2 + python-docx | 同左 |
| 前端框架 | React + TypeScript + Vite | 同左 |
| 前端 UI | Ant Design | Ant Design |
| 对话前端 | 自研 Chat UI 组件 | 同左 |
4.5 项目结构
digital-employee-platform/
├── backend/
│ ├── app/
│ │ ├── __init__.py
│ │ ├── main.py # FastAPI 入口
│ │ ├── config.py # 配置管理
│ │ ├── database.py # 数据库连接
│ │ ├── models/ # SQLAlchemy 模型
│ │ │ ├── __init__.py
│ │ │ ├── tenant.py
│ │ │ ├── employee.py
│ │ │ ├── conversation.py
│ │ │ └── knowledge.py
│ │ ├── schemas/ # Pydantic 请求/响应模型
│ │ │ ├── __init__.py
│ │ │ ├── tenant.py
│ │ │ ├── employee.py
│ │ │ ├── conversation.py
│ │ │ └── knowledge.py
│ │ ├── api/ # API 路由
│ │ │ ├── __init__.py
│ │ │ ├── v1/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── tenants.py
│ │ │ │ ├── employees.py
│ │ │ │ ├── conversations.py
│ │ │ │ └── knowledge.py
│ │ │ └── deps.py # 依赖注入
│ │ ├── services/ # 业务逻辑
│ │ │ ├── __init__.py
│ │ │ ├── tenant_service.py
│ │ │ ├── employee_service.py
│ │ │ ├── conversation_service.py
│ │ │ ├── knowledge_service.py
│ │ │ └── rag_service.py
│ │ ├── providers/ # LLM Provider 抽象层
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── openai_provider.py
│ │ │ └── qwen_provider.py
│ │ └── middleware/ # 中间件
│ │ ├── __init__.py
│ │ └── tenant.py # 租户隔离中间件
│ ├── tests/ # 测试
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_tenants.py
│ │ ├── test_employees.py
│ │ ├── test_conversations.py
│ │ ├── test_knowledge.py
│ │ └── test_providers.py
│ ├── alembic/ # 数据库迁移
│ │ └── ...
│ ├── pyproject.toml
│ └── .env.example
├── frontend/
│ ├── src/
│ │ ├── api/ # API 客户端
│ │ ├── components/ # 通用组件
│ │ ├── pages/
│ │ │ ├── admin/ # 管理后台页面
│ │ │ │ ├── Tenants.tsx
│ │ │ │ ├── Employees.tsx
│ │ │ │ └── Knowledge.tsx
│ │ │ └── chat/ # 对话前端页面
│ │ │ └── Chat.tsx
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
└── docs/
└── plans/
5. 错误处理
| 错误类型 | 处理策略 | HTTP 状态码 |
|---|---|---|
| LLM Provider 调用失败 | 指数退避重试 2 次,记录日志,返回降级提示 | 502 |
| LLM API Key 无效/过期 | 返回配置错误提示,不重试 | 401 |
| 租户 API 限流 | 返回 429,附带 Retry-After 头 | 429 |
| 文档解析失败 | 标记文档状态为 failed,记录错误信息 | 200(异步) |
| RAG 无相关结果 | 正常返回,不注入知识上下文,LLM 按通用知识回答 | - |
| 向量库写入失败 | 重试 1 次,失败则文档标记 failed | 200(异步) |
| 数据库连接失败 | 返回 503,记录告警 | 503 |
6. 测试策略
6.1 单元测试
- Provider 抽象层:mock LLM API 响应,验证接口一致性
- 服务层:mock 数据库,验证业务逻辑
- RAG Pipeline:mock embedding,验证分块、检索逻辑
6.2 集成测试
- 端到端对话流:使用真实 LLM API(测试 Key),验证完整链路
- 租户隔离:创建两个租户,验证数据不互渗
- RAG 问答:上传文档后提问,验证回答包含文档内容
6.3 安全测试
- 越权访问:租户 A 尝试访问租户 B 资源,必须 403
- API Key 不出现在日志和 API 响应中
- 文档上传类型和大小限制
6.4 性能测试
- 对话接口:p99 < 3s(不含 LLM 调用)
- 文档上传:10MB PDF 解析 < 30s
- 并发:单租户 10 并发对话无异常
7. 演进路径
MVP (方案 A) V1 (方案 C)
───────────── ──────────────
SQLite + ChromaDB ──迁移──→ PostgreSQL + pgvector
文件存储对话历史 ──迁移──→ PostgreSQL JSONB
无缓存 ──新增──→ Redis 会话缓存
单实例部署 ──演进──→ Docker Compose 多服务
基础对话 ──新增──→ 对话质量评估
无模板 ──新增──→ 数字员工模板市场
Web 对话 ──新增──→ 多渠道接入