16 KiB
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),通过依赖注入连接:
handler(HTTP 请求处理)
→ 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- 统一模型 IDMessages- 消息列表(支持 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_tokensrefusal- 非标准字段,作为 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) |
运行方式
安装依赖
go mod download
启动服务
go run cmd/server/main.go
服务将在端口 9826 启动。首次启动会自动创建配置文件和运行数据库迁移。
配置
配置支持多种方式:配置文件、环境变量、命令行参数,优先级为:CLI 参数 > 环境变量 > 配置文件 > 默认值
配置文件
配置文件位于 ~/.nex/config.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_ 前缀:
export NEX_SERVER_PORT=9000
export NEX_DATABASE_PATH=/data/nex.db
export NEX_LOG_LEVEL=debug
命名规则:配置路径转大写 + 下划线 + NEX_ 前缀(如 server.port → NEX_SERVER_PORT)。
命令行参数
./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 (指定配置文件路径)
使用示例
# 默认配置
./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/- 日志目录
测试
# 运行所有测试
make test
# 生成覆盖率报告
make test-coverage
数据库迁移
# 使用 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- 删除供应商
{
"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):
{
"provider_id": "openai",
"model_name": "gpt-4"
}
响应示例:
{
"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"}
开发
make build # 构建
make lint # 代码检查
make deps # 整理依赖
环境要求:Go 1.26 或更高版本
公共库使用指南
pkg/errors — 结构化错误
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():
func NewMyService(repo Repository, logger *zap.Logger) *MyService {
if logger == nil {
logger = zap.L()
}
return &MyService{repo: repo, logger: logger}
}
pkg/validator — 请求验证
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等精确分割,不使用索引切片