1
0
Files
nex/backend/README.md

545 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI Gateway Backend
AI 网关后端服务,提供统一的大模型 API 代理接口。
## 功能特性
- 支持 OpenAI 协议(`/openai/v1/...`
- 支持 Anthropic 协议(`/anthropic/v1/...`
- 支持 Hub-and-Spoke 跨协议双向转换OpenAI ↔ Anthropic
- 同协议透传(零语义损失、零序列化开销)
- 支持流式响应SSE
- 支持 Function Calling / Tools
- 支持 Thinking / Reasoning
- 支持扩展层接口Models、Embeddings、Rerank
- 多供应商配置和路由
- 用量统计
- 结构化日志zap + lumberjack
- YAML 配置管理
- 请求验证
- 中间件支持(请求 ID、日志、恢复、CORS
## 技术栈
- **语言**: Go 1.26+
- **HTTP 框架**: Gin
- **ORM**: GORM
- **数据库**: SQLite
- **日志**: zap + lumberjack
- **配置**: Viper + pflag多层配置CLI > 环境变量 > 配置文件 > 默认值)
- **验证**: go-playground/validator/v10
- **迁移**: goose
## 项目结构
```
backend/
├── cmd/
│ └── server/
│ └── main.go # 主程序入口(依赖注入)
├── internal/
│ ├── config/ # 配置管理
│ │ ├── config.go # Viper 多层配置加载/验证
│ │ └── models.go # GORM 数据模型
│ ├── domain/ # 领域模型
│ │ ├── provider.go
│ │ ├── model.go
│ │ ├── stats.go
│ │ └── route.go
│ ├── handler/ # HTTP 处理器
│ │ ├── middleware/ # 中间件
│ │ │ ├── request_id.go
│ │ │ ├── logging.go
│ │ │ ├── recovery.go
│ │ │ └── cors.go
│ │ ├── proxy_handler.go # 统一代理处理器
│ │ ├── provider_handler.go
│ │ ├── model_handler.go
│ │ └── stats_handler.go
│ ├── conversion/ # 协议转换引擎
│ │ ├── canonical/ # Canonical Model
│ │ │ ├── types.go # 核心请求/响应类型
│ │ │ ├── stream.go # 流式事件类型
│ │ │ └── extended.go # 扩展层 Models
│ │ ├── openai/ # OpenAI 协议适配器
│ │ │ ├── types.go
│ │ │ ├── adapter.go
│ │ │ ├── decoder.go
│ │ │ ├── encoder.go
│ │ │ ├── stream_decoder.go
│ │ │ └── stream_encoder.go
│ │ ├── anthropic/ # Anthropic 协议适配器
│ │ │ ├── types.go
│ │ │ ├── adapter.go
│ │ │ ├── decoder.go
│ │ │ ├── encoder.go
│ │ │ ├── stream_decoder.go
│ │ │ └── stream_encoder.go
│ │ ├── adapter.go # ProtocolAdapter 接口 + Registry
│ │ ├── stream.go # StreamDecoder/Encoder/Converter
│ │ ├── middleware.go # Middleware 接口和 Chain
│ │ ├── engine.go # ConversionEngine 门面
│ │ ├── errors.go # ConversionError
│ │ ├── interface.go # InterfaceType 枚举
│ │ └── provider.go # TargetProvider
│ ├── provider/ # 供应商客户端
│ │ └── client.go
│ ├── repository/ # 数据访问层
│ │ ├── provider_repo.go # 接口定义
│ │ ├── provider_repo_impl.go
│ │ ├── model_repo.go
│ │ ├── model_repo_impl.go
│ │ ├── stats_repo.go
│ │ └── stats_repo_impl.go
│ └── service/ # 业务逻辑层
│ ├── provider_service.go # 接口定义
│ ├── provider_service_impl.go
│ ├── model_service.go
│ ├── model_service_impl.go
│ ├── routing_service.go
│ ├── routing_service_impl.go
│ ├── stats_service.go
│ └── stats_service_impl.go
├── pkg/ # 公共包
│ ├── errors/ # 结构化错误
│ │ ├── errors.go
│ │ └── wrap.go
│ ├── logger/ # 日志系统
│ │ ├── logger.go
│ │ ├── rotate.go
│ │ └── context.go
│ ├── modelid/ # 统一模型 ID 工具包
│ │ ├── model_id.go
│ │ └── model_id_test.go
│ └── validator/ # 验证器
│ └── validator.go
├── migrations/ # 数据库迁移
│ └── 20260421000001_initial_schema.sql
├── tests/ # 集成测试
│ ├── helpers.go # 测试辅助函数
│ ├── config/ # 测试配置
│ ├── integration/ # 集成测试
│ │ └── e2e_conversion_test.go # E2E 协议转换测试
│ └── mocks/ # Mock 实现
├── Makefile
├── go.mod
└── README.md
```
## 架构
采用三层架构handler → service → repository通过依赖注入连接
```
handlerHTTP 请求处理)
→ service业务逻辑
→ repository数据访问
```
代理请求通过 ConversionEngine 进行协议转换:
```
Client Request (clientProtocol)
→ ProxyHandler 路由到上游 provider
→ ConversionEngine 请求转换 (clientProtocol → providerProtocol)
→ ProviderClient 发送请求
→ ConversionEngine 响应转换 (providerProtocol → clientProtocol)
→ Client Response
```
同协议时自动透传,跳过序列化开销。
## 协议转换架构
### 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 |
## 运行方式
### 安装依赖
```bash
go mod download
```
### 启动服务
```bash
go run cmd/server/main.go
```
服务将在端口 9826 启动。首次启动会自动创建配置文件和运行数据库迁移。
## 配置
配置支持多种方式:配置文件、环境变量、命令行参数,优先级为:**CLI 参数 > 环境变量 > 配置文件 > 默认值**
### 配置文件
配置文件位于 `~/.nex/config.yaml`,首次启动自动生成。
```yaml
server:
port: 9826
read_timeout: 30s
write_timeout: 30s
database:
path: ~/.nex/config.db
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
```
命名规则:配置路径转大写 + 下划线 + `NEX_` 前缀(如 `server.port``NEX_SERVER_PORT`)。
### 命令行参数
```bash
./server --server-port 9000 --log-level debug --database-path /tmp/test.db
```
命名规则:配置路径转 kebab-case + `--` 前缀(如 `server.port``--server-port`)。
完整参数列表:
```
服务器: --server-port, --server-read-timeout, --server-write-timeout
数据库: --database-path, --database-max-idle-conns, --database-max-open-conns, --database-conn-max-lifetime
日志: --log-level, --log-path, --log-max-size, --log-max-backups, --log-max-age, --log-compress
通用: --config (指定配置文件路径)
```
### 使用示例
```bash
# 默认配置
./server
# 临时修改端口
./server --server-port 9000
# 测试场景
./server --database-path /tmp/test.db --log-level debug
# Docker 部署
docker run -d -e NEX_SERVER_PORT=9000 -e NEX_LOG_LEVEL=info nex-server
# 自定义配置文件
./server --config /path/to/custom.yaml
```
数据文件:
- `~/.nex/config.yaml` - 配置文件
- `~/.nex/config.db` - SQLite 数据库
- `~/.nex/log/` - 日志目录
## 测试
```bash
# 运行所有测试
make test
# 生成覆盖率报告
make test-coverage
```
## 数据库迁移
```bash
# 使用 Makefile
make migrate-up DB_PATH=~/.nex/config.db
make migrate-down DB_PATH=~/.nex/config.db
make migrate-status DB_PATH=~/.nex/config.db
# 创建新迁移
make migrate-create
# 或直接使用 goose
goose -dir migrations sqlite3 ~/.nex/config.db up
```
## API 文档
### 代理接口
使用 `/{protocol}/v1/{path}` URL 前缀路由:
#### OpenAI 协议
```
POST /openai/chat/completions
GET /openai/models
POST /openai/embeddings
POST /openai/rerank
```
#### Anthropic 协议
```
POST /anthropic/v1/messages
GET /anthropic/v1/models
```
**协议转换**:网关支持任意协议间的双向转换。客户端使用 OpenAI 协议请求,上游供应商可以是 Anthropic 协议(反之亦然)。同协议时自动透传,零序列化开销。
**统一模型 ID**:代理请求中的 `model` 字段使用 `provider_id/model_name` 格式(如 `openai/gpt-4`),网关据此路由到对应供应商。同协议时自动改写为上游 `model_name`,跨协议时通过全量转换处理。
### 管理接口
#### 供应商管理
- `GET /api/providers` - 列出所有供应商
- `POST /api/providers` - 创建供应商
- `GET /api/providers/:id` - 获取供应商
- `PUT /api/providers/:id` - 更新供应商
- `DELETE /api/providers/:id` - 删除供应商
```json
{
"id": "openai",
"name": "OpenAI",
"api_key": "sk-...",
"base_url": "https://api.openai.com/v1",
"protocol": "openai"
}
```
**Protocol 字段**:标识上游供应商使用的协议类型,可选值 `"openai"`(默认)、`"anthropic"`
**base_url 说明**
- OpenAI 协议:配置到 API 版本路径,如 `https://api.openai.com/v1``https://open.bigmodel.cn/api/paas/v4`
- Anthropic 协议:配置到域名,不包含版本路径,如 `https://api.anthropic.com`
**对外 URL 格式**
- OpenAI 协议:`/{protocol}/{endpoint}`,如 `/openai/chat/completions``/openai/models``/openai/embeddings`
- Anthropic 协议:`/{protocol}/v1/{endpoint}`,如 `/anthropic/v1/messages``/anthropic/v1/models`
#### 模型管理
- `GET /api/models` - 列出模型(支持 `?provider_id=xxx` 过滤)
- `POST /api/models` - 创建模型
- `GET /api/models/:id` - 获取模型
- `PUT /api/models/:id` - 更新模型
- `DELETE /api/models/:id` - 删除模型
**创建请求**id 由系统自动生成 UUID
```json
{
"provider_id": "openai",
"model_name": "gpt-4"
}
```
**响应示例**
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"provider_id": "openai",
"model_name": "gpt-4",
"unified_id": "openai/gpt-4",
"enabled": true,
"created_at": "2026-04-21T00:00:00Z"
}
```
**统一模型 ID**`unified_id` 字段为 `provider_id/model_name` 格式,用于代理请求的 `model` 参数。
#### 统计查询
- `GET /api/stats` - 查询统计
- `GET /api/stats/aggregate` - 聚合统计
查询参数:`provider_id``model_name``start_date`YYYY-MM-DD`end_date``group_by`provider/model/date
#### 健康检查
- `GET /health` - 返回 `{"status": "ok"}`
## 开发
```bash
make build # 构建
make lint # 代码检查
make deps # 整理依赖
```
环境要求Go 1.26 或更高版本
## 公共库使用指南
### pkg/errors — 结构化错误
```go
import (
"errors"
pkgErrors "nex/backend/pkg/errors"
)
return pkgErrors.ErrRequestSend.WithCause(err)
var appErr *pkgErrors.AppError
if errors.As(err, &appErr) {
// appErr.Code, appErr.HTTPStatus, appErr.Message
}
```
### pkg/logger — 日志系统
构造函数接受 `*zap.Logger` 参数nil 时回退到 `zap.L()`
```go
func NewMyService(repo Repository, logger *zap.Logger) *MyService {
if logger == nil {
logger = zap.L()
}
return &MyService{repo: repo, logger: logger}
}
```
### pkg/validator — 请求验证
```go
import "nex/backend/pkg/validator"
v := validator.Get()
err := v.Validate(myStruct)
```
## 编码规范
- **JSON 解析**:使用 `encoding/json` 标准库,不手动扫描字节
- **字符串拼接**:使用 `strings.Join`,不手写循环拼接
- **错误判断**:使用 `errors.Is` / `errors.As`,不使用字符串匹配
- **日志使用**:通过依赖注入 `*zap.Logger`,不直接调用 `zap.L()`
- **字符串分割**:使用 `strings.SplitN` 等精确分割,不使用索引切片