跳转至

统一 API 接口

文档版本: 2.1.0 最后更新: 2026-09-13 主要变更: v3.1 双入口面(/v1 OpenAI 原生面 + /api/v1 控制台面)+ 鉴权约定 作者: Lincoln

重要变更说明 (v2.0.0)

DTO 重构

为了提升 API 的可维护性和扩展性,我们对请求 DTO 进行了重构:

重构前:所有参数都在同一层级,导致参数字典臃肿(ChatDTO 有 56 个字段)

重构后: - 核心字段:只保留必需和常用参数(ChatDTO 11 个) - 扩展选项:适配器特定参数放入 options 对象中(ChatDTO 29 个)

优势: - ✅ 核心参数清晰,便于快速上手 - ✅ 扩展参数按需使用,不影响简单场景 - ✅ 向后兼容,现有请求仍然有效 - ✅ 便于后续添加新参数

使用示例:

{
  "model": "qwen3:4b",
  "messages": [{"role": "user", "content": "你好"}],
  "temperature": 0.7,
  "options": {
    "repeat_penalty": 1.1,
    "seed": 42
  }
}

JAiRouter 提供兼容 OpenAI 格式的统一 API 接口,支持多种 AI 模型服务。OpenAI 兼容面在 /v1 下;TTS / STT / 图像接口只在控制台面 /api/v1/** 暴露(见下表)。

两个入口面(v3.1 起)

入口面路径响应形态用途
OpenAI 原生面/v1/**非流式:原生 JSON({id, object:"chat.completion", choices, usage});流式:原生 SSE(data: {...},以 \n\n 分隔)OpenAI SDK / LangChain / LlamaIndex 直连:base_url=http://<host>:8080/v1
控制台面/api/v1/**统一 RouterResponse 包裹({success, message, data})Web 控制台 / AI 试验场内部调用;不对外承诺 OpenAI 兼容

鉴权约定

  • 网关凭据:X-API-Key: <管理台创建的 API Key>,或 Jairouter_Token: <JWT>。
  • Authorization 头保留并透传给下游 AI 服务;实例级 headers 优先级更高——因此可以把下游密钥只配在实例上(headers: {Authorization: "Bearer <下游密钥>"}),客户端无需重复提供。
  • 服务级权限:API Key 需具备 CHAT / EMBEDDING / RERANK 等服务权限(或 ADMIN)。
  • 未携带凭据返回 401,权限不足返回 403,配额超限返回 429(带 Retry-After 与 X-Quota-*)。

v3.1 变更:此前 /v1/** 由一个路径改写过滤器转发到 /api/v1/**,且转发后请求体丢失(恒返回 400 Failed to read HTTP message)。现已由 OpenAiNativeController 直接提供原生响应,/v1 才真正可作为 OpenAI SDK 的 base_url。

聊天完成接口

POST /v1/chat/completions

处理聊天完成请求,支持流式和非流式响应。

请求参数

{
  "model": "gpt-3.5-turbo",
  "messages": [
    {
      "role": "system",
      "content": "你是一个有用的助手。"
    },
    {
      "role": "user",
      "content": "你好!"
    }
  ],
  "stream": false,
  "max_tokens": 1000,
  "temperature": 0.7,
  "top_p": 1.0,
  "top_k": 50,
  "frequency_penalty": 0.0,
  "presence_penalty": 0.0,
  "stop": null,
  "user": "user-123",
  "options": {
    "repeat_penalty": 1.1,
    "seed": 42
  }
}

参数说明

核心参数
参数类型必需说明
modelstring是要使用的模型名称
messagesarray是对话消息列表
streamboolean否是否启用流式响应,默认 false
max_tokensinteger否生成的最大令牌数
temperaturenumber否采样温度,0-2 之间,默认 1
top_pnumber否核采样参数,0-1 之间,默认 1
top_kinteger否Top-K 采样参数
frequency_penaltynumber否频率惩罚,-2 到 2 之间,默认 0
presence_penaltynumber否存在惩罚,-2 到 2 之间,默认 0
stopstring/array否停止序列
userstring否用户标识符
扩展参数(options 对象)

扩展参数包含在 options 对象中,按适配器类型分类:

通用扩展参数:

参数类型说明
options.ninteger生成数量,默认 1
options.logprobsboolean是否返回 log 概率
options.top_logprobsinteger返回的 top log 概率数
options.use_beam_searchboolean是否使用束搜索
options.min_pnumber最小概率阈值
options.repetition_penaltynumber重复惩罚,默认 1.0
options.length_penaltynumber长度惩罚,默认 1.0
options.include_stop_str_in_outputboolean是否在输出中包含停止符
options.ignore_eosboolean是否忽略 EOS 标记
options.min_tokensinteger生成的最小 token 数
options.skip_special_tokensboolean是否跳过特殊 token
options.add_generation_promptboolean是否添加生成提示

Ollama 特定参数:

参数类型说明
options.repeat_penaltynumber重复惩罚,默认 1.1
options.seedinteger随机种子
options.num_keepinteger保留的 token 数
options.tfs_znumber尾部自由采样参数
options.typical_pnumber典型采样参数
options.repeat_last_ninteger重复检测窗口大小
options.penalize_newlineboolean是否惩罚换行符

GPUStack 特定参数:

参数类型说明
options.documentsarray文档列表(用于 RAG)
options.chat_templatestring聊天模板
options.chat_template_kwargsobject聊天模板参数
options.structured_outputsobject结构化输出配置
options.priorityinteger请求优先级
options.request_idstring请求 ID
options.cache_saltstring缓存盐值

消息格式

{
  "role": "user|assistant|system",
  "content": "消息内容",
  "name": "可选的消息发送者名称"
}

响应格式

非流式响应:

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-3.5-turbo",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "你好!我是一个AI助手,很高兴为你服务。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 15,
    "total_tokens": 35
  },
  "system_fingerprint": "fp_44709d6fcb"
}

流式响应:

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"role":"assistant","content":"你好"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

文本嵌入接口

POST /v1/embeddings

将文本转换为向量表示,用于语义搜索、相似度计算等任务。

请求参数

{
  "model": "text-embedding-ada-002",
  "input": "要嵌入的文本内容",
  "encoding_format": "float",
  "dimensions": 1536,
  "user": "user-123"
}

参数说明

参数类型必需说明
modelstring是嵌入模型名称
inputstring/array是要嵌入的文本,可以是字符串或字符串数组
encoding_formatstring否编码格式,支持 "float" 或 "base64"
dimensionsinteger否输出向量的维度
userstring否用户标识符

响应格式

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "embedding": [0.0023064255, -0.009327292, ...],
      "index": 0
    }
  ],
  "model": "text-embedding-ada-002",
  "usage": {
    "prompt_tokens": 8,
    "total_tokens": 8
  }
}

重排序接口

POST /v1/rerank

对文档列表进行重新排序,根据查询的相关性进行排名。

请求参数

{
  "model": "rerank-multilingual-v2.0",
  "query": "什么是人工智能?",
  "documents": [
    "人工智能是计算机科学的一个分支",
    "机器学习是人工智能的子领域",
    "深度学习使用神经网络"
  ],
  "top_n": 3,
  "return_documents": true
}

参数说明

参数类型必需说明
modelstring是重排序模型名称
querystring是查询文本
documentsarray是要排序的文档列表
top_ninteger否返回的文档数量
return_documentsboolean否是否返回文档内容

响应格式

{
  "id": "rerank-123",
  "results": [
    {
      "index": 0,
      "score": 0.95,
      "document": "人工智能是计算机科学的一个分支"
    },
    {
      "index": 1,
      "score": 0.87,
      "document": "机器学习是人工智能的子领域"
    }
  ],
  "model": "rerank-multilingual-v2.0",
  "usage": {
    "total_tokens": 25
  }
}

文本转语音接口

POST /api/v1/audio/speech

仅控制台面:/v1 面只注册了 GET /v1/models、POST /v1/chat/completions、POST /v1/embeddings、POST /v1/rerank,本端点没有 /v1/... 映射。

将文本转换为语音音频文件。

请求参数

{
  "model": "tts-1",
  "input": "你好,这是一段测试语音。",
  "voice": "alloy",
  "response_format": "mp3",
  "speed": 1.0
}

参数说明

参数类型必需说明
modelstring是TTS 模型名称
inputstring是要转换的文本
voicestring是语音类型
response_formatstring否音频格式,默认 mp3
speednumber否语音速度,0.25-4.0,默认 1.0

响应格式

返回音频文件的二进制数据,Content-Type 根据 response_format 设置。

语音转文本接口

POST /api/v1/audio/transcriptions

仅控制台面:/v1 面只注册了 GET /v1/models、POST /v1/chat/completions、POST /v1/embeddings、POST /v1/rerank,本端点没有 /v1/... 映射。

将音频文件转换为文本。

请求参数

使用 multipart/form-data 格式:

file: (音频文件)
model: whisper-1
language: zh
prompt: (可选提示词)
responseFormat: json
temperature: 0

参数说明

参数类型必需说明
filefile是音频文件
modelstring是STT 模型名称
languagestring否音频语言代码
responseFormatstring否响应格式,默认 json
temperaturenumber否采样温度

响应格式

{
  "text": "转录的文本内容"
}

图像生成接口

POST /api/v1/images/generations

仅控制台面:/v1 面只注册了 GET /v1/models、POST /v1/chat/completions、POST /v1/embeddings、POST /v1/rerank,本端点没有 /v1/... 映射。

根据文本描述生成图像。

请求参数

{
  "model": "dall-e-3",
  "prompt": "一只可爱的小猫在花园里玩耍",
  "n": 1,
  "size": "1024x1024",
  "quality": "standard",
  "response_format": "url",
  "style": "vivid",
  "user": "user-123"
}

参数说明

参数类型必需说明
modelstring是图像生成模型名称
promptstring是图像描述文本
ninteger否生成图像数量,默认 1
sizestring否图像尺寸,默认 1024x1024
qualitystring否图像质量,standard 或 hd
response_formatstring否响应格式,url 或 b64_json
stylestring否图像风格,vivid 或 natural
userstring否用户标识符

响应格式

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://example.com/image.png",
      "revised_prompt": "修订后的提示词"
    }
  ]
}

图像编辑接口

POST /api/v1/images/edits

仅控制台面:/v1 面只注册了 GET /v1/models、POST /v1/chat/completions、POST /v1/embeddings、POST /v1/rerank,本端点没有 /v1/... 映射。

编辑现有图像。

请求参数

JSON 请求体(@RequestBody ImageEditDTO.Request),不是 multipart/form-data:

{
  "image": ["<图像引用>"],
  "prompt": "编辑描述",
  "model": "dall-e-2",
  "n": 1,
  "size": "1024x1024",
  "response_format": "url",
  "user": "user-123"
}

对照:/api/v1/audio/transcriptions 是 multipart/form-data,且那里的部件名是 responseFormat(驼峰)。

响应格式

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://example.com/edited-image.png"
    }
  ]
}

错误处理

错误响应格式

{
  "error": {
    "message": "Invalid request: missing required parameter 'model'",
    "type": "invalid_request_error",
    "code": "INVALID_REQUEST"
  }
}

常见错误类型

错误类型说明
invalid_request_error400 / 422 / 其他 4xx —— 请求参数错误
authentication_error401 —— 认证失败
permission_error403 —— 权限不足
not_found_error404 —— 资源不存在
rate_limit_error429 —— 限流 / 配额超限
api_error所有 5xx(含 503)—— 网关或下游内部错误

以上是网关实际只会产出的取值(V1ErrorBodyMapper#errorTypeOf);overloaded_error 不会被产出。该 OpenAI 形状的错误体适用于 /v1/**;/api/** 通常沿用 RouterResponse 形状(success / message / data / errorCode / timestamp)—— 但由认证过滤器写出的响应除外(缺少/非法凭据的 401、RATE_LIMITED_API_KEY 的 429),它在 /api/** 上也发这个 {"error":{"message","type","code"}} 形状。完整例外清单见 错误码对照表。

使用示例

cURL 示例

# 聊天完成
curl -X POST "http://localhost:8080/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {"role": "user", "content": "你好!"}
    ]
  }'

# 文本嵌入
curl -X POST "http://localhost:8080/v1/embeddings" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "model": "text-embedding-ada-002",
    "input": "要嵌入的文本"
  }'

Python 示例

import requests

# 配置
base_url = "http://localhost:8080"
api_key = "your-api-key"
headers = {
    "Content-Type": "application/json",
    "X-API-Key": api_key
}

# 聊天完成
response = requests.post(
    f"{base_url}/v1/chat/completions",
    headers=headers,
    json={
        "model": "gpt-3.5-turbo",
        "messages": [
            {"role": "user", "content": "你好!"}
        ]
    }
)
print(response.json())

# 文本嵌入
response = requests.post(
    f"{base_url}/v1/embeddings",
    headers=headers,
    json={
        "model": "text-embedding-ada-002",
        "input": "要嵌入的文本"
    }
)
print(response.json())

JavaScript 示例

const baseUrl = 'http://localhost:8080';
const apiKey = 'your-api-key';

// 聊天完成
async function chatCompletion() {
  const response = await fetch(`${baseUrl}/v1/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': apiKey
    },
    body: JSON.stringify({
      model: 'gpt-3.5-turbo',
      messages: [
        { role: 'user', content: '你好!' }
      ]
    })
  });

  const data = await response.json();
  console.log(data);
}

// 文本嵌入
async function embedding() {
  const response = await fetch(`${baseUrl}/v1/embeddings`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': apiKey
    },
    body: JSON.stringify({
      model: 'text-embedding-ada-002',
      input: '要嵌入的文本'
    })
  });

  const data = await response.json();
  console.log(data);
}