# Nex - AI Gateway

一个统一的大模型 API 网关，支持 OpenAI 和 Anthropic 双协议，让应用只需配置一个地址即可透明使用多个供应商的大模型服务。

## 项目结构

```
nex/
├── backend/              # Go 后端服务（分层架构）
│   ├── cmd/
│   │   ├── server/       # CLI 主程序入口
│   │   └── desktop/      # 桌面应用入口
│   ├── internal/
│   │   ├── handler/      # HTTP 处理器 + 中间件
│   │   ├── service/      # 业务逻辑层
│   │   ├── repository/   # 数据访问层
│   │   ├── domain/       # 领域模型
│   │   ├── conversion/   # 协议转换引擎（OpenAI/Anthropic 适配器）
│   │   ├── provider/     # 供应商客户端
│   │   └── config/       # 配置管理
│   ├── pkg/              # 公共包（logger/errors/validator）
│   ├── migrations/       # 数据库迁移
│   └── tests/            # 测试（unit/integration）
│
├── frontend/             # React 前端界面
│   ├── src/
│   │   ├── api/          # API 层（统一请求封装 + 字段转换）
│   │   ├── hooks/        # TanStack Query hooks
│   │   ├── components/   # 通用组件（AppLayout）
│   │   ├── pages/        # 页面（Providers, Stats）
│   │   ├── routes/       # React Router 路由配置
│   │   ├── types/        # TypeScript 类型定义
│   │   └── __tests__/    # 单元测试 + 组件测试
│   ├── e2e/              # Playwright E2E 测试
│   └── package.json
│
├── assets/               # 应用资源
│   ├── icon.png          # 托盘图标
│   ├── icon.icns         # macOS 应用图标
│   └── icon.ico          # Windows 应用图标
│
└── README.md             # 本文件
```

## 功能特性

- **双协议支持**：同时支持 OpenAI 和 Anthropic 协议
- **跨协议转换**：Hub-and-Spoke 架构实现 OpenAI ↔ Anthropic 双向转换
- **统一模型 ID**：`provider_id/model_name` 格式全局唯一标识模型（如 `openai/gpt-4`）
- **Smart Passthrough**：同协议请求跳过 Canonical 全量转换，仅在 JSON 层改写 model 字段
- **流式响应**：完整支持 SSE 流式传输，包括跨协议流式转换
- **Function Calling**：支持工具调用（Tools）
- **Thinking / Reasoning**：支持 OpenAI `reasoning_effort` 和 Anthropic `thinking` 配置
- **扩展接口**：支持 Embeddings 和 Rerank 接口
- **多供应商管理**：配置和管理多个供应商（供应商 ID 仅限字母、数字、下划线）
- **用量统计**：按供应商、模型、日期统计请求数量
- **Web 配置界面**：提供供应商和模型配置管理

## 技术栈

### 后端
- **语言**: Go 1.26+
- **HTTP 框架**: Gin
- **ORM**: GORM
- **数据库**: SQLite / MySQL
- **日志**: zap + lumberjack（结构化日志 + 日志轮转 + 模块标识）
- **配置**: Viper + pflag（多层配置：CLI > 环境变量 > 配置文件 > 默认值）
- **验证**: go-playground/validator/v10
- **迁移**: goose

#### 日志模块标识规范

每个模块通过依赖注入获取带模块标识的 logger，日志输出格式为 `[module.name]`：

```
Console: INFO  [handler.proxy]  处理请求  method=POST path=/v1/chat
JSON:    {"level":"info","logger":"handler.proxy","msg":"处理请求","method":"POST"}
```

模块命名规范：
- 单一职责包：`database`、`config`
- 多实体包：`handler.proxy`、`service.provider`
- 子包：`handler.middleware`

### 前端
- **运行时**: Bun
- **构建工具**: Vite
- **语言**: TypeScript (strict mode)
- **框架**: React
- **UI 组件库**: TDesign React
- **图表库**: Recharts
- **路由**: React Router v7
- **数据获取**: TanStack Query v5
- **样式**: SCSS Modules
- **测试**: Vitest + React Testing Library + Playwright

## 快速开始

### 桌面应用（推荐）

**构建桌面应用**：

```bash
# macOS (arm64 + amd64，并打包为 .app)
make desktop-build-mac

# Windows
make desktop-build-win

# Linux
make desktop-build-linux

# 构建所有平台
make desktop-build
```

**使用桌面应用**：
- 双击启动应用（macOS: Nex.app，Windows: nex-win-amd64.exe，Linux: nex-linux-amd64）
- 系统托盘图标出现，浏览器自动打开管理界面
- 点击托盘图标显示菜单，可打开管理界面或退出
- 关闭浏览器后服务继续运行，可通过托盘重新打开

**注意事项**：
- 桌面应用需要 CGO 支持
- macOS: 自带 Xcode Command Line Tools
- Linux: 自带 gcc，部分桌面环境需要 `libappindicator3-dev`
- Windows: 需要 MinGW-w64 或在 Windows 环境构建

**Linux 桌面环境兼容性**：
- GNOME: 需要 AppIndicator 扩展
- KDE Plasma: 原生支持
- Xfce: 需要 libappindicator
- 其他支持 StatusNotifierItem 规范的环境

### CLI 模式

#### 后端

```bash
cd backend
go mod download
go run cmd/server/main.go
```

后端服务将在 `http://localhost:9826` 启动。首次启动会自动：
- 创建配置文件 `~/.nex/config.yaml`
- 初始化数据库 `~/.nex/config.db`
- 运行数据库迁移
- 创建日志目录 `~/.nex/log/`

### 前端

```bash
cd frontend
bun install
bun dev
```

前端开发服务器将在 `http://localhost:5173` 启动，API 请求通过 Vite proxy 转发到后端。

## API 接口

### 代理接口（对外部应用）

代理接口统一使用 `/{protocol}/*path` 路由格式，模型 ID 使用 `provider_id/model_name` 格式（如 `openai/gpt-4`）。同协议请求走 Smart Passthrough，最小化 JSON 改写并保持未改写字段的 JSON 内容和类型不变；跨协议请求走完整 decode/encode 转换。

**OpenAI 协议**（`protocol=openai`）：
- `POST /openai/chat/completions` - 对话补全
- `GET /openai/models` - 模型列表（本地数据库聚合）
- `GET /openai/models/{provider_id}/{model_name}` - 模型详情（本地数据库查询）
- `POST /openai/embeddings` - 嵌入
- `POST /openai/rerank` - 重排序

**Anthropic 协议**（`protocol=anthropic`）：
- `POST /anthropic/v1/messages` - 消息对话
- `GET /anthropic/v1/models` - 模型列表（本地数据库聚合）
- `GET /anthropic/v1/models/{provider_id}/{model_name}` - 模型详情（本地数据库查询）

路径边界：网关只剥离第一段协议前缀，剩余路径保持协议原生形态交给 adapter。OpenAI adapter 接收 `/chat/completions`、`/models`、`/embeddings`、`/rerank`；Anthropic adapter 接收 `/v1/messages`、`/v1/models`。因此 OpenAI 供应商 `base_url` 配置到版本路径一级（如 `https://api.openai.com/v1`），Anthropic 供应商 `base_url` 配置到域名级（如 `https://api.anthropic.com`）。

代理错误边界：网关层错误统一返回 `{"error":"...","code":"..."}`，例如 `INVALID_JSON`、`MODEL_NOT_FOUND`、`CONVERSION_FAILED`、`UPSTREAM_UNAVAILABLE`。只要上游已经返回 HTTP 响应，非 2xx 的 status、过滤 hop-by-hop header 后的 headers 和 body 会直接透传，不包装为应用错误或协议错误。

模型路由边界：只有 adapter 明确适配的接口会解析请求体中的 `model` 并使用统一模型 ID 路由；未知接口即使包含顶层 `model` 也按无 model 透传处理。

流式边界：同协议无响应 model 改写时原样透传 SSE frame 和 `[DONE]`；同协议需要响应 model 改写时只解析 SSE frame 的 `data` JSON 并改写 `model`；跨协议流式仍走 provider decoder → Canonical stream event → client encoder。

### 管理接口（对前端）

#### 供应商管理
- `GET /api/providers` - 列出所有供应商
- `POST /api/providers` - 创建供应商（`id` 仅限字母、数字、下划线，长度 1-64）
- `GET /api/providers/:id` - 获取供应商
- `PUT /api/providers/:id` - 更新供应商（`id` 不可修改）
- `DELETE /api/providers/:id` - 删除供应商

#### 模型管理
- `GET /api/models` - 列出模型（支持 `?provider_id=xxx` 过滤）
- `POST /api/models` - 创建模型（`id` 由系统自动生成 UUID，`provider_id` + `model_name` 联合唯一）
- `GET /api/models/:id` - 获取模型（响应含 `unified_id` 字段，格式 `provider_id/model_name`）
- `PUT /api/models/:id` - 更新模型（不可修改 `id`）
- `DELETE /api/models/:id` - 删除模型

#### 统计查询
- `GET /api/stats` - 查询统计
- `GET /api/stats/aggregate` - 聚合统计

查询参数支持：`provider_id`、`model_name`、`start_date`、`end_date`、`group_by`

## 配置

配置支持多种方式，优先级为：**CLI 参数 > 环境变量 > 配置文件 > 默认值**

### 配置文件

配置文件位于 `~/.nex/config.yaml`，首次启动自动生成：

```yaml
server:
  port: 9826
  read_timeout: 30s
  write_timeout: 30s

database:
  driver: sqlite               # sqlite 或 mysql
  path: ~/.nex/config.db       # SQLite 数据库文件路径
  # --- MySQL 配置（driver=mysql 时生效）---
  # host: localhost
  # port: 3306
  # user: nex
  # password: ""
  # dbname: nex
  max_idle_conns: 10
  max_open_conns: 100
  conn_max_lifetime: 1h

log:
  level: info
  path: ~/.nex/log
  max_size: 100        # MB
  max_backups: 10
  max_age: 30          # 天
  compress: true
```

### 环境变量

所有配置项支持环境变量，使用 `NEX_` 前缀：

```bash
export NEX_SERVER_PORT=9000
export NEX_DATABASE_PATH=/data/nex.db
export NEX_LOG_LEVEL=debug

# MySQL 模式
export NEX_DATABASE_DRIVER=mysql
export NEX_DATABASE_HOST=db.example.com
export NEX_DATABASE_PORT=3306
export NEX_DATABASE_USER=nex
export NEX_DATABASE_PASSWORD=secret
export NEX_DATABASE_DBNAME=nex
```

命名规则：配置路径转大写 + 下划线（如 `server.port` → `NEX_SERVER_PORT`）。

### CLI 参数

```bash
./server --server-port 9000 --log-level debug --database-path /tmp/test.db
```

命名规则：配置路径转 kebab-case（如 `server.port` → `--server-port`）。

### 数据文件

- `~/.nex/config.yaml` - 配置文件
- `~/.nex/config.db` - SQLite 数据库（MySQL 模式下不使用本地数据库文件）
- `~/.nex/log/` - 日志目录

## 测试

```bash
# 顶层便捷命令
make test                       # 运行所有测试

# 后端测试
make backend-test               # 后端测试
make backend-test-coverage      # 后端覆盖率
make backend-test-unit          # 后端单元测试
make backend-test-integration   # 后端集成测试

# 前端测试
make frontend-test              # 前端测试
make frontend-test-e2e          # 前端 E2E 测试
make frontend-test-coverage     # 前端覆盖率
```

## 开发

```bash
# 首次克隆后安装 Git hooks
lefthook install

# 顶层便捷命令
make dev                        # 启动开发环境（并行启动后端和前端）
make build                      # 构建所有产物
make lint                       # 检查所有代码
make clean                      # 清理所有构建产物

# 后端开发
make backend-build              # 构建后端
make backend-run                # 运行后端
make backend-dev                # 后端开发模式
make backend-lint               # 后端代码检查
make backend-clean              # 清理后端构建产物

# 数据库操作
make backend-db-up              # 数据库迁移
make backend-db-down            # 数据库回滚
make backend-db-status          # 数据库迁移状态
make backend-db-create          # 创建新迁移

# 前端开发
make frontend-build             # 构建前端
make frontend-dev               # 前端开发模式
make frontend-lint              # 前端代码检查
make frontend-clean             # 清理前端构建产物
```

## 开发规范

详见各子项目的 README.md：
- [后端 README](backend/README.md) - 分层架构、依赖注入、数据库迁移
- [前端 README](frontend/README.md) - TypeScript strict、SCSS Modules、测试策略

## 许可证

MIT
