1
0

docs: 更新三份 README 文档以反映实际项目情况

This commit is contained in:
2026-04-22 16:05:23 +08:00
parent f5e45d032e
commit 72aebef625
3 changed files with 274 additions and 20 deletions

View File

@@ -13,7 +13,7 @@ nex/
│ │ ├── service/ # 业务逻辑层
│ │ ├── repository/ # 数据访问层
│ │ ├── domain/ # 领域模型
│ │ ├── protocol/ # 协议适配器OpenAI/Anthropic
│ │ ├── conversion/ # 协议转换引擎OpenAI/Anthropic 适配器
│ │ ├── provider/ # 供应商客户端
│ │ └── config/ # 配置管理
│ ├── pkg/ # 公共包logger/errors/validator
@@ -38,10 +38,13 @@ nex/
## 功能特性
- **双协议支持**:同时支持 OpenAI 和 Anthropic 协议
- **跨协议转换**Hub-and-Spoke 架构实现 OpenAI ↔ Anthropic 双向转换
- **统一模型 ID**`provider_id/model_name` 格式全局唯一标识模型(如 `openai/gpt-4`
- **透明代理**:对 OpenAI 兼容供应商 Smart Passthrough最小化改写保持参数保真
- **流式响应**:完整支持 SSE 流式传输
- **Smart Passthrough**:同协议请求零序列化开销,仅改写 model 字段
- **流式响应**:完整支持 SSE 流式传输,包括跨协议流式转换
- **Function Calling**支持工具调用Tools
- **Thinking / Reasoning**:支持 OpenAI `reasoning_effort` 和 Anthropic `thinking` 配置
- **扩展接口**:支持 Embeddings 和 Rerank 接口
- **多供应商管理**:配置和管理多个供应商(供应商 ID 仅限字母、数字、下划线)
- **用量统计**:按供应商、模型、日期统计请求数量
- **Web 配置界面**:提供供应商和模型配置管理
@@ -54,7 +57,7 @@ nex/
- **ORM**: GORM
- **数据库**: SQLite
- **日志**: zap + lumberjack结构化日志 + 日志轮转)
- **配置**: gopkg.in/yaml.v3
- **配置**: Viper + pflag多层配置CLI > 环境变量 > 配置文件 > 默认值)
- **验证**: go-playground/validator/v10
- **迁移**: goose
@@ -100,12 +103,19 @@ bun dev
### 代理接口(对外部应用)
代理接口统一使用 `provider_id/model_name` 格式的模型 ID(如 `openai/gpt-4`)。同协议请求走 Smart Passthrough最小化 JSON 改写保持参数保真;跨协议请求走完整 decode/encode 转换。
代理接口统一使用 `/{protocol}/*path` 路由格式,模型 ID 使用 `provider_id/model_name` 格式(如 `openai/gpt-4`)。同协议请求走 Smart Passthrough最小化 JSON 改写保持参数保真;跨协议请求走完整 decode/encode 转换。
- `POST /v1/chat/completions` - OpenAI Chat Completions API
- `POST /v1/messages` - Anthropic Messages API
- `GET /v1/models` - 模型列表(本地数据库聚合,不请求上游
- `GET /v1/models/{provider_id}/{model_name}` - 模型详情(本地数据库查询)
**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}` - 模型详情(本地数据库查询)
### 管理接口(对前端)
@@ -131,6 +141,10 @@ bun dev
## 配置
配置支持多种方式,优先级为:**CLI 参数 > 环境变量 > 配置文件 > 默认值**
### 配置文件
配置文件位于 `~/.nex/config.yaml`,首次启动自动生成:
```yaml
@@ -154,7 +168,28 @@ log:
compress: true
```
数据文件:
### 环境变量
所有配置项支持环境变量,使用 `NEX_` 前缀:
```bash
export NEX_SERVER_PORT=9000
export NEX_DATABASE_PATH=/data/nex.db
export NEX_LOG_LEVEL=debug
```
命名规则:配置路径转大写 + 下划线(如 `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 数据库
- `~/.nex/log/` - 日志目录

View File

@@ -116,8 +116,11 @@ backend/
├── migrations/ # 数据库迁移
│ └── 20260421000001_initial_schema.sql
├── tests/ # 集成测试
│ ├── helpers.go
── integration/
│ ├── helpers.go # 测试辅助函数
── config/ # 测试配置
│ ├── integration/ # 集成测试
│ │ └── e2e_conversion_test.go # E2E 协议转换测试
│ └── mocks/ # Mock 实现
├── Makefile
├── go.mod
└── README.md
@@ -146,6 +149,120 @@ Client Request (clientProtocol)
同协议时自动透传,跳过序列化开销。
## 协议转换架构
### Canonical Model 中间表示
所有协议转换都经过 Canonical Model 中间表示层,实现 Hub-and-Spoke 架构:
```
OpenAI Request → Canonical Request → Anthropic Request
(中间表示)
OpenAI Response ← Canonical Response ← Anthropic Response
```
**CanonicalRequest 核心字段**
- `Model` - 统一模型 ID
- `Messages` - 消息列表(支持 text、tool_use、tool_result、thinking 类型)
- `Tools` - 工具定义
- `Thinking` - 推理配置(`budget_tokens``effort`
- `Parameters` - 通用参数(`max_tokens``temperature``top_p` 等)
### Smart Passthrough 机制
同协议请求走 Smart Passthrough 路径,**零序列化开销**
```
1. 检测 clientProtocol == providerProtocol
2. 仅改写请求体中的 model 字段unified_id → upstream_model_name
3. 直接转发请求到上游
4. 响应中仅改写 model 字段upstream_model_name → unified_id
```
### 流式转换器层次
```
StreamConverter (接口)
├── PassthroughStreamConverter # 直接透传,无任何处理
├── SmartPassthroughStreamConverter # 同协议 + 逐 chunk 改写 model
└── CanonicalStreamConverter # 跨协议完整转换decode → encode
```
### InterfaceType 枚举
| 类型 | 说明 |
|------|------|
| `CHAT` | 对话补全chat/completions、messages |
| `MODELS` | 模型列表 |
| `MODEL_INFO` | 模型详情 |
| `EMBEDDINGS` | 嵌入接口 |
| `RERANK` | 重排序接口 |
| `PASSTHROUGH` | 未知接口,直接透传 |
## 协议适配器特性
### OpenAI 适配器
**特有字段支持**
- `reasoning_effort` - 映射到 Canonical Thinking 配置(`none` → 禁用,其他 → `effort`
- `reasoning_content` - 非标准字段,映射到 Canonical thinking 块
- `max_completion_tokens` - 新字段,优先于 `max_tokens`
- `refusal` - 非标准字段,作为 text 块处理
**废弃字段兼容**
- `functions` / `function_call` - 自动转换为 `tools` / `tool_choice`
**消息处理**
- 合并连续同角色消息Anthropic 不支持连续同角色)
- 工具选择映射:`any``required`
### Anthropic 适配器
**特有字段支持**
- `thinking` - 推理配置(`type: enabled``budget_tokens``effort`
- `output_config` - 结构化输出配置
- `disable_parallel_tool_use` - 禁用并行工具调用
- `container` - 工具容器字段
**不支持的功能**
- Embeddings 接口(返回 `INTERFACE_NOT_SUPPORTED` 错误)
### 跨协议转换注意事项
| 源协议 | 目标协议 | 转换说明 |
|--------|----------|----------|
| OpenAI | Anthropic | `reasoning_effort``thinking`,消息角色合并 |
| Anthropic | OpenAI | `thinking` 块 → `reasoning_content`,工具选择转换 |
## 错误码
### ConversionError 错误码
| 错误码 | 说明 |
|--------|------|
| `INVALID_INPUT` | 输入数据无效 |
| `MISSING_REQUIRED_FIELD` | 缺少必填字段 |
| `INCOMPATIBLE_FEATURE` | 功能不兼容(如跨协议不支持某特性) |
| `FIELD_MAPPING_FAILURE` | 字段映射失败 |
| `TOOL_CALL_PARSE_ERROR` | 工具调用解析错误 |
| `JSON_PARSE_ERROR` | JSON 解析错误 |
| `STREAM_STATE_ERROR` | 流式状态错误 |
| `UTF8_DECODE_ERROR` | UTF-8 解码错误(流式 chunk 截断) |
| `PROTOCOL_CONSTRAINT_VIOLATION` | 协议约束违反 |
| `ENCODING_FAILURE` | 编码失败 |
| `INTERFACE_NOT_SUPPORTED` | 接口不支持(如 Anthropic Embeddings |
### AppError 预定义错误
| 错误 | HTTP 状态码 | 说明 |
|------|-------------|------|
| `ErrModelNotFound` | 404 | 模型未找到 |
| `ErrModelDisabled` | 404 | 模型已禁用 |
| `ErrProviderNotFound` | 404 | 供应商未找到 |
| `ErrInvalidProviderID` | 400 | 供应商 ID 格式无效 |
| `ErrDuplicateModel` | 409 | 同一供应商下模型名称重复 |
| `ErrImmutableField` | 400 | 不可修改字段(如供应商 ID |
## 运行方式
### 安装依赖
@@ -278,10 +395,10 @@ goose -dir migrations sqlite3 ~/.nex/config.db up
#### OpenAI 协议
```
POST /openai/v1/chat/completions
GET /openai/v1/models
POST /openai/v1/embeddings
POST /openai/v1/rerank
POST /openai/chat/completions
GET /openai/models
POST /openai/embeddings
POST /openai/rerank
```
#### Anthropic 协议
@@ -322,7 +439,7 @@ GET /anthropic/v1/models
- Anthropic 协议:配置到域名,不包含版本路径,如 `https://api.anthropic.com`
**对外 URL 格式**
- OpenAI 协议:`/{protocol}/{endpoint}`,如 `/openai/chat/completions``/openai/models`
- OpenAI 协议:`/{protocol}/{endpoint}`,如 `/openai/chat/completions``/openai/models``/openai/embeddings`
- Anthropic 协议:`/{protocol}/v1/{endpoint}`,如 `/anthropic/v1/messages``/anthropic/v1/models`
#### 模型管理

View File

@@ -14,6 +14,68 @@ AI 网关管理前端,提供供应商配置和用量统计界面。
- **样式**: SCSS Modules禁止使用纯 CSS
- **测试**: Vitest + React Testing Library + Playwright
## API 层
### 字段转换机制
后端使用 `snake_case`,前端使用 `camelCase`API 层自动转换:
```typescript
// 发送请求时camelCase → snake_case
toApi({ providerId: "openai" }) // → { provider_id: "openai" }
// 接收响应时snake_case → camelCase
fromApi({ provider_id: "openai" }) // → { providerId: "openai" }
```
### 统一请求函数
```typescript
export async function request<T>(method: string, path: string, body?: unknown): Promise<T>
```
- 自动处理字段转换
- 自动处理 204 响应(无 body
- 抛出 `ApiError` 包含 `status``code``message`
### 错误处理
```typescript
class ApiError extends Error {
status: number; // HTTP 状态码
code?: string; // 业务错误码
message: string; // 错误消息
}
```
## TanStack Query 模式
### Query Keys 定义
```typescript
// src/hooks/useProviders.ts
export const providerKeys = {
all: ['providers'] as const,
};
// src/hooks/useModels.ts
export const modelKeys = {
all: ['models'] as const,
byProvider: (providerId: string) => [...modelKeys.all, { providerId }] as const,
};
```
### Mutation 使用
```typescript
const mutation = useMutation({
mutationFn: createProvider,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: providerKeys.all });
},
});
```
## 项目结构
```
@@ -25,7 +87,7 @@ frontend/
│ │ ├── models.ts # Model CRUD
│ │ └── stats.ts # Stats 查询
│ ├── components/
│ │ └── AppLayout/ # 顶部导航布局
│ │ └── AppLayout/ # 侧边栏导航布局
│ ├── hooks/ # TanStack Query hooks
│ │ ├── useProviders.ts
│ │ ├── useModels.ts
@@ -33,6 +95,7 @@ frontend/
│ ├── pages/
│ │ ├── Providers/ # 供应商管理(含内嵌模型管理)
│ │ ├── Stats/ # 用量统计
│ │ ├── Settings/ # 设置(开发中)
│ │ └── NotFound.tsx
│ ├── routes/
│ │ └── index.tsx # 路由配置
@@ -125,11 +188,50 @@ bun run test:e2e
- 按模型筛选
- 按日期范围筛选DatePicker.RangePicker
## 测试策略
### 目录结构
```
__tests__/
├── setup.ts # 测试配置happy-dom
├── api/ # API 层测试
│ └── client.test.ts
├── hooks/ # TanStack Query Hook 测试
│ ├── useProviders.test.ts
│ └── useModels.test.ts
└── components/ # 组件测试
└── AppLayout.test.tsx
```
### E2E 测试
- 位于 `e2e/` 目录
- 使用 Playwright
- 自动启动后端服务(临时端口 19026
- 配置文件:`playwright.config.ts`
### Mock 策略
- API 层测试使用 MSWMock Service Worker
- Hook 测试使用 `@testing-library/react-hooks`
- 组件测试使用 `@testing-library/react`
## 环境变量
| 变量 | 开发环境 | 生产环境 | 说明 |
|------|----------|----------|------|
| `VITE_API_BASE` | (空) | `/api` | API 基础路径,空则走 Vite proxy |
**E2E 测试特有**
- `NEX_BACKEND_PORT` - E2E 后端端口(默认 19026
- `NEX_E2E_TEMP_DIR` - E2E 临时目录
## 开发规范
- 所有样式使用 SCSS禁止使用纯 CSS 文件
- 组件级样式使用 SCSS Modules*.module.scss
- 图标优先使用 @ant-design/icons
- 图标优先使用 TDesign 图标tdesign-icons-react
- TypeScript strict 模式,禁止 any 类型
- API 层自动处理 snake_case ↔ camelCase 字段转换
- 使用路径别名 `@/` 引用 src 目录
@@ -143,4 +245,4 @@ bun run test:e2e
1.`src/pages/` 创建页面目录和组件
2.`src/hooks/` 创建对应的 TanStack Query hook
3.`src/routes/index.tsx` 添加路由
4.`src/components/AppLayout/index.tsx`menuItems 添加导航项
4.`src/components/AppLayout/index.tsx`Menu 中添加 MenuItem