Ctrl K
ComunidadGuías…

internal/app/language_models.go

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

Archivo original ↗Preguntar a KiBot
package app

import (
	"log/slog"
	"strings"

	"github.com/yeomyeonggeori/blueclaw/internal/config"
	"github.com/yeomyeonggeori/blueclaw/internal/llm"
	"github.com/yeomyeonggeori/blueprotocol/agentcontract"
	"github.com/yeomyeonggeori/blueprotocol/model"
)

func resolveTaskTierLanguageModelProviders(runtimeConfiguration config.RuntimeConfiguration, logger *slog.Logger) (agentcontract.TaskTierLanguageModels, error) {
	providers, errorValue := resolveModelTierProviders(runtimeConfiguration, logger)
	if errorValue != nil {
		return agentcontract.TaskTierLanguageModels{}, errorValue
	}
	minimumModelTier := normalizeModelTier(runtimeConfiguration.LanguageModel.MinimumModelTier)
	maximumModelTier := normalizeModelTier(runtimeConfiguration.LanguageModel.MaximumModelTier)
	if maximumModelTier == "" {
		return agentcontract.TaskTierLanguageModels{
			XLow:   providers.xLow,
			Low:    providers.low,
			Medium: providers.medium,
			High:   providers.high,
			XHigh:  providers.xHigh,
			Max:    providers.max,
		}, nil
	}
	return agentcontract.TaskTierLanguageModels{
		XLow:   providers.providerWithinBounds("xlow", minimumModelTier, maximumModelTier),
		Low:    providers.providerWithinBounds("low", minimumModelTier, maximumModelTier),
		Medium: providers.providerWithinBounds("medium", minimumModelTier, maximumModelTier),
		High:   providers.providerWithinBounds("high", minimumModelTier, maximumModelTier),
		XHigh:  providers.providerWithinBounds("xhigh", minimumModelTier, maximumModelTier),
		Max:    providers.providerWithinBounds("max", minimumModelTier, maximumModelTier),
	}, nil
}

func resolveIntakeLanguageModelProvider(runtimeConfiguration config.RuntimeConfiguration, logger *slog.Logger) (llm.LanguageModelProvider, error) {
	if !runtimeConfiguration.Agent.Intake.Enabled {
		return nil, nil
	}
	intakeConfiguration := runtimeConfiguration
	intakeConfiguration.LanguageModel.Capability.ExecutionMode = firstNonEmptyString(
		runtimeConfiguration.Agent.Intake.ExecutionMode,
		runtimeConfiguration.LanguageModel.Capability.ExecutionMode,
	)
	providers, errorValue := resolveModelTierProviders(intakeConfiguration, logger)
	if errorValue != nil {
		return nil, errorValue
	}
	if maximumModelTier := normalizeModelTier(runtimeConfiguration.LanguageModel.MaximumModelTier); maximumModelTier != "" {
		return providers.providerForTier(maximumModelTier), nil
	}
	return llm.FallbackLanguageModelProvider{
		PrimaryProvider:  providers.mediumTierOnly,
		FallbackProvider: providers.highTierOnly,
		PrimaryLabel:     "intake",
		FallbackLabel:    "high",
		Logger:           logger,
	}, nil
}

type modelTierProviders struct {
	xLow           llm.LanguageModelProvider
	low            llm.LanguageModelProvider
	medium         llm.LanguageModelProvider
	high           llm.LanguageModelProvider
	xHigh          llm.LanguageModelProvider
	max            llm.LanguageModelProvider
	mediumTierOnly llm.LanguageModelProvider
	highTierOnly   llm.LanguageModelProvider
}

func resolveModelTierProviders(runtimeConfiguration config.RuntimeConfiguration, logger *slog.Logger) (modelTierProviders, error) {
	tierProviderFactory, errorValue := llm.NewTierProviderFactory(runtimeConfiguration)
	if errorValue != nil {
		logModelTierFailure(logger, "", errorValue)
		return modelTierProviders{}, errorValue
	}
	tiers := map[string]llm.TierProvider{}
	for _, modelTier := range llm.ModelTiers {
		tierProvider, tierError := tierProviderFactory(modelTier)
		if tierError != nil {
			logModelTierFailure(logger, modelTier, tierError)
			return modelTierProviders{}, tierError
		}
		tiers[modelTier] = tierProvider
	}
	logResolvedModelTiers(logger, tiers)
	if normalizeModelTier(runtimeConfiguration.LanguageModel.MaximumModelTier) != "" {
		return cappedModelTierProviders(tiers, logger), nil
	}
	return uncappedModelTierProviders(tiers, logger), nil
}

func cappedModelTierProviders(tiers map[string]llm.TierProvider, logger *slog.Logger) modelTierProviders {
	xLowModel := taggedTierProvider(tiers, "xlow")
	lowProvider := descendingFallbackProvider(taggedTierProvider(tiers, "low"), xLowModel, "low", "xlow", logger)
	return modelTierProvidersAboveLow(tiers, modelTierProvidersBelowMedium{
		xLow:           llm.VisionFallbackProvider{TextOnlyModel: xLowModel, VisionModel: lowProvider},
		low:            lowProvider,
		mediumFallback: lowProvider,
	}, logger)
}

func uncappedModelTierProviders(tiers map[string]llm.TierProvider, logger *slog.Logger) modelTierProviders {
	lowTierOnly := taggedTierProvider(tiers, "low")
	lowProvider := lowTierOnly
	if tiers["medium"].Reaches != tiers["low"].Reaches {
		lowProvider = descendingFallbackProvider(lowTierOnly, taggedTierProvider(tiers, "medium"), "low", "medium", logger)
	}
	return modelTierProvidersAboveLow(tiers, modelTierProvidersBelowMedium{
		xLow:           descendingFallbackProvider(taggedTierProvider(tiers, "xlow"), lowProvider, "xlow", "low", logger),
		low:            lowProvider,
		mediumFallback: lowTierOnly,
	}, logger)
}

type modelTierProvidersBelowMedium struct {
	xLow           llm.LanguageModelProvider
	low            llm.LanguageModelProvider
	mediumFallback llm.LanguageModelProvider
}

func modelTierProvidersAboveLow(tiers map[string]llm.TierProvider, lowerTiers modelTierProvidersBelowMedium, logger *slog.Logger) modelTierProviders {
	mediumTierOnly := taggedTierProvider(tiers, "medium")
	highTierOnly := taggedTierProvider(tiers, "high")
	mediumProvider := descendingFallbackProvider(mediumTierOnly, lowerTiers.mediumFallback, "medium", "low", logger)
	highProvider := descendingFallbackProvider(highTierOnly, mediumProvider, "high", "medium", logger)
	xHighProvider := descendingFallbackProvider(taggedTierProvider(tiers, "xhigh"), highProvider, "xhigh", "high", logger)
	return modelTierProviders{
		xLow:           lowerTiers.xLow,
		low:            lowerTiers.low,
		medium:         mediumProvider,
		high:           highProvider,
		xHigh:          xHighProvider,
		max:            descendingFallbackProvider(taggedTierProvider(tiers, "max"), xHighProvider, "max", "xhigh", logger),
		mediumTierOnly: mediumTierOnly,
		highTierOnly:   highTierOnly,
	}
}

func taggedTierProvider(tiers map[string]llm.TierProvider, modelTier string) llm.LanguageModelProvider {
	return llm.WithModelTier(tiers[modelTier].Provider, modelTier)
}

func descendingFallbackProvider(primaryProvider llm.LanguageModelProvider, fallbackProvider llm.LanguageModelProvider, primaryLabel string, fallbackLabel string, logger *slog.Logger) llm.LanguageModelProvider {
	return llm.FallbackLanguageModelProvider{
		PrimaryProvider: primaryProvider, FallbackProvider: fallbackProvider,
		PrimaryLabel: primaryLabel, FallbackLabel: fallbackLabel, Logger: logger,
	}
}

func (providers modelTierProviders) providerWithinBounds(requestedTier string, minimumModelTier string, maximumModelTier string) llm.LanguageModelProvider {
	boundedTier := requestedTier
	if minimumModelTier != "" && modelTierRank(boundedTier) < modelTierRank(minimumModelTier) {
		boundedTier = minimumModelTier
	}
	if modelTierRank(boundedTier) > modelTierRank(maximumModelTier) {
		boundedTier = maximumModelTier
	}
	return providers.providerForTier(boundedTier)
}

func (providers modelTierProviders) providerForTier(modelTier string) llm.LanguageModelProvider {
	switch normalizeModelTier(modelTier) {
	case "max":
		return providers.max
	case "xhigh":
		return providers.xHigh
	case "high":
		return providers.high
	case "medium":
		return providers.medium
	case "low":
		return providers.low
	default:
		return providers.xLow
	}
}

func normalizeModelTier(modelTier string) string {
	normalizedModelTier := strings.ToLower(strings.TrimSpace(modelTier))
	for _, supportedModelTier := range llm.ModelTiers {
		if normalizedModelTier == supportedModelTier {
			return supportedModelTier
		}
	}
	return ""
}

func modelTierRank(modelTier string) int {
	for rank, supportedModelTier := range llm.ModelTiers {
		if normalizeModelTier(modelTier) == supportedModelTier {
			return rank
		}
	}
	return 0
}

func logResolvedModelTiers(logger *slog.Logger, tiers map[string]llm.TierProvider) {
	if logger == nil {
		return
	}
	attributes := []any{}
	for _, modelTier := range llm.ModelTiers {
		attributes = append(attributes, modelTier, tiers[modelTier].Reaches)
	}
	logger.Info("resolved task model tiers", attributes...)
}

func logModelTierFailure(logger *slog.Logger, modelTier string, errorValue error) {
	if logger == nil {
		return
	}
	logger.Error("language model provider configuration failed", "tier", modelTier, "error", errorValue.Error())
}

func classificationLanguageModelProvider(taskTierLanguageModels agentcontract.TaskTierLanguageModels, intakeLanguageModelProvider model.LanguageModelProvider) model.LanguageModelProvider {
	if taskTierLanguageModels.XLow != nil {
		return taskTierLanguageModels.XLow
	}
	if intakeLanguageModelProvider != nil {
		return intakeLanguageModelProvider
	}
	return taskTierLanguageModels.High
}

func newConfiguredDecisionModel(runtimeConfiguration config.RuntimeConfiguration, logger *slog.Logger) model.DecisionModel {
	decisionModel, errorValue := llm.NewConfiguredDecisionModel(runtimeConfiguration)
	if errorValue != nil {
		if logger != nil {
			logger.Error("decision model configuration failed", "error", errorValue.Error())
		}
		return nil
	}
	return decisionModel
}

func newConfiguredVisualReviewModel(runtimeConfiguration config.RuntimeConfiguration, logger *slog.Logger) model.DecisionModel {
	visualReviewModel, errorValue := llm.NewConfiguredVisualReviewModel(runtimeConfiguration)
	if errorValue != nil {
		if logger != nil {
			logger.Error("visual review model configuration failed", "error", errorValue.Error())
		}
		return nil
	}
	return visualReviewModel
}

func newToolSelector(newBundledToolSelector BundledToolSelectorFactory, decisionModel model.DecisionModel) agentcontract.ToolSelector {
	if newBundledToolSelector == nil || decisionModel == nil {
		return nil
	}
	return newBundledToolSelector(decisionModel)
}