1
0
Files
nex/backend/README.md

16 KiB
Raw Blame History

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_tokenseffort
  • Parameters - 通用参数(max_tokenstemperaturetop_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 不支持连续同角色)
  • 工具选择映射:anyrequired

Anthropic 适配器

特有字段支持

  • thinking - 推理配置(type: enabledbudget_tokenseffort
  • output_config - 结构化输出配置
  • disable_parallel_tool_use - 禁用并行工具调用
  • container - 工具容器字段

不支持的功能

  • Embeddings 接口(返回 INTERFACE_NOT_SUPPORTED 错误)

跨协议转换注意事项

源协议 目标协议 转换说明
OpenAI Anthropic reasoning_effortthinking,消息角色合并
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.portNEX_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/v1https://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"
}

统一模型 IDunified_id 字段为 provider_id/model_name 格式,用于代理请求的 model 参数。

统计查询

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

查询参数:provider_idmodel_namestart_dateYYYY-MM-DDend_dategroup_byprovider/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 等精确分割,不使用索引切片