Skip to content

Commit 45f0833

Browse files
authored
feat: swagger docs (#50)
* feat: init swag * feat: relay swagger * feat: swag workflow * fix: fmt swagger commend * docs: swagger readme * feat: relay swag headers
1 parent a3f6afe commit 45f0833

129 files changed

Lines changed: 21022 additions & 1675 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
steps:
4848
- name: Checkout
4949
uses: actions/checkout@v4
50-
50+
5151
- name: Download tiktoken
5252
run: |
5353
bash common/tiktoken/assest.sh
@@ -57,6 +57,11 @@ jobs:
5757
with:
5858
go-version: "1.23"
5959

60+
- name: Generate Swagger
61+
run: |
62+
go install github.com/swaggo/swag/cmd/swag@latest
63+
swag init
64+
6065
- name: Build
6166
run: |
6267
export GOOS=${{ matrix.targets.GOOS }}

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ RUN apk add --no-cache curl
88

99
RUN sh common/tiktoken/assest.sh
1010

11+
RUN go install github.com/swaggo/swag/cmd/swag@latest
12+
13+
RUN swag init
14+
1115
RUN go build -trimpath -tags "jsoniter" -ldflags "-s -w" -o aiproxy
1216

1317
FROM alpine:latest

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Next-generation AI gateway, using OpenAI as the protocol entry point.
3333
- Think model support `<think>` split to `reasoning_content`
3434
- Prompt Token Cache billing support
3535
- Inline tiktoken, no need to download tiktoken file
36+
- API `Swagger` documentation support `http://host:port/swagger/index.html`
3637

3738
## Deploy
3839

README.zh.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- Think 模型支持 `<think>` 切分到 `reasoning_content`
3535
- 提示词缓存计费支持
3636
- 内敛分词器,无需额外下载 tiktoken 文件
37+
- API `Swagger` 文档支持 `http://host:port/swagger/index.html`
3738

3839
## 部署
3940

controller/channel-billing.go

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,11 @@ import (
99
"time"
1010

1111
"github.com/gin-gonic/gin"
12-
"github.com/labring/aiproxy/common/balance"
1312
"github.com/labring/aiproxy/common/notify"
1413
"github.com/labring/aiproxy/middleware"
1514
"github.com/labring/aiproxy/model"
1615
"github.com/labring/aiproxy/relay/adaptor"
17-
"github.com/labring/aiproxy/relay/adaptor/openai"
1816
"github.com/labring/aiproxy/relay/channeltype"
19-
log "github.com/sirupsen/logrus"
2017
)
2118

2219
// https://github.com/labring/aiproxy/issues/79
@@ -43,6 +40,16 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
4340
return 0, nil
4441
}
4542

43+
// UpdateChannelBalance godoc
44+
//
45+
// @Summary Update channel balance
46+
// @Description Updates the balance for a single channel
47+
// @Tags channel
48+
// @Produce json
49+
// @Security ApiKeyAuth
50+
// @Param id path int true "Channel ID"
51+
// @Success 200 {object} middleware.APIResponse{data=float64}
52+
// @Router /api/channel/{id}/balance [get]
4653
func UpdateChannelBalance(c *gin.Context) {
4754
id, err := strconv.Atoi(c.Param("id"))
4855
if err != nil {
@@ -69,11 +76,7 @@ func UpdateChannelBalance(c *gin.Context) {
6976
})
7077
return
7178
}
72-
c.JSON(http.StatusOK, middleware.APIResponse{
73-
Success: true,
74-
Message: "",
75-
Data: balance,
76-
})
79+
middleware.SuccessResponse(c, balance)
7780
}
7881

7982
func updateAllChannelsBalance() error {
@@ -105,6 +108,15 @@ func updateAllChannelsBalance() error {
105108
return nil
106109
}
107110

111+
// UpdateAllChannelsBalance godoc
112+
//
113+
// @Summary Update all channels balance
114+
// @Description Updates the balance for all channels
115+
// @Tags channel
116+
// @Produce json
117+
// @Security ApiKeyAuth
118+
// @Success 200 {object} middleware.APIResponse
119+
// @Router /api/channels/balance [get]
108120
func UpdateAllChannelsBalance(c *gin.Context) {
109121
err := updateAllChannelsBalance()
110122
if err != nil {
@@ -120,33 +132,3 @@ func UpdateChannelsBalance(frequency time.Duration) {
120132
_ = updateAllChannelsBalance()
121133
}
122134
}
123-
124-
// subscription
125-
func GetSubscription(c *gin.Context) {
126-
group := middleware.GetGroup(c)
127-
b, _, err := balance.GetGroupRemainBalance(c, *group)
128-
if err != nil {
129-
if errors.Is(err, balance.ErrNoRealNameUsedAmountLimit) {
130-
middleware.ErrorResponse(c, http.StatusForbidden, err.Error())
131-
return
132-
}
133-
log.Errorf("get group (%s) balance failed: %s", group.ID, err)
134-
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Sprintf("get group (%s) balance failed", group.ID))
135-
return
136-
}
137-
token := middleware.GetToken(c)
138-
quota := token.Quota
139-
if quota <= 0 {
140-
quota = b
141-
}
142-
c.JSON(http.StatusOK, openai.SubscriptionResponse{
143-
HardLimitUSD: quota + token.UsedAmount,
144-
SoftLimitUSD: b,
145-
SystemHardLimitUSD: quota + token.UsedAmount,
146-
})
147-
}
148-
149-
func GetUsage(c *gin.Context) {
150-
token := middleware.GetToken(c)
151-
c.JSON(http.StatusOK, openai.UsageResponse{TotalUsage: token.UsedAmount * 100})
152-
}

controller/channel-test.go

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
"github.com/labring/aiproxy/monitor"
2626
"github.com/labring/aiproxy/relay/channeltype"
2727
"github.com/labring/aiproxy/relay/meta"
28-
"github.com/labring/aiproxy/relay/relaymode"
28+
"github.com/labring/aiproxy/relay/mode"
2929
"github.com/labring/aiproxy/relay/utils"
3030
log "github.com/sirupsen/logrus"
3131
)
@@ -60,7 +60,7 @@ func testSingleModel(mc *model.ModelCaches, channel *model.Channel, modelName st
6060
if !ok {
6161
return nil, errors.New(modelName + " model config not found")
6262
}
63-
if modelConfig.Type == relaymode.Unknown {
63+
if modelConfig.Type == mode.Unknown {
6464
newModelConfig := guessModelConfig(modelName)
6565
if newModelConfig != nil {
6666
modelConfig = newModelConfig
@@ -81,7 +81,7 @@ func testSingleModel(mc *model.ModelCaches, channel *model.Channel, modelName st
8181
}, nil
8282
}
8383

84-
body, mode, err := utils.BuildRequest(modelConfig)
84+
body, m, err := utils.BuildRequest(modelConfig)
8585
if err != nil {
8686
return nil, err
8787
}
@@ -97,23 +97,23 @@ func testSingleModel(mc *model.ModelCaches, channel *model.Channel, modelName st
9797

9898
meta := meta.NewMeta(
9999
channel,
100-
mode,
100+
m,
101101
modelName,
102102
modelConfig,
103103
meta.WithRequestID(channelTestRequestID),
104104
)
105-
relayController, ok := relayController(mode)
105+
relayController, ok := relayController(m)
106106
if !ok {
107-
return nil, fmt.Errorf("relay mode %d not implemented", mode)
107+
return nil, fmt.Errorf("relay mode %d not implemented", m)
108108
}
109109
result := relayController(meta, newc)
110110
success := result.Error == nil
111111
var respStr string
112112
var code int
113113
if success {
114114
switch meta.Mode {
115-
case relaymode.AudioSpeech,
116-
relaymode.ImagesGenerations:
115+
case mode.AudioSpeech,
116+
mode.ImagesGenerations:
117117
respStr = ""
118118
default:
119119
respStr = w.Body.String()
@@ -136,6 +136,18 @@ func testSingleModel(mc *model.ModelCaches, channel *model.Channel, modelName st
136136
)
137137
}
138138

139+
// TestChannel godoc
140+
//
141+
// @Summary Test channel model
142+
// @Description Tests a single model in the channel
143+
// @Tags channel
144+
// @Produce json
145+
// @Security ApiKeyAuth
146+
// @Param id path int true "Channel ID"
147+
// @Param model path string true "Model name"
148+
// @Success 200 {object} middleware.APIResponse{data=model.ChannelTest}
149+
// @Router /api/channel/{id}/{model} [get]
150+
//
139151
//nolint:goconst
140152
func TestChannel(c *gin.Context) {
141153
id, err := strconv.Atoi(c.Param("id"))
@@ -193,13 +205,13 @@ func TestChannel(c *gin.Context) {
193205
})
194206
}
195207

196-
type testResult struct {
208+
type TestResult struct {
197209
Data *model.ChannelTest `json:"data,omitempty"`
198210
Message string `json:"message,omitempty"`
199211
Success bool `json:"success"`
200212
}
201213

202-
func processTestResult(mc *model.ModelCaches, channel *model.Channel, modelName string, returnSuccess bool, successResponseBody bool) *testResult {
214+
func processTestResult(mc *model.ModelCaches, channel *model.Channel, modelName string, returnSuccess bool, successResponseBody bool) *TestResult {
203215
ct, err := testSingleModel(mc, channel, modelName)
204216

205217
e := &utils.UnsupportedModelTypeError{}
@@ -208,7 +220,7 @@ func processTestResult(mc *model.ModelCaches, channel *model.Channel, modelName
208220
return nil
209221
}
210222

211-
result := &testResult{
223+
result := &TestResult{
212224
Success: err == nil,
213225
}
214226
if err != nil {
@@ -232,6 +244,16 @@ func processTestResult(mc *model.ModelCaches, channel *model.Channel, modelName
232244
return result
233245
}
234246

247+
// TestChannelModels godoc
248+
//
249+
// @Summary Test channel models
250+
// @Description Tests all models in the channel
251+
// @Tags channel
252+
// @Produce json
253+
// @Security ApiKeyAuth
254+
// @Param id path int true "Channel ID"
255+
// @Success 200 {object} middleware.APIResponse{data=[]TestResult}
256+
// @Router /api/channel/{id}/models [get]
235257
func TestChannelModels(c *gin.Context) {
236258
id, err := strconv.Atoi(c.Param("id"))
237259
if err != nil {
@@ -259,7 +281,7 @@ func TestChannelModels(c *gin.Context) {
259281
common.SetEventStreamHeaders(c)
260282
}
261283

262-
results := make([]*testResult, 0)
284+
results := make([]*TestResult, 0)
263285
resultsMutex := sync.Mutex{}
264286
hasError := atomic.Bool{}
265287

@@ -318,6 +340,16 @@ func TestChannelModels(c *gin.Context) {
318340
}
319341
}
320342

343+
// TestAllChannels godoc
344+
//
345+
// @Summary Test all channels
346+
// @Description Tests all channels
347+
// @Tags channel
348+
// @Produce json
349+
// @Security ApiKeyAuth
350+
// @Success 200 {object} middleware.APIResponse{data=[]TestResult}
351+
//
352+
// @Router /api/channels/test [get]
321353
func TestAllChannels(c *gin.Context) {
322354
testDisabled := c.Query("test_disabled") == "true"
323355
var channels []*model.Channel
@@ -342,7 +374,7 @@ func TestAllChannels(c *gin.Context) {
342374
common.SetEventStreamHeaders(c)
343375
}
344376

345-
results := make([]*testResult, 0)
377+
results := make([]*TestResult, 0)
346378
resultsMutex := sync.Mutex{}
347379
hasErrorMap := make(map[int]*atomic.Bool)
348380

0 commit comments

Comments
 (0)