Ctrl K
ComunidadGuías…

internal/gateway/request_model_resolution.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package gateway

import (
	"context"
	"errors"
	"net/http"
	"strings"

	"github.com/enterpilot/gomodel/internal/core"
)

type modelCountProvider interface {
	ModelCount() int
}

type providerModelRefresher interface {
	RefreshProviderModels(ctx context.Context, providerSelector string) (int, error)
}

type modelRefreshTargetResolver interface {
	ResolveRefreshTarget(requested core.RequestedModelSelector) (core.ModelSelector, bool, error)
}

// ResolvedProviderName returns the configured provider instance name for a selector.
func ResolvedProviderName(provider core.RoutableProvider, selector core.ModelSelector, fallback string) string {
	fallback = strings.TrimSpace(fallback)
	if provider == nil {
		return fallback
	}
	if named, ok := provider.(core.ProviderNameResolver); ok {
		if providerName := strings.TrimSpace(named.GetProviderName(selector.QualifiedModel())); providerName != "" {
			return providerName
		}
	}
	// A model the catalog does not list (a pinned jev version) still belongs to
	// the provider its selector names, not to the fallback's.
	if providerName, providerType := configuredSelectorProvider(provider, selector); providerType != "" {
		return providerName
	}
	return fallback
}

// configuredSelectorProvider returns the provider a selector names explicitly
// and that provider's type, or empty strings when the selector names no
// configured provider.
func configuredSelectorProvider(provider core.RoutableProvider, selector core.ModelSelector) (string, string) {
	providerName := strings.TrimSpace(selector.Provider)
	named, ok := provider.(core.ProviderNameTypeResolver)
	if providerName == "" || !ok {
		return "", ""
	}
	providerType := strings.TrimSpace(named.GetProviderTypeForName(providerName))
	if providerType == "" {
		return "", ""
	}
	return providerName, providerType
}

// ResolvedWorkflowProviderName returns the configured provider name recorded in a resolution.
func ResolvedWorkflowProviderName(resolution *core.RequestModelResolution) string {
	if resolution == nil {
		return ""
	}
	if providerName := strings.TrimSpace(resolution.ProviderName); providerName != "" {
		return providerName
	}
	return strings.TrimSpace(resolution.ResolvedSelector.Provider)
}

// WorkflowProviderNameForType maps a provider type to its configured provider name when available.
func WorkflowProviderNameForType(provider core.RoutableProvider, providerType string) string {
	providerType = strings.TrimSpace(providerType)
	if providerType == "" || provider == nil {
		return ""
	}
	if named, ok := provider.(core.ProviderTypeNameResolver); ok {
		return strings.TrimSpace(named.GetProviderNameForType(providerType))
	}
	return ""
}

// ResolveRequestModelWithAuthorizer resolves and validates a requested selector.
func ResolveRequestModelWithAuthorizer(
	ctx context.Context,
	provider core.RoutableProvider,
	resolver ModelResolver,
	authorizer ModelAuthorizer,
	requested core.RequestedModelSelector,
) (*core.RequestModelResolution, error) {
	requested = core.NewRequestedModelSelector(requested.Model, requested.ProviderHint)

	resolvedSelector, route, aliasApplied, err := resolveExecutionRoute(ctx, provider, resolver, requested)
	refreshed := false
	if err != nil {
		var refreshErr error
		refreshed, refreshErr = refreshProviderModelsForResolution(ctx, provider, resolver, requested, resolvedSelector)
		if refreshErr != nil {
			return nil, refreshErr
		}
		if !refreshed {
			return nil, resolutionError(err)
		}
		resolvedSelector, route, aliasApplied, err = resolveExecutionRoute(ctx, provider, resolver, requested)
		if err != nil {
			return nil, resolutionError(err)
		}
	}
	if resolvedSelector == (core.ModelSelector{}) {
		resolvedSelector, err = requested.Normalize()
		if err != nil {
			return nil, core.NewInvalidRequestError(err.Error(), err)
		}
	}

	resolvedModel := resolvedSelector.QualifiedModel()
	if counted, ok := provider.(modelCountProvider); ok && counted.ModelCount() == 0 {
		if !refreshed {
			var refreshErr error
			refreshed, refreshErr = refreshProviderModelsForResolution(ctx, provider, resolver, requested, resolvedSelector)
			if refreshErr != nil {
				return nil, refreshErr
			}
			if refreshed {
				resolvedSelector, route, aliasApplied, err = resolveExecutionRoute(ctx, provider, resolver, requested)
				if err != nil {
					return nil, resolutionError(err)
				}
				resolvedModel = resolvedSelector.QualifiedModel()
			}
		}
	}
	if counted, ok := provider.(modelCountProvider); ok && counted.ModelCount() == 0 {
		return nil, core.NewProviderError("", http.StatusServiceUnavailable, "model registry not initialized: no models have been loaded yet, retry shortly", nil)
	}
	if !provider.Supports(resolvedModel) {
		if !refreshed {
			var refreshErr error
			refreshed, refreshErr = refreshProviderModelsForResolution(ctx, provider, resolver, requested, resolvedSelector)
			if refreshErr != nil {
				return nil, refreshErr
			}
			if refreshed {
				resolvedSelector, route, aliasApplied, err = resolveExecutionRoute(ctx, provider, resolver, requested)
				if err != nil {
					return nil, resolutionError(err)
				}
				resolvedModel = resolvedSelector.QualifiedModel()
			}
		}
	}
	if !provider.Supports(resolvedModel) {
		return nil, core.NewModelNotFoundError(resolvedModel)
	}
	if authorizer != nil {
		if err := authorizer.ValidateModelAccess(ctx, resolvedSelector); err != nil {
			return nil, err
		}
	}

	resolution := &core.RequestModelResolution{
		Requested:        requested,
		ResolvedSelector: resolvedSelector,
		ProviderType:     strings.TrimSpace(provider.GetProviderType(resolvedModel)),
		ProviderName:     ResolvedProviderName(provider, resolvedSelector, ""),
		AliasApplied:     aliasApplied,
		VirtualModelPath: route,
	}
	if slowdownResolver, ok := resolver.(ModelSlowdownResolver); ok {
		resolution.Slowdown = slowdownResolver.ResolveSlowdown(ctx, requested, resolvedSelector)
	}
	resolution.CacheDerivedSelectors()
	return resolution, nil
}

// resolutionError reports a failed model resolution. A typed gateway error
// keeps its status (the router answers 503 while the model registry is still
// empty, so clients retry); anything else is a selector the caller got wrong.
func resolutionError(err error) error {
	if gatewayErr, ok := errors.AsType[*core.GatewayError](err); ok {
		return gatewayErr
	}
	return core.NewInvalidRequestError(err.Error(), err)
}

func refreshProviderModelsForResolution(
	ctx context.Context,
	provider core.RoutableProvider,
	resolver ModelResolver,
	requested core.RequestedModelSelector,
	resolvedSelector core.ModelSelector,
) (bool, error) {
	refresher, ok := provider.(providerModelRefresher)
	if !ok {
		return false, nil
	}

	providerSelector := strings.TrimSpace(resolvedSelector.Provider)
	if providerSelector == "" {
		if targetResolver, ok := resolver.(modelRefreshTargetResolver); ok {
			selector, ok, err := targetResolver.ResolveRefreshTarget(requested)
			if err != nil {
				return false, err
			}
			if ok {
				providerSelector = strings.TrimSpace(selector.Provider)
			}
		}
	}
	if providerSelector == "" {
		selector, err := requested.Normalize()
		if err != nil {
			return false, nil
		}
		providerSelector = strings.TrimSpace(selector.Provider)
	}
	if providerSelector == "" {
		return false, nil
	}

	_, err := refresher.RefreshProviderModels(ctx, providerSelector)
	return true, err
}

// ResolveExecutionSelector applies explicit and provider-owned selector
// resolution. ctx carries the effective request user path so a resolver that
// implements UserPathModelResolver can apply user_path-scoped redirects.
func ResolveExecutionSelector(
	ctx context.Context,
	provider core.RoutableProvider,
	resolver ModelResolver,
	requested core.RequestedModelSelector,
) (core.ModelSelector, bool, error) {
	selector, _, aliasApplied, err := resolveExecutionRoute(ctx, provider, resolver, requested)
	return selector, aliasApplied, err
}

// resolveExecutionRoute is ResolveExecutionSelector that also returns the
// virtual-model route a RoutedModelResolver reports.
func resolveExecutionRoute(
	ctx context.Context,
	provider core.RoutableProvider,
	resolver ModelResolver,
	requested core.RequestedModelSelector,
) (core.ModelSelector, []string, bool, error) {
	requested = core.NewRequestedModelSelector(requested.Model, requested.ProviderHint)

	var (
		resolvedSelector core.ModelSelector
		route            []string
		aliasApplied     bool
		err              error
	)

	if resolver != nil {
		resolvedSelector, route, aliasApplied, err = resolveRequestedModel(ctx, resolver, requested)
		if err != nil {
			return core.ModelSelector{}, nil, false, err
		}
		requested = core.NewRequestedModelSelector(resolvedSelector.QualifiedModel(), "")
	}

	if providerResolver, ok := provider.(ModelResolver); ok {
		providerSelector, providerChanged, err := providerResolver.ResolveModel(requested)
		if err != nil {
			if resolvedSelector != (core.ModelSelector{}) {
				// Preserve alias targets so callers can refresh the concrete provider before retrying.
				return resolvedSelector, route, aliasApplied, err
			}
			return core.ModelSelector{}, nil, false, err
		}
		return providerSelector, route, aliasApplied || providerChanged, nil
	}

	if resolvedSelector != (core.ModelSelector{}) {
		return resolvedSelector, route, aliasApplied, nil
	}

	resolvedSelector, err = requested.Normalize()
	return resolvedSelector, route, aliasApplied, err
}

// resolveRequestedModel resolves through a RoutedModelResolver or
// UserPathModelResolver when the resolver implements one (applying
// user_path-scoped redirects), falling back to the unscoped ModelResolver
// otherwise. Only a RoutedModelResolver reports a route.
func resolveRequestedModel(ctx context.Context, resolver ModelResolver, requested core.RequestedModelSelector) (core.ModelSelector, []string, bool, error) {
	if routed, ok := resolver.(RoutedModelResolver); ok {
		return routed.ResolveModelRouteForUserPath(ctx, requested)
	}
	if scoped, ok := resolver.(UserPathModelResolver); ok {
		selector, changed, err := scoped.ResolveModelForUserPath(ctx, requested)
		return selector, nil, changed, err
	}
	selector, changed, err := resolver.ResolveModel(requested)
	return selector, nil, changed, err
}