feat(routing): add formal chat route endpoint

This commit is contained in:
phamnazage-jpg
2026-05-29 13:17:56 +08:00
parent 09b43ce2d8
commit ecdeedb103
4 changed files with 325 additions and 3 deletions

View File

@@ -40,6 +40,22 @@ type ProxyRouteChatCompletionsRequest struct {
Sync bool `json:"sync,omitempty"`
}
type RouteChatCompletionsRequest struct {
RequestID string `json:"request_id,omitempty"`
LogicalGroupID string `json:"logical_group_id"`
Model string `json:"model"`
Scope string `json:"scope"`
SubjectID string `json:"subject_id"`
UserKey string `json:"user_key,omitempty"`
ConversationKey string `json:"conversation_key,omitempty"`
GatewayAPIKey string `json:"gateway_api_key,omitempty"`
SubscriptionUserID string `json:"subscription_user_id,omitempty"`
Messages []ChatCompletionMessage `json:"messages,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
Sync bool `json:"sync,omitempty"`
}
type ChatCompletionMessage struct {
Role string `json:"role"`
Content string `json:"content"`
@@ -68,6 +84,33 @@ type RouteChatCompletionsForwardInfo struct {
Response any `json:"response,omitempty"`
}
type RouteChatCompletionsResult struct {
RequestID string `json:"request_id"`
Backend string `json:"backend"`
LogicalGroupID string `json:"logical_group_id"`
Model string `json:"model"`
Scope string `json:"scope"`
SubjectID string `json:"subject_id"`
StickyKey string `json:"sticky_key"`
StickyHit bool `json:"sticky_hit"`
StickyAction string `json:"sticky_action"`
FallbackUsed bool `json:"fallback_used,omitempty"`
SelectedRoute RouteChatCompletionsRouteInfo `json:"selected_route"`
Forward RouteChatCompletionsForwardInfo `json:"forward"`
}
type RouteChatCompletionsRouteInfo struct {
RouteID string `json:"route_id"`
RouteName string `json:"route_name,omitempty"`
ShadowHostID string `json:"shadow_host_id"`
ShadowGroupID string `json:"shadow_group_id"`
ShadowModel string `json:"shadow_model,omitempty"`
Priority int `json:"priority"`
Weight int `json:"weight"`
BoundAt string `json:"bound_at,omitempty"`
ExpiresAt string `json:"expires_at,omitempty"`
}
func handleProxyRouteChatCompletions(w http.ResponseWriter, r *http.Request, fn func(context.Context, ProxyRouteChatCompletionsRequest) (ProxyRouteChatCompletionsResult, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "proxy-route-chat-completions action is not configured"})
@@ -86,6 +129,24 @@ func handleProxyRouteChatCompletions(w http.ResponseWriter, r *http.Request, fn
writeJSON(w, http.StatusOK, result)
}
func handleRouteChatCompletions(w http.ResponseWriter, r *http.Request, fn func(context.Context, RouteChatCompletionsRequest) (RouteChatCompletionsResult, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "route-chat-completions action is not configured"})
return
}
var req RouteChatCompletionsRequest
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
result, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, result)
}
func buildProxyRouteChatCompletionsAction(
sqliteDSN string,
resolveRoute func(context.Context, ResolveRouteRequest) (ResolveRouteInfo, error),
@@ -166,6 +227,59 @@ func buildProxyRouteChatCompletionsAction(
}
}
func buildRouteChatCompletionsAction(
proxyRouteChatCompletions func(context.Context, ProxyRouteChatCompletionsRequest) (ProxyRouteChatCompletionsResult, error),
) func(context.Context, RouteChatCompletionsRequest) (RouteChatCompletionsResult, error) {
return func(ctx context.Context, req RouteChatCompletionsRequest) (RouteChatCompletionsResult, error) {
result, err := proxyRouteChatCompletions(ctx, ProxyRouteChatCompletionsRequest{
RequestID: req.RequestID,
LogicalGroupID: req.LogicalGroupID,
PublicModel: req.Model,
Scope: req.Scope,
SubjectID: req.SubjectID,
UserKey: req.UserKey,
ConversationKey: req.ConversationKey,
GatewayAPIKey: req.GatewayAPIKey,
SubscriptionUserID: req.SubscriptionUserID,
Messages: req.Messages,
MaxTokens: req.MaxTokens,
Temperature: req.Temperature,
Sync: req.Sync,
})
if err != nil {
return RouteChatCompletionsResult{}, err
}
return routeChatCompletionsResultFromProxy(result), nil
}
}
func routeChatCompletionsResultFromProxy(result ProxyRouteChatCompletionsResult) RouteChatCompletionsResult {
return RouteChatCompletionsResult{
RequestID: result.Resolve.RequestID,
Backend: result.Resolve.Backend,
LogicalGroupID: result.Resolve.LogicalGroupID,
Model: result.Resolve.PublicModel,
Scope: result.Resolve.Scope,
SubjectID: result.Resolve.SubjectID,
StickyKey: result.Resolve.StickyKey,
StickyHit: result.Resolve.StickyHit,
StickyAction: result.Resolve.StickyAction,
FallbackUsed: result.Resolve.FallbackUsed,
SelectedRoute: RouteChatCompletionsRouteInfo{
RouteID: result.Resolve.RouteID,
RouteName: result.Resolve.RouteName,
ShadowHostID: result.Resolve.ShadowHostID,
ShadowGroupID: result.Resolve.ShadowGroupID,
ShadowModel: result.Resolve.ShadowModel,
Priority: result.Resolve.Priority,
Weight: result.Resolve.Weight,
BoundAt: result.Resolve.BoundAt,
ExpiresAt: result.Resolve.ExpiresAt,
},
Forward: result.Forward,
}
}
func appendProxyRouteDecisionLog(
ctx context.Context,
writerSource *lazyRouteLogWriter,