+57
-5
@@ -422,9 +422,47 @@ func aiPathID(c *gin.Context) (uint, bool) {
|
||||
return uint(id), true
|
||||
}
|
||||
|
||||
// modelCatalog 返回启用渠道聚合去重后的模型目录(含能力),黑名单添加弹窗用。
|
||||
//
|
||||
// @Summary 聚合模型目录
|
||||
// @Tags AI 管理
|
||||
// @Success 200 {object} itemsResponse[service.AggregatedModel]
|
||||
// @Security BearerAuth
|
||||
// @Router /api/v1/ai-model-catalog [get]
|
||||
func (h *aiAdminHandler) modelCatalog(c *gin.Context) {
|
||||
items, err := h.gw.AggregatedModels(c.Request.Context())
|
||||
if err != nil {
|
||||
respondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
}
|
||||
|
||||
// aiSettingsResponse 是 AI 网关全局设置(文档与响应共用)。
|
||||
type aiSettingsResponse struct {
|
||||
// FilterDeprecated 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除
|
||||
FilterDeprecated bool `json:"filterDeprecated"`
|
||||
// StreamGuardEnabled / StreamGuardKB 是 Responses 流式保险丝:
|
||||
// instructions+tools 合计超阈值(KB)的流式请求改非流式上游并合成 SSE
|
||||
StreamGuardEnabled bool `json:"streamGuardEnabled"`
|
||||
StreamGuardKB int `json:"streamGuardKB"`
|
||||
// GrokWebSearch / GrokXSearch 是 xai. 模型服务端搜索工具默认注入开关;
|
||||
// 请求 tools 已包含同名工具时不覆盖
|
||||
GrokWebSearch bool `json:"grokWebSearch"`
|
||||
GrokXSearch bool `json:"grokXSearch"`
|
||||
}
|
||||
|
||||
// currentAiSettings 汇总网关运行时设置为响应体。
|
||||
func (h *aiAdminHandler) currentAiSettings() aiSettingsResponse {
|
||||
guardOn, guardKB := h.gw.StreamGuard()
|
||||
web, x := h.gw.GrokSearch()
|
||||
return aiSettingsResponse{
|
||||
FilterDeprecated: h.gw.FilterDeprecated(),
|
||||
StreamGuardEnabled: guardOn,
|
||||
StreamGuardKB: guardKB,
|
||||
GrokWebSearch: web,
|
||||
GrokXSearch: x,
|
||||
}
|
||||
}
|
||||
|
||||
// aiSettings 返回 AI 网关全局设置。
|
||||
@@ -435,15 +473,16 @@ type aiSettingsResponse struct {
|
||||
// @Security BearerAuth
|
||||
// @Router /api/v1/ai-settings [get]
|
||||
func (h *aiAdminHandler) aiSettings(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, aiSettingsResponse{FilterDeprecated: h.gw.FilterDeprecated()})
|
||||
c.JSON(http.StatusOK, h.currentAiSettings())
|
||||
}
|
||||
|
||||
// updateAiSettings 更新 AI 网关全局设置(当前仅「过滤弃用模型」开关)。
|
||||
// updateAiSettings 更新 AI 网关全局设置(过滤弃用/流式保险丝/grok 搜索工具默认注入)。
|
||||
//
|
||||
// @Summary 更新 AI 网关全局设置
|
||||
// @Tags AI 管理
|
||||
// @Param body body aiSettingsResponse true "开启后已宣布弃用(即使未退役)的模型从列表与路由中排除"
|
||||
// @Param body body aiSettingsResponse true "全量提交;保险丝阈值限 1..1024 KB"
|
||||
// @Success 200 {object} aiSettingsResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Security BearerAuth
|
||||
// @Router /api/v1/ai-settings [put]
|
||||
func (h *aiAdminHandler) updateAiSettings(c *gin.Context) {
|
||||
@@ -452,9 +491,22 @@ func (h *aiAdminHandler) updateAiSettings(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := h.gw.SetFilterDeprecated(c.Request.Context(), req.FilterDeprecated); err != nil {
|
||||
if req.StreamGuardKB < 1 || req.StreamGuardKB > 1024 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "streamGuardKB 须在 1..1024"})
|
||||
return
|
||||
}
|
||||
ctx := c.Request.Context()
|
||||
if err := h.gw.SetFilterDeprecated(ctx, req.FilterDeprecated); err != nil {
|
||||
respondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, aiSettingsResponse{FilterDeprecated: h.gw.FilterDeprecated()})
|
||||
if err := h.gw.SetStreamGuard(ctx, req.StreamGuardEnabled, req.StreamGuardKB); err != nil {
|
||||
respondError(c, err)
|
||||
return
|
||||
}
|
||||
if err := h.gw.SetGrokSearch(ctx, req.GrokWebSearch, req.GrokXSearch); err != nil {
|
||||
respondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, h.currentAiSettings())
|
||||
}
|
||||
|
||||
@@ -587,8 +587,13 @@ func (h *aiGatewayHandler) responsesPassthrough(c *gin.Context, raw []byte, req
|
||||
return
|
||||
}
|
||||
logRespCompat(req.Model, compat)
|
||||
web, x := h.gw.GrokSearch()
|
||||
if injectedBody, injected := service.RespInjectGrokTools(body, req.Model, web, x); len(injected) > 0 {
|
||||
body = injectedBody
|
||||
log.Printf("responses 直通(model=%s): 默认注入 %s", req.Model, strings.Join(injected, ", "))
|
||||
}
|
||||
if req.Stream {
|
||||
if len(body) > service.RespStreamUpgradeLimit {
|
||||
if on, kb := h.gw.StreamGuard(); on && service.RespGuardBytes(body) > kb*1024 {
|
||||
h.responsesStreamUpgrade(c, body, req, compat)
|
||||
return
|
||||
}
|
||||
@@ -630,9 +635,9 @@ func logRespCompat(model string, compat service.RespCompat) {
|
||||
log.Printf("responses 直通(model=%s): %s", model, strings.Join(parts, "; "))
|
||||
}
|
||||
|
||||
// responsesStreamUpgrade 流式升级回退:上游对超大流式请求会在推理途中掐断
|
||||
// (实测 >~82KB,见 RespStreamUpgradeLimit),超限时改调非流式上游拿完整响应,
|
||||
// 本地合成最小 SSE 事件序列回给客户端;丢失增量输出,换会话不中断。
|
||||
// responsesStreamUpgrade 流式升级回退:instructions+tools 合计超过保险丝阈值
|
||||
// (设置页 AI Tab 配置,默认开 60KB;上游对 >≈64.5KB 会静默断流)时改调非流式
|
||||
// 上游拿完整响应,本地合成最小 SSE 事件序列回给客户端;丢失增量输出,换会话不中断。
|
||||
func (h *aiGatewayHandler) responsesStreamUpgrade(c *gin.Context, body []byte, req aiwire.RespRequest, compat service.RespCompat) {
|
||||
start := time.Now()
|
||||
nsBody, err := service.RespDisableStream(body)
|
||||
@@ -640,7 +645,8 @@ func (h *aiGatewayHandler) responsesStreamUpgrade(c *gin.Context, body []byte, r
|
||||
aiError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
|
||||
return
|
||||
}
|
||||
log.Printf("responses 直通(model=%s): 请求体 %dKB 超流式安全上限,改走非流式合成 SSE", req.Model, len(body)/1024)
|
||||
log.Printf("responses 直通(model=%s): instructions+tools %dKB 超保险丝阈值,改走非流式合成 SSE",
|
||||
req.Model, service.RespGuardBytes(body)/1024)
|
||||
payload, meta, err := h.gw.RespPassthrough(c.Request.Context(), nsBody, req.Model, keyGroup(c))
|
||||
entry := h.logEntry(c, "responses", req.Model, true, meta, start)
|
||||
if err != nil {
|
||||
|
||||
@@ -39,6 +39,7 @@ func registerAiAdmin(secured *gin.RouterGroup, aiGateway *service.AiGatewayServi
|
||||
secured.GET("/ai-channels/:id/models", aiadmin.listChannelModels)
|
||||
secured.POST("/ai-channels/:id/test-model", aiadmin.testChannelModel)
|
||||
secured.GET("/ai-models", aiadmin.gatewayModels)
|
||||
secured.GET("/ai-model-catalog", aiadmin.modelCatalog)
|
||||
secured.GET("/ai-settings", aiadmin.aiSettings)
|
||||
secured.PUT("/ai-settings", aiadmin.updateAiSettings)
|
||||
secured.GET("/ai-blacklist", aiadmin.listBlacklist)
|
||||
|
||||
Reference in New Issue
Block a user