Files

463 lines
18 KiB
Go
Raw Permalink 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.
package docs
import (
"context"
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
v1 "github.com/qianfree/team-api/api/docs/v1"
"github.com/qianfree/team-api/internal/logic/common"
"github.com/qianfree/team-api/internal/service"
)
func init() {
service.RegisterDocs(New())
}
type sDocs struct{}
func New() *sDocs {
return &sDocs{}
}
func (s *sDocs) OpenAPISpec(ctx context.Context, _ *v1.OpenAPISpecReq) (json.RawMessage, error) {
// Try cache first
cached, err := g.Redis().Do(ctx, "GET", "docs:openapi:spec")
if err == nil && !cached.IsNil() && !cached.IsEmpty() {
return json.RawMessage(cached.String()), nil
}
spec := buildOpenAPISpec(ctx)
raw, err := json.Marshal(spec)
if err != nil {
return nil, err
}
// Cache for 1 hour
_, _ = g.Redis().Do(ctx, "SET", "docs:openapi:spec", string(raw), "EX", 3600)
return raw, nil
}
func buildOpenAPISpec(ctx context.Context) map[string]any {
baseURL := common.Config().GetString(ctx, "api_base_url")
if baseURL == "" {
baseURL = "https://api.team-api.example.com"
}
return map[string]any{
"openapi": "3.0.3",
"info": map[string]any{
"title": "Team API",
"description": "多租户 大模型 API 网关,兼容 OpenAI API 格式",
"version": "1.0.0",
},
"servers": []map[string]any{
{"url": baseURL, "description": "Production"},
},
"paths": map[string]any{
"/v1/chat/completions": map[string]any{
"post": map[string]any{
"summary": "Chat Completions",
"description": "创建聊天补全,兼容 OpenAI API 格式,支持流式和非流式响应。",
"operationId": "createChatCompletion",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{"$ref": "#/components/schemas/ChatCompletionRequest"},
},
},
},
"responses": map[string]any{
"200": map[string]any{
"description": "Successful response",
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{"$ref": "#/components/schemas/ChatCompletionResponse"},
},
"text/event-stream": map[string]any{
"schema": map[string]any{"type": "string", "description": "SSE stream"},
},
},
},
"401": map[string]any{"description": "Authentication failed"},
"402": map[string]any{"description": "Insufficient quota"},
"429": map[string]any{"description": "Rate limit exceeded"},
"500": map[string]any{"description": "Internal error"},
},
},
},
"/v1/completions": map[string]any{
"post": map[string]any{
"summary": "Text Completions",
"description": "文本补全,兼容 OpenAI API 格式。",
"operationId": "createCompletion",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{"$ref": "#/components/schemas/CompletionRequest"},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Successful response"},
},
},
},
"/v1/embeddings": map[string]any{
"post": map[string]any{
"summary": "Embeddings",
"description": "创建文本向量嵌入,兼容 OpenAI API 格式。",
"operationId": "createEmbedding",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{"$ref": "#/components/schemas/EmbeddingRequest"},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Successful response"},
},
},
},
"/v1/images/generations": map[string]any{
"post": map[string]any{
"summary": "Image Generations",
"description": "AI 图像生成,兼容 OpenAI API 格式。",
"operationId": "createImage",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"model", "prompt"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "dall-e-3"},
"prompt": map[string]any{"type": "string"},
"n": map[string]any{"type": "integer", "default": 1},
"size": map[string]any{"type": "string", "enum": []string{"256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"}},
},
},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Successful response"},
},
},
},
"/v1/images/generations/async": map[string]any{
"post": map[string]any{
"summary": "Image Generations Async (Submit)",
"description": "异步图像生成任务提交。阿里云百炼(DashScope)等异步图像上游无法经同步接口一次性返回,需提交任务拿到 task_id 后轮询 /v1/images/generations/async/{task_id} 取图。",
"operationId": "createImageAsync",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"model", "prompt"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "wanx2.1-t2i-turbo"},
"prompt": map[string]any{"type": "string"},
"n": map[string]any{"type": "integer", "default": 1},
"size": map[string]any{"type": "string", "example": "1024x1024"},
},
},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "任务已提交,返回 task_id 与初始状态"},
},
},
},
"/v1/images/generations/async/{task_id}": map[string]any{
"get": map[string]any{
"summary": "Image Generations Async (Fetch)",
"description": "查询异步图像生成任务结果。轮询直到 status 为 SUCCESS(含图片 url)或 FAILURE(含 error),建议间隔 2~3 秒。",
"operationId": "getImageAsync",
"security": []map[string]any{{"BearerAuth": []any{}}},
"parameters": []map[string]any{
{
"name": "task_id",
"in": "path",
"required": true,
"schema": map[string]any{"type": "string"},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "任务状态与结果"},
},
},
},
"/v1/videos": map[string]any{
"post": map[string]any{
"summary": "Create Video",
"description": "创建视频生成任务(OpenAI Videos 协议,兼容 openai-python / openai-node SDK 的 client.videos.create)。请求体支持 multipart/form-data(官方 SDK 形态,input_reference 可为文件 part)与 application/json 两种编码。返回官方 Video 对象(status: queued),轮询 GET /v1/videos/{video_id} 直到 completed,再经 GET /v1/videos/{video_id}/content 获取视频二进制。",
"operationId": "createVideo",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"model", "prompt"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "kling-v2-master"},
"prompt": map[string]any{"type": "string"},
"seconds": map[string]any{"type": "string", "enum": []string{"4", "8", "12"}, "default": "4"},
"size": map[string]any{"type": "string", "enum": []string{"720x1280", "1280x720", "1024x1792", "1792x1024"}, "default": "720x1280"},
"aspect_ratio": map[string]any{"type": "string", "description": "扩展字段:屏幕比例,如 16:9 / 9:16 / 1:1;取值原样透传上游,不传由模型取默认档"},
"sound": map[string]any{"type": "string", "enum": []string{"on", "off"}, "description": "扩展字段:是否包含声音;仅部分模型支持(可灵 / 豆包),其余模型忽略"},
"input_reference": map[string]any{"type": "string", "format": "binary", "description": "可选参考图(图生视频),文件上传或 {\"image_url\": \"...\"} 引用对象;file_id 形态不支持"},
},
},
},
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"model", "prompt"},
"properties": map[string]any{
"model": map[string]any{"type": "string"},
"prompt": map[string]any{"type": "string"},
"seconds": map[string]any{"type": "string"},
"size": map[string]any{"type": "string"},
"aspect_ratio": map[string]any{"type": "string", "description": "扩展字段:屏幕比例,如 16:9 / 9:16 / 1:1"},
"sound": map[string]any{"type": "string", "enum": []string{"on", "off"}, "description": "扩展字段:是否包含声音(也可用布尔形态 generate_audio)"},
"generate_audio": map[string]any{"type": "boolean", "description": "扩展字段:sound 的等价布尔形态,sound 缺省时生效"},
"input_reference": map[string]any{"type": "object", "properties": map[string]any{"image_url": map[string]any{"type": "string"}}},
},
},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Video 对象(object: video,status: queued)"},
},
},
},
"/v1/videos/{video_id}": map[string]any{
"get": map[string]any{
"summary": "Retrieve Video",
"description": "查询视频生成任务,返回官方 Video 对象。status: queued / in_progress / completed / failed;非终态响应携带 openai-poll-after-ms 轮询间隔建议头。生成失败以 status=failed + error 对象表达(HTTP 仍为 200)。",
"operationId": "retrieveVideo",
"security": []map[string]any{{"BearerAuth": []any{}}},
"parameters": []map[string]any{
{
"name": "video_id",
"in": "path",
"required": true,
"schema": map[string]any{"type": "string"},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Video 对象(含 progress 百分比、completed_at、失败时的 error)"},
"404": map[string]any{"description": "视频不存在或已被删除"},
},
},
"delete": map[string]any{
"summary": "Delete Video",
"description": "删除已完成或已失败的视频任务(软删除,计费与审计记录保留),删除后查询与下载均返回 404。进行中的任务不可删除。",
"operationId": "deleteVideo",
"security": []map[string]any{{"BearerAuth": []any{}}},
"parameters": []map[string]any{
{
"name": "video_id",
"in": "path",
"required": true,
"schema": map[string]any{"type": "string"},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "删除确认(object: video.deleted,deleted: true)"},
"400": map[string]any{"description": "任务非终态或计费未结算,不可删除"},
"404": map[string]any{"description": "视频不存在或已被删除"},
},
},
},
"/v1/videos/{video_id}/content": map[string]any{
"get": map[string]any{
"summary": "Download Video Content",
"description": "下载已生成完成的视频二进制流(mp4)。仅 status=completed 的任务可下载;variant 仅支持默认 video(thumbnail / spritesheet 暂不支持)。",
"operationId": "downloadVideoContent",
"security": []map[string]any{{"BearerAuth": []any{}}},
"parameters": []map[string]any{
{
"name": "video_id",
"in": "path",
"required": true,
"schema": map[string]any{"type": "string"},
},
{
"name": "variant",
"in": "query",
"required": false,
"schema": map[string]any{"type": "string", "enum": []string{"video"}, "default": "video"},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "视频二进制流(video/mp4)"},
"400": map[string]any{"description": "任务未完成或无可用内容"},
"404": map[string]any{"description": "视频不存在或已被删除"},
"502": map[string]any{"description": "上游内容拉取失败"},
},
},
},
"/v1/messages": map[string]any{
"post": map[string]any{
"summary": "Claude Messages",
"description": "Claude Messages API,兼容 Anthropic API 格式。",
"operationId": "createClaudeMessage",
"security": []map[string]any{{"BearerAuth": []any{}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"model", "messages"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "claude-sonnet-4-20250514"},
"messages": map[string]any{"type": "array"},
"max_tokens": map[string]any{"type": "integer", "default": 4096},
"stream": map[string]any{"type": "boolean", "default": false},
},
},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Successful response"},
},
},
},
"/v1/models": map[string]any{
"get": map[string]any{
"summary": "List Models",
"description": "获取可用模型列表。",
"operationId": "listModels",
"security": []map[string]any{{"BearerAuth": []any{}}},
"responses": map[string]any{
"200": map[string]any{"description": "Successful response"},
},
},
},
"/v1/models/{model_id}": map[string]any{
"get": map[string]any{
"summary": "Get Model",
"description": "获取模型详情。",
"operationId": "getModel",
"security": []map[string]any{{"BearerAuth": []any{}}},
"parameters": []map[string]any{
{"name": "model_id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
},
"responses": map[string]any{
"200": map[string]any{"description": "Successful response"},
},
},
},
},
"components": map[string]any{
"securitySchemes": map[string]any{
"BearerAuth": map[string]any{
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
},
},
"schemas": map[string]any{
"ChatCompletionRequest": map[string]any{
"type": "object",
"required": []string{"model", "messages"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "gpt-4o", "description": "模型 ID"},
"messages": map[string]any{
"type": "array",
"items": map[string]any{"$ref": "#/components/schemas/ChatMessage"},
},
"temperature": map[string]any{"type": "number", "minimum": 0, "maximum": 2, "default": 1},
"top_p": map[string]any{"type": "number", "minimum": 0, "maximum": 1, "default": 1},
"max_tokens": map[string]any{"type": "integer", "minimum": 1},
"stream": map[string]any{"type": "boolean", "default": false},
"frequency_penalty": map[string]any{"type": "number", "minimum": -2, "maximum": 2},
"presence_penalty": map[string]any{"type": "number", "minimum": -2, "maximum": 2},
"stop": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
},
},
"ChatMessage": map[string]any{
"type": "object",
"required": []string{"role", "content"},
"properties": map[string]any{
"role": map[string]any{"type": "string", "enum": []string{"system", "user", "assistant"}},
"content": map[string]any{"type": "string"},
},
},
"ChatCompletionResponse": map[string]any{
"type": "object",
"properties": map[string]any{
"id": map[string]any{"type": "string"},
"object": map[string]any{"type": "string"},
"created": map[string]any{"type": "integer"},
"model": map[string]any{"type": "string"},
"choices": map[string]any{
"type": "array",
"items": map[string]any{"$ref": "#/components/schemas/ChatChoice"},
},
"usage": map[string]any{"$ref": "#/components/schemas/Usage"},
},
},
"ChatChoice": map[string]any{
"type": "object",
"properties": map[string]any{
"index": map[string]any{"type": "integer"},
"message": map[string]any{"$ref": "#/components/schemas/ChatMessage"},
"finish_reason": map[string]any{"type": "string"},
},
},
"Usage": map[string]any{
"type": "object",
"properties": map[string]any{
"prompt_tokens": map[string]any{"type": "integer"},
"completion_tokens": map[string]any{"type": "integer"},
"total_tokens": map[string]any{"type": "integer"},
},
},
"CompletionRequest": map[string]any{
"type": "object",
"required": []string{"model", "prompt"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "gpt-4o"},
"prompt": map[string]any{"type": "string"},
"max_tokens": map[string]any{"type": "integer", "default": 16},
"stream": map[string]any{"type": "boolean", "default": false},
},
},
"EmbeddingRequest": map[string]any{
"type": "object",
"required": []string{"model", "input"},
"properties": map[string]any{
"model": map[string]any{"type": "string", "example": "text-embedding-3-small"},
"input": map[string]any{"oneOf": []any{
map[string]any{"type": "string"},
map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
}},
},
},
},
},
}
}