架构概览¶
HermesX 的 SaaS-only 系统设计和多租户架构。
SaaS-only 运行模型¶
HermesX 对外只支持 SaaS 服务形态:
| 入口 | 用途 | 存储 |
|---|---|---|
hermesx saas-api |
多租户 HTTP API + 内嵌 WebUI | PostgreSQL 或 MySQL,Redis,MinIO/S3 |
旧本地助手命令、一次性对话命令、gateway 运行时和分离前端容器不再是受支持的公开运行模式。内部 agent、tool、skills、scheduler 等包继续作为 SaaS API 的实现模块复用。
SaaS 模式架构¶
┌─────────────────────────────────────────────────┐
│ HTTP 请求 │
└───────────────────────┬─────────────────────────┘
│
┌───────────────────────▼─────────────────────────┐
│ CORS Middleware │
│ (SAAS_ALLOWED_ORIGINS 配置) │
└───────────────────────┬─────────────────────────┘
│
┌────▼────┐
│ SPA 路由 │ ←── 静态文件(index.html, admin.html)
└────┬────┘
│
┌───────────────────────▼─────────────────────────┐
│ 9 层中间件栈(固定顺序) │
│ │
│ Tracing → Metrics → RequestID → Auth → Tenant │
│ → Logging → Audit → RBAC → RateLimit → Handler │
└───────────────────────┬─────────────────────────┘
│
┌─────────────▼─────────────┐
│ 路由分发 │
│ │
│ /health/* → 公开 │
│ /metrics → Prometheus │
│ /v1/tenants → Admin │
│ /v1/api-keys → Admin │
│ /v1/chat/* → User │
│ /v1/me → User │
└─────────┬─────────────────┘
│
┌──────────────▼──────────────┐
│ Store 层 │
│ PostgreSQL(多租户隔离) │
└─────────────────────────────┘
中间件栈¶
中间件按固定顺序执行,由 middleware.MiddlewareStack 强制保证:
Tracing → Metrics → RequestID → Auth → Tenant → Logging → Audit → RBAC → RateLimit → Handler
| 层 | 职责 | 源文件 |
|---|---|---|
| Tracing | OpenTelemetry span 创建与传播 | internal/observability/tracer.go |
| Metrics | Prometheus 请求计数、延迟、并发量 | internal/middleware/metrics.go |
| RequestID | 生成或提取 X-Request-ID |
internal/middleware/requestid.go |
| Auth | 链式认证(Static Token → API Key → JWT) | internal/auth/extractor.go |
| Tenant | 从 AuthContext 提取 tenant_id 写入 Context | internal/middleware/tenant.go |
| Logging | 注入 tenant_id 和 request_id 到 slog Logger | internal/observability/logger.go |
| Audit | 记录所有认证请求到 audit_logs 表 | internal/middleware/audit.go |
| RBAC | 基于路径前缀的角色访问控制 | internal/middleware/rbac.go |
| RateLimit | 按租户维度限流(分布式 + 本地 LRU 降级) | internal/middleware/ratelimit.go |
设计要点:
- Logging 层位于 Auth + Tenant 之后,确保日志自动包含 tenant_id
- Auth 错误使用 ContextLogger 降级(slog.Default + request_id)
- 所有中间件槽位可为 nil(passthrough),便于测试和按需启用
多租户模型¶
租户隔离¶
Hermes 使用数据库级租户隔离:
- tenant_id 派生自凭证:永远不从请求头读取,防止租户伪造
- 所有表 FK 到 tenants:11 张业务表均包含
tenant_id UUID NOT NULL REFERENCES tenants(id) - 查询自动过滤:所有 Store 方法按 tenant_id 过滤数据
- RLS FORCE — PostgreSQL 行级安全策略(含 INSERT/UPDATE/DELETE 写入策略),调度器写操作通过
SET LOCAL app.current_tenant兼容 RLS
AuthContext.TenantID(从凭证派生)
│
▼
┌──────────────┐
│ Context 传播 │ ← Tenant 中间件写入
└──────┬───────┘
│
▼
┌──────────────┐
│ Store 查询 │ ← WHERE tenant_id = $1
└──────────────┘
默认租户¶
首次启动时自动创建默认租户:
- ID:
00000000-0000-0000-0000-000000000001 - Name:
Default Tenant - Plan:
pro - Rate Limit: 120 RPM
- Max Sessions: 10
Static Token 认证自动映射到此租户。
Store 架构¶
接口抽象¶
store.Store 接口定义统一的数据访问层,支持多种后端实现:
type Store interface {
Tenants() TenantStore
Sessions() SessionStore
Messages() MessageStore
Users() UserStore
AuditLogs() AuditLogStore
APIKeys() APIKeyStore
CronJobs() CronJobStore
Memories() MemoryStore
UserProfiles() UserProfileStore
Roles() RoleStore
PricingRules() PricingRuleStore
}
存储后端¶
| 后端 | 用途 | 包路径 |
|---|---|---|
| PostgreSQL | SaaS 多租户生产后端 | internal/store/pg/ |
| MySQL | SaaS 多租户生产后端 | internal/store/mysql/ |
SaaS API 启动时必须提供 DATABASE_URL,并通过 DATABASE_DRIVER 选择 postgres 或 mysql。遗留 SQLite 包仍留在源码中作为内部兼容实现,不是公开部署后端。
分布式 Cron Scheduler¶
SaaS 模式下的定时任务调度由 internal/scheduler/ 包实现,支持多 Pod 分布式执行:
┌──────────────────────────────────────────┐
│ SaasScheduler │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ pollLoop │───▶│ syncOnce (PG) │ │
│ │ (30s) │ │ ListAllEnabled │ │
│ └──────────┘ └────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ gocron Scheduler │ │
│ │ (CronJob定义) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ execute() │ │
│ │ execWithTenant │ │
│ └────────┬─────────┘ │
│ │ │
└───────────────────────────┼──────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌───────▼───────┐
│ Redis Lock │ │ AgentRunner │ │ ResultDeliverer│
│ (互斥执行) │ │ (AI 对话) │ │ (结果投递) │
└─────────────┘ └─────────────┘ └───────────────┘
关键设计决策:
| 机制 | 说明 |
|---|---|
| PG 轮询同步 | 每 30s 从 cron_jobs 表拉取所有启用任务,对比内存状态执行增删改 |
| Redis 分布式锁 | redislock.WithTries(1) 无重试竞争,竞争失败的 Pod 跳过本次执行 |
| 幂等执行 | ON CONFLICT (cron_job_id, scheduled_at) DO NOTHING,PG 唯一约束保证 |
| 租户隔离 | execWithTenant() 在事务内 SET LOCAL app.current_tenant,兼容 FORCE RLS |
| 跨租户清理 | scheduler_cleanup_stale_runs() SECURITY DEFINER 函数绕过 RLS |
| 结果投递 | 执行完成后通过 ResultDeliverer 推送结果回用户来源平台 |
| 生命周期 | Scheduler 持有独立 ctx/cancelFunc,Stop() 先排空 gocron 再取消上下文 |
LLM 集成¶
可插拔 Transport¶
LLM 调用通过 Transport 接口抽象,支持多种提供商:
请求 → FallbackRouter → RetryTransport → CircuitBreaker → Provider Transport → LLM API
支持的提供商: - OpenAI(openai 协议) - Anthropic(anthropic 协议,含 prompt caching) - 自动检测(根据 API URL 和 Key 格式推断)
弹性机制¶
| 组件 | 职责 | 包路径 |
|---|---|---|
| FallbackRouter | 主 Provider 故障时自动切换到备用 | internal/llm/fallback_router.go |
| RetryTransport | 指数退避重试 + ±25% 抖动 | internal/llm/retry_transport.go |
| Circuit Breaker | 按模型独立断路,Prometheus 指标 | internal/llm/breaker.go |
| Model Catalog | 热重载模型注册表,能力元数据 | internal/llm/model_catalog.go |
| Multimodal Router | 图片/音频/视频按 Provider 能力分发 | internal/agent/multimodal.go |
配置¶
| 环境变量 | 说明 |
|---|---|
LLM_API_URL |
LLM API 端点 |
LLM_API_KEY |
API 认证密钥 |
LLM_MODEL |
默认模型名称 |
LLM_FALLBACK_API_URL |
备用 Provider 端点 |
LLM_FALLBACK_API_KEY |
备用 Provider 密钥 |
OIDC_ISSUER_URL |
OIDC Provider URL(设置后激活 SSO) |
Chat 请求流程¶
一次完整的 /v1/chat/completions 请求经历以下路径:
1. HTTP 请求到达
2. CORS 检查(如果配置了 SAAS_ALLOWED_ORIGINS)
3. Tracing:创建 span
4. Metrics:记录请求开始
5. RequestID:生成唯一请求标识
6. Auth:链式认证,生成 AuthContext
7. Tenant:从 AuthContext 提取 tenant_id
8. Logging:注入 tenant_id 到 logger
9. Audit:记录审计日志(action、status_code、latency_ms)
10. RBAC:检查 "user" 角色权限
11. RateLimit:检查租户 RPM 配额
12. Handler:
a. 解析 ChatCompletionRequest
b. 获取或创建 Session(按 tenant_id 隔离)
c. 调用 LLM API(通过 Transport)
d. 存储 Message(关联 tenant_id)
e. 返回 ChatCompletionResponse
HTTP 服务器配置¶
| 参数 | 值 | 说明 |
|---|---|---|
| Read Timeout | 30s | 读取请求体超时 |
| Write Timeout | 60s | 写入响应超时(LLM 调用可能较慢) |
| Idle Timeout | 120s | Keep-Alive 空闲超时 |
| Listen Address | 0.0.0.0:{port} |
监听所有网卡 |
项目结构¶
hermesx/
├── cmd/hermesx/
│ ├── main.go # CLI 入口
│ └── saas.go # SaaS API 入口(hermesx saas-api)
├── internal/
│ ├── api/ # HTTP handlers 和路由
│ │ ├── server.go # APIServer、路由注册、中间件组装
│ │ ├── tenants.go # 租户 CRUD
│ │ ├── apikeys.go # API Key 管理
│ │ ├── mockchat.go # Chat completions + session store
│ │ ├── health.go # 健康探针
│ │ └── gdpr.go # GDPR 数据导出/删除
│ ├── auth/ # 认证链
│ │ ├── context.go # AuthContext 定义
│ │ ├── extractor.go # ExtractorChain 接口
│ │ ├── static.go # Static Token 认证
│ │ ├── apikey.go # API Key 认证
│ │ └── jwt.go # JWT 认证(预留)
│ ├── middleware/ # HTTP 中间件
│ │ ├── chain.go # 固定顺序中间件栈
│ │ ├── rbac.go # 角色访问控制
│ │ ├── ratelimit.go # 速率限制
│ │ ├── metrics.go # Prometheus 指标
│ │ └── audit.go # 审计日志
│ ├── store/ # 数据存储抽象
│ │ ├── types.go # 数据模型定义
│ │ ├── pg/ # PostgreSQL 实现
│ │ ├── mysql/ # MySQL 实现
│ │ └── sqlite/ # 遗留内部兼容实现
│ ├── skills/ # Skills 系统
│ │ ├── hub.go # Skills Hub 发现与安装
│ │ ├── scanner.go # 安全扫描
│ │ └── loader.go # SaaS 租户加载
│ ├── observability/ # 可观测性
│ │ ├── tracer.go # OpenTelemetry 初始化
│ │ └── logger.go # Context-enriched 日志
│ ├── objstore/ # MinIO/S3 对象存储
│ ├── gateway/ # 内部通道适配、media dispatch、lifecycle hooks
│ │ └── platforms/ # 15 platform adapters + registry
│ ├── config/ # 配置管理
│ └── dashboard/ # 管理面板静态文件(历史路径)
│ └── static/ # HTML/CSS/JS;发布镜像使用 /static
├── skills/ # 81 个内置 Skills
├── deploy/ # 部署配置
│ ├── helm/ # Helm Chart
│ └── kind/ # Kind 本地 K8s
├── scripts/ # 测试和工具脚本
└── docs/ # 文档
v1.4.0 新增模块(上游 v2026.4.30 吸收)¶
Agent 层¶
| 模块 | 职责 | 源文件 |
|---|---|---|
| Memory Curator | 自主去重、LLM 合并、过期清理 | internal/agent/curator.go |
| Self-improvement | 定期 LLM 对话自评 + 洞察持久化 | internal/agent/self_improve.go |
| Multimodal Router | 图片/音频/视频请求按提供商能力分发 | internal/agent/multimodal.go |
| Compress | 上下文压缩(接近 token 限制时自动摘要) | internal/agent/compress.go |
Gateway 层¶
| 模块 | 职责 | 源文件 |
|---|---|---|
| Media Dispatcher | 感知平台能力的媒体路由 + 降级链 | internal/gateway/media_dispatch.go |
| Lifecycle Hooks | 优先级排序事件钩子(RWMutex 并发安全) | internal/gateway/lifecycle_hooks.go |
| Platform Registry | 平台注册与能力声明 | internal/gateway/registry.go |
LLM 层¶
| 模块 | 职责 | 源文件 |
|---|---|---|
| Model Catalog | 支持热重载的模型注册表 + 能力元数据 | internal/llm/model_catalog.go |
Store 层¶
| 模块 | 职责 | 源文件 |
|---|---|---|
| Trigram Search | pg_trgm CJK 模糊搜索 | internal/store/pg/trigram_search.go |