| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- package service
- import (
- "context"
- "strings"
- "time"
- infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
- )
- const (
- CompositeRouteMatchExact = "exact"
- CompositeRouteMatchPrefix = "prefix"
- CompositeRouteEndpointAny = "any"
- CompositeRouteEndpointMessages = "messages"
- CompositeRouteEndpointCountTokens = "count_tokens"
- CompositeRouteEndpointResponses = "responses"
- CompositeRouteEndpointChatCompletions = "chat_completions"
- CompositeRouteEndpointEmbeddings = "embeddings"
- CompositeRouteEndpointImages = "images"
- CompositeRouteEndpointGemini = "gemini"
- CompositeRouteSourceExplicit = "route"
- CompositeRouteSourceDetector = "detector"
- CompositeRouteSourceAccount = "account_model"
- )
- // CompositeModelOwnership identifies the concrete provider that exposes a
- // public model through an account-level exact mapping.
- type CompositeModelOwnership struct {
- TargetPlatform string
- Matched bool
- Ambiguous bool
- }
- type CompositeModelOwnershipResolver func(context.Context, int64, string) (CompositeModelOwnership, error)
- var (
- ErrCompositeRouteNotFound = infraerrors.NotFound("COMPOSITE_ROUTE_NOT_FOUND", "composite route not found")
- ErrCompositeRouteExists = infraerrors.Conflict("COMPOSITE_ROUTE_EXISTS", "composite route already exists")
- )
- // CompositeModelRoute maps one public model identifier in a composite group to
- // the concrete provider/model that should handle the request.
- type CompositeModelRoute struct {
- ID int64 `json:"id"`
- GroupID int64 `json:"group_id"`
- PublicModel string `json:"public_model"`
- MatchType string `json:"match_type"`
- TargetPlatform string `json:"target_platform"`
- UpstreamModel string `json:"upstream_model"`
- Endpoint string `json:"endpoint"`
- Priority int `json:"priority"`
- Enabled bool `json:"enabled"`
- Notes string `json:"notes"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
- }
- type CompositeRoutePreviewRequest struct {
- Model string `json:"model"`
- Endpoint string `json:"endpoint"`
- }
- type CompositeRouteDecision struct {
- Matched bool `json:"matched"`
- Source string `json:"source"`
- GroupID int64 `json:"group_id"`
- PublicModel string `json:"public_model"`
- TargetPlatform string `json:"target_platform"`
- UpstreamModel string `json:"upstream_model"`
- Endpoint string `json:"endpoint"`
- Route *CompositeModelRoute `json:"route,omitempty"`
- Reason string `json:"reason,omitempty"`
- }
- type CompositeRouteInput struct {
- PublicModel string
- MatchType string
- TargetPlatform string
- UpstreamModel string
- Endpoint string
- Priority int
- Enabled bool
- Notes string
- }
- type CompositeModelRouteRepository interface {
- ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]CompositeModelRoute, error)
- Create(ctx context.Context, route *CompositeModelRoute) error
- Update(ctx context.Context, route *CompositeModelRoute) error
- Delete(ctx context.Context, id int64) error
- DeleteByGroup(ctx context.Context, groupID int64) error
- }
- func normalizeCompositeRouteEndpoint(endpoint string) string {
- endpoint = strings.ToLower(strings.TrimSpace(endpoint))
- if endpoint == "" {
- return CompositeRouteEndpointAny
- }
- switch endpoint {
- case CompositeRouteEndpointMessages,
- CompositeRouteEndpointCountTokens,
- CompositeRouteEndpointResponses,
- CompositeRouteEndpointChatCompletions,
- CompositeRouteEndpointEmbeddings,
- CompositeRouteEndpointImages,
- CompositeRouteEndpointGemini:
- return endpoint
- default:
- return CompositeRouteEndpointAny
- }
- }
- func normalizeCompositeRouteMatchType(matchType string) string {
- matchType = strings.ToLower(strings.TrimSpace(matchType))
- switch matchType {
- case CompositeRouteMatchPrefix:
- return CompositeRouteMatchPrefix
- default:
- return CompositeRouteMatchExact
- }
- }
- func normalizeCompositeRouteInput(input CompositeRouteInput) CompositeRouteInput {
- input.PublicModel = strings.TrimSpace(input.PublicModel)
- input.MatchType = normalizeCompositeRouteMatchType(input.MatchType)
- input.TargetPlatform = strings.TrimSpace(input.TargetPlatform)
- input.UpstreamModel = strings.TrimSpace(input.UpstreamModel)
- input.Endpoint = normalizeCompositeRouteEndpoint(input.Endpoint)
- // 仅对 exact 路由把空 upstream_model 回填成 public_model:exact 命中时请求模型
- // 恒等于 public_model,回填只影响持久化/后台展示,保留原有契约不变。
- // prefix 路由留空则不回填——Resolve 会回退到具体请求模型,从而透传原始模型
- // (否则 public=deepseek-v4 的前缀路由会把 deepseek-v4-flash / deepseek-v4-pro
- // 都塌缩成固定的 deepseek-v4)。显式填写 upstream_model 时任何模式都原样固定转发。
- if input.UpstreamModel == "" && input.MatchType == CompositeRouteMatchExact {
- input.UpstreamModel = input.PublicModel
- }
- input.Notes = strings.TrimSpace(input.Notes)
- return input
- }
|